test/wcc: retire carriers migrated to fixture and @test owners
Behavior and reject carriers whose rows now live as test/wcc/data fixtures or test/lang @test tables; the 31 library launchers plus 900_stdlib, whose only assertion was an existing @test source's exit status (LIBRARY_TESTS runs those sources directly); and the test/run shell harness, its protocol self-test, and the runww corpus runner. Their Makefile registrations dangle until the target-graph flip in the next commits.
This commit is contained in:
643
test/run
643
test/run
@@ -1,643 +0,0 @@
|
||||
#!/bin/sh
|
||||
# A manifest-backed collector prevents a lost parallel worker from shrinking a
|
||||
# required corpus into a false green.
|
||||
|
||||
BIN=${BIN:-out/bin}
|
||||
WW=${WW:-$BIN/ww}
|
||||
TEST_DIR=${TEST_DIR:-test/wcc}
|
||||
TEST_EXPECTED_MIN=${TEST_EXPECTED_MIN:-337}
|
||||
TEST_TIMEOUT=${TEST_TIMEOUT:-900s}
|
||||
TEST_KILL_AFTER=${TEST_KILL_AFTER:-10s}
|
||||
TEST_WORKER=${TEST_WORKER:-$0}
|
||||
|
||||
# Required-suite results describe the toolchain under BIN, not ambient driver
|
||||
# overrides from a developer shell. Individual tests still set overrides
|
||||
# explicitly when that behavior is what they exercise.
|
||||
unset WW_W6C WW_W6A WW_W6L WW_LIB WW_SRCLIB WW_PKGCACHE
|
||||
|
||||
atomic_record() {
|
||||
record_path=$1
|
||||
record_text=$2
|
||||
record_tmp=$record_path.tmp.$$
|
||||
if ! printf '%s\n' "$record_text" > "$record_tmp"; then
|
||||
rm -f "$record_tmp"
|
||||
return 1
|
||||
fi
|
||||
if ! mv "$record_tmp" "$record_path"; then
|
||||
rm -f "$record_tmp"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Worker mode owns one result prefix. A result becomes visible only after every
|
||||
# capture and timing file is complete, so the collector cannot accept a torn
|
||||
# terminal record as a finished test.
|
||||
if [ "${1:-}" = "--one" ]; then
|
||||
[ "$#" -eq 3 ] || exit 64
|
||||
prefix=$2
|
||||
t=$3
|
||||
name=${t##*/}
|
||||
name=${name%.c}
|
||||
short=${name#[0-9][0-9][0-9]_}
|
||||
bin=$BIN/test_$short
|
||||
|
||||
if ! atomic_record "$prefix.started" "WWTEST_STARTED 1"; then
|
||||
exit 70
|
||||
fi
|
||||
if ! : > "$prefix.out" || ! : > "$prefix.err"; then
|
||||
exit 70
|
||||
fi
|
||||
|
||||
t0=$(date +%s.%N)
|
||||
outcome=pass
|
||||
rc=0
|
||||
if [ ! -x "$bin" ]; then
|
||||
printf 'unwired test: no executable %s\n' "$bin" > "$prefix.err"
|
||||
outcome=harness_error
|
||||
rc=127
|
||||
else
|
||||
# The child records its own status before the timeout wrapper exits.
|
||||
# That distinguishes a real child exit 124/137 from expiration, and
|
||||
# child exits 125-127 from timeout's reserved infrastructure codes.
|
||||
command_status=$prefix.command-status
|
||||
timeout_diag=$prefix.timeout-diag
|
||||
if LC_ALL=C timeout --verbose --kill-after="$TEST_KILL_AFTER" \
|
||||
"$TEST_TIMEOUT" sh -c '
|
||||
"$1" > "$2" 2> "$3"
|
||||
command_rc=$?
|
||||
status_tmp=$4.tmp.$$
|
||||
printf "%s\n" "$command_rc" > "$status_tmp" || exit 70
|
||||
mv "$status_tmp" "$4" || exit 70
|
||||
' sh "$bin" "$prefix.out" "$prefix.err" "$command_status" \
|
||||
2> "$timeout_diag"; then
|
||||
timeout_rc=0
|
||||
else
|
||||
timeout_rc=$?
|
||||
fi
|
||||
deadline_expired=0
|
||||
if grep -Eq '^timeout: sending signal (TERM|KILL) to command ' \
|
||||
"$timeout_diag"; then
|
||||
deadline_expired=1
|
||||
fi
|
||||
if [ -s "$timeout_diag" ]; then
|
||||
cat "$timeout_diag" >> "$prefix.err"
|
||||
fi
|
||||
if [ "$deadline_expired" -eq 1 ]; then
|
||||
outcome=timeout
|
||||
rc=$timeout_rc
|
||||
elif [ -f "$command_status" ]; then
|
||||
rc="" extra=""
|
||||
IFS=' ' read -r rc extra < "$command_status"
|
||||
case $rc in
|
||||
''|*[!0-9]*) rc_valid=0 ;;
|
||||
*) rc_valid=1 ;;
|
||||
esac
|
||||
if [ "$rc_valid" -ne 1 ] || [ -n "$extra" ] \
|
||||
|| [ "$timeout_rc" -ne 0 ]; then
|
||||
printf 'timeout wrapper returned %s with child status %s\n' \
|
||||
"$timeout_rc" "${rc:-missing}" >> "$prefix.err"
|
||||
outcome=harness_error
|
||||
rc=125
|
||||
elif [ "$rc" -eq 0 ]; then
|
||||
outcome=pass
|
||||
elif [ "$rc" -eq 77 ]; then
|
||||
outcome=skip
|
||||
else
|
||||
outcome=fail
|
||||
fi
|
||||
else
|
||||
printf 'timeout wrapper produced no child status (rc=%s)\n' \
|
||||
"$timeout_rc" >> "$prefix.err"
|
||||
outcome=harness_error
|
||||
case $timeout_rc in
|
||||
125|126|127) rc=$timeout_rc ;;
|
||||
*) rc=125 ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
t1=$(date +%s.%N)
|
||||
dur_tmp=$prefix.dur.tmp.$$
|
||||
if ! awk -v a="$t0" -v b="$t1" -v n="$name" \
|
||||
'BEGIN{printf "%.3f\t%s\n", b-a, n}' > "$dur_tmp"; then
|
||||
rm -f "$dur_tmp"
|
||||
exit 70
|
||||
fi
|
||||
if ! mv "$dur_tmp" "$prefix.dur"; then
|
||||
rm -f "$dur_tmp"
|
||||
exit 70
|
||||
fi
|
||||
if ! atomic_record "$prefix.result" "WWTEST_RESULT 1 $outcome $rc"; then
|
||||
exit 70
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
RESULTS=$(mktemp -d "${TMPDIR:-/tmp}/wwtest.XXXXXX") || exit 2
|
||||
trap 'rm -rf "$RESULTS"' EXIT
|
||||
trap 'exit 1' HUP INT TERM
|
||||
|
||||
ALL_MANIFEST=$RESULTS/all.manifest
|
||||
PHASE1_MANIFEST=$RESULTS/phase1.manifest
|
||||
PHASE2_MANIFEST=$RESULTS/phase2.manifest
|
||||
RUN1_MANIFEST=$RESULTS/run1.manifest
|
||||
RUN2_MANIFEST=$RESULTS/run2.manifest
|
||||
SELECTED_NAMES=$RESULTS/selected.names
|
||||
CACHED_NAMES=$RESULTS/cached.names
|
||||
ALL_NAMES=$RESULTS/all.names
|
||||
for manifest in "$ALL_MANIFEST" "$PHASE1_MANIFEST" "$PHASE2_MANIFEST" \
|
||||
"$RUN1_MANIFEST" "$RUN2_MANIFEST" "$SELECTED_NAMES" \
|
||||
"$CACHED_NAMES" "$ALL_NAMES"; do
|
||||
if ! : > "$manifest"; then
|
||||
printf 'HARNESS suite (cannot initialize manifest %s)\n' "$manifest"
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
is_phase2() {
|
||||
case $1 in
|
||||
950_*|990_*|991_*|992_*|993_*|994_*|995_*|996_*|997_*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
is_cached_gate() {
|
||||
case $1 in
|
||||
950_selfcheck.c|989_lib_byteid.c|\
|
||||
990_*|991_*|992_*|993_*|994_*|995_*|997_*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
for t in "$TEST_DIR"/*.c; do
|
||||
[ -f "$t" ] || continue
|
||||
printf '%s\n' "$t" >> "$ALL_MANIFEST"
|
||||
name=${t##*/}
|
||||
name=${name%.c}
|
||||
printf '%s\n' "$name" >> "$ALL_NAMES"
|
||||
if is_phase2 "${t##*/}"; then
|
||||
printf '%s\n' "$t" >> "$PHASE2_MANIFEST"
|
||||
else
|
||||
printf '%s\n' "$t" >> "$PHASE1_MANIFEST"
|
||||
fi
|
||||
done
|
||||
|
||||
discovered=$(awk 'END{print NR + 0}' "$ALL_MANIFEST")
|
||||
excluded=0
|
||||
preflight_error=0
|
||||
harness_error=0
|
||||
|
||||
case $TEST_EXPECTED_MIN in
|
||||
''|*[!0-9]*)
|
||||
printf 'HARNESS suite (invalid TEST_EXPECTED_MIN: %s)\n' "$TEST_EXPECTED_MIN"
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
;;
|
||||
*)
|
||||
if [ "$discovered" -eq 0 ]; then
|
||||
echo 'HARNESS suite (empty corpus)'
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
elif [ "$discovered" -lt "$TEST_EXPECTED_MIN" ]; then
|
||||
printf 'HARNESS suite (corpus shrank: %d < %d)\n' \
|
||||
"$discovered" "$TEST_EXPECTED_MIN"
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
duplicate_names=$(sort "$ALL_NAMES" | uniq -d)
|
||||
if [ -n "$duplicate_names" ]; then
|
||||
echo 'HARNESS suite (duplicate test identifiers)'
|
||||
printf '%s\n' "$duplicate_names"
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
|
||||
JOBS=${JOBS:-$(nproc 2>/dev/null || echo 4)}
|
||||
case $JOBS in
|
||||
''|*[!0-9]*|0)
|
||||
printf 'HARNESS suite (invalid JOBS: %s)\n' "$JOBS"
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
;;
|
||||
esac
|
||||
if ! command -v timeout >/dev/null 2>&1; then
|
||||
echo 'HARNESS suite (GNU timeout is unavailable)'
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
valid_duration() {
|
||||
awk -v duration="$1" 'BEGIN {
|
||||
if (duration !~ /^[0-9]+([.][0-9]+)?[smhd]?$/) exit 1
|
||||
sub(/[smhd]$/, "", duration)
|
||||
exit !(duration + 0 > 0)
|
||||
}'
|
||||
}
|
||||
if ! valid_duration "$TEST_TIMEOUT"; then
|
||||
printf 'HARNESS suite (invalid TEST_TIMEOUT: %s)\n' "$TEST_TIMEOUT"
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
if ! valid_duration "$TEST_KILL_AFTER"; then
|
||||
printf 'HARNESS suite (invalid TEST_KILL_AFTER: %s)\n' "$TEST_KILL_AFTER"
|
||||
preflight_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
|
||||
OUT_DIR=$(dirname "$BIN")
|
||||
CACHE_DIR=$OUT_DIR/.testcache
|
||||
CACHE_FILE=$CACHE_DIR/last-green
|
||||
|
||||
# The cache includes nonignored untracked inputs because a local fixture or
|
||||
# source file can change a gate even before it is added to the index.
|
||||
testcache_key() {
|
||||
cache_inputs=$RESULTS/cache-inputs
|
||||
if ! : > "$cache_inputs"; then
|
||||
return 1
|
||||
fi
|
||||
# Hash the effective built tools, runtime archive, and every executable
|
||||
# that the last-green policy may replace with a cached result.
|
||||
for b in ww w6c w6a w6l wwdump ww_ww w6c_ww w6a_ww w6l_ww wwdump_ww; do
|
||||
printf '%s\0' "$BIN/$b" >> "$cache_inputs" || return 1
|
||||
done
|
||||
printf '%s\0' "$OUT_DIR/lib/libwwrt.a" >> "$cache_inputs" || return 1
|
||||
while IFS= read -r t; do
|
||||
[ -n "$t" ] || continue
|
||||
if is_cached_gate "${t##*/}"; then
|
||||
name=${t##*/}
|
||||
name=${name%.c}
|
||||
short=${name#[0-9][0-9][0-9]_}
|
||||
printf '%s\0' "$BIN/test_$short" >> "$cache_inputs" || return 1
|
||||
fi
|
||||
printf '%s\0' "$t" >> "$cache_inputs" || return 1
|
||||
done < "$ALL_MANIFEST"
|
||||
if ! git ls-files --cached -z -- \
|
||||
selfhost lib cmd rt examples test/wcc test/run test/run_test.sh Makefile \
|
||||
>> "$cache_inputs"; then
|
||||
return 1
|
||||
fi
|
||||
# Generated package-cache state is not fixture input; hashing it would make
|
||||
# a gate mutate its own key. Other untracked files under data/ remain input.
|
||||
if ! git ls-files --others --exclude-standard -z -- \
|
||||
selfhost lib cmd rt examples test/wcc test/run test/run_test.sh Makefile \
|
||||
':(exclude,glob)test/wcc/data/**/.pkgcache/**' >> "$cache_inputs"; then
|
||||
return 1
|
||||
fi
|
||||
if ! sort -z -u "$cache_inputs" -o "$cache_inputs"; then
|
||||
return 1
|
||||
fi
|
||||
for setting in \
|
||||
"TEST_DIR=$TEST_DIR" \
|
||||
"TEST_EXPECTED_MIN=$TEST_EXPECTED_MIN" \
|
||||
"TEST_TIMEOUT=$TEST_TIMEOUT" \
|
||||
"TEST_KILL_AFTER=$TEST_KILL_AFTER" \
|
||||
"TEST_WORKER=$TEST_WORKER" \
|
||||
"JOBS=$JOBS"; do
|
||||
setting_key=$(printf '%s' "$setting" | md5sum) || return 1
|
||||
printf 'setting %s\n' "${setting_key%% *}"
|
||||
done
|
||||
# The index can name an unstaged deletion. Preserve that state in the
|
||||
# stream instead of letting md5sum abort and silently disable caching.
|
||||
xargs -0 -r sh -c '
|
||||
for cache_file do
|
||||
path_key=$(printf "%s" "$cache_file" | md5sum) || exit
|
||||
path_key=${path_key%% *}
|
||||
if [ -f "$cache_file" ]; then
|
||||
content_key=$(md5sum < "$cache_file") || exit
|
||||
content_key=${content_key%% *}
|
||||
mode=-
|
||||
[ -x "$cache_file" ] && mode=x
|
||||
printf "file %s %s %s\n" \
|
||||
"$path_key" "$mode" "$content_key"
|
||||
elif [ ! -e "$cache_file" ] && [ ! -L "$cache_file" ]; then
|
||||
printf "missing %s\n" "$path_key"
|
||||
else
|
||||
printf "unsupported cache input %s\n" "$path_key" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
' sh < "$cache_inputs"
|
||||
}
|
||||
|
||||
KEY=""
|
||||
if [ "${UNIT:-0}" != "1" ] && [ "$TEST_WORKER" = "$0" ]; then
|
||||
if key_stream=$(testcache_key 2>/dev/null); then
|
||||
KEY=$(printf '%s\n' "$key_stream" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
fi
|
||||
CACHE_HIT=0
|
||||
if [ "${COMMIT:-0}" = "1" ] && [ -n "$KEY" ] && [ -f "$CACHE_FILE" ] \
|
||||
&& [ "$(cat "$CACHE_FILE" 2>/dev/null)" = "$KEY" ]; then
|
||||
CACHE_HIT=1
|
||||
fi
|
||||
|
||||
emit_cached() {
|
||||
cached_name=$1
|
||||
cached_prefix=$RESULTS/$cached_name
|
||||
if ! atomic_record "$cached_prefix.result" "WWTEST_RESULT 1 cached 0"; then
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$cached_name" >> "$CACHED_NAMES"
|
||||
return 0
|
||||
}
|
||||
|
||||
plan_manifest() {
|
||||
plan_input=$1
|
||||
plan_run=$2
|
||||
while IFS= read -r t; do
|
||||
[ -n "$t" ] || continue
|
||||
name=${t##*/}
|
||||
name=${name%.c}
|
||||
printf '%s\n' "$name" >> "$SELECTED_NAMES"
|
||||
if [ "$CACHE_HIT" = 1 ] && is_cached_gate "${t##*/}"; then
|
||||
if ! emit_cached "$name"; then
|
||||
printf 'HARNESS %s (cannot record cached result)\n' "$name"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
else
|
||||
printf '%s\n' "$t" >> "$plan_run"
|
||||
fi
|
||||
done < "$plan_input"
|
||||
}
|
||||
|
||||
if [ "$preflight_error" -eq 0 ]; then
|
||||
plan_manifest "$PHASE1_MANIFEST" "$RUN1_MANIFEST"
|
||||
if [ "${UNIT:-0}" = "1" ]; then
|
||||
while IFS= read -r t; do
|
||||
[ -n "$t" ] || continue
|
||||
name=${t##*/}
|
||||
name=${name%.c}
|
||||
printf 'excluded %s (test-unit phase-2)\n' "$name"
|
||||
excluded=$((excluded + 1))
|
||||
done < "$PHASE2_MANIFEST"
|
||||
else
|
||||
plan_manifest "$PHASE2_MANIFEST" "$RUN2_MANIFEST"
|
||||
fi
|
||||
fi
|
||||
|
||||
launch_manifest() {
|
||||
launch_file=$1
|
||||
[ -s "$launch_file" ] || return 0
|
||||
while IFS= read -r t; do
|
||||
name=${t##*/}
|
||||
name=${name%.c}
|
||||
printf '%s/%s\0%s\0' "$RESULTS" "$name" "$t"
|
||||
done < "$launch_file" | \
|
||||
xargs -0 -r -n2 -P "$JOBS" "$TEST_WORKER" --one
|
||||
}
|
||||
|
||||
run_start=$(date +%s.%N)
|
||||
launcher_error=0
|
||||
p1_end=$run_start
|
||||
p2par_start=""
|
||||
p2par_end=""
|
||||
if [ "$preflight_error" -eq 0 ]; then
|
||||
if launch_manifest "$RUN1_MANIFEST"; then
|
||||
:
|
||||
else
|
||||
rc=$?
|
||||
printf 'HARNESS launcher (phase 1 xargs rc=%d)\n' "$rc"
|
||||
launcher_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
p1_end=$(date +%s.%N)
|
||||
if [ "${UNIT:-0}" != "1" ] && [ -s "$RUN2_MANIFEST" ]; then
|
||||
p2par_start=$(date +%s.%N)
|
||||
if launch_manifest "$RUN2_MANIFEST"; then
|
||||
:
|
||||
else
|
||||
rc=$?
|
||||
printf 'HARNESS launcher (phase 2 xargs rc=%d)\n' "$rc"
|
||||
launcher_error=1
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
p2par_end=$(date +%s.%N)
|
||||
fi
|
||||
fi
|
||||
|
||||
emit_output() {
|
||||
output_prefix=$1
|
||||
output_name=$2
|
||||
if [ -s "$output_prefix.out" ]; then
|
||||
printf '%s\n' "--- stdout: $output_name ---"
|
||||
cat "$output_prefix.out"
|
||||
fi
|
||||
if [ -s "$output_prefix.err" ]; then
|
||||
printf '%s\n' "--- stderr: $output_name ---"
|
||||
cat "$output_prefix.err"
|
||||
fi
|
||||
}
|
||||
|
||||
started=0
|
||||
completed=0
|
||||
pass=0
|
||||
fail=0
|
||||
skip=0
|
||||
timed_out=0
|
||||
cached=0
|
||||
worker_harness_error=0
|
||||
|
||||
if [ "$preflight_error" -eq 0 ]; then
|
||||
while IFS= read -r name; do
|
||||
[ -n "$name" ] || continue
|
||||
prefix=$RESULTS/$name
|
||||
expect_cached=0
|
||||
if grep -Fqx "$name" "$CACHED_NAMES"; then
|
||||
expect_cached=1
|
||||
fi
|
||||
has_started=0
|
||||
if [ -f "$prefix.started" ]; then
|
||||
if grep -qx 'WWTEST_STARTED 1' "$prefix.started"; then
|
||||
has_started=1
|
||||
started=$((started + 1))
|
||||
else
|
||||
printf 'HARNESS %s (malformed started record)\n' "$name"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$prefix.result" ]; then
|
||||
if [ "$has_started" -eq 1 ]; then
|
||||
printf 'HARNESS %s (started but no terminal result)\n' "$name"
|
||||
else
|
||||
printf 'HARNESS %s (worker never started)\n' "$name"
|
||||
fi
|
||||
harness_error=$((harness_error + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
magic="" version="" outcome="" result_rc="" extra=""
|
||||
IFS=' ' read -r magic version outcome result_rc extra < "$prefix.result"
|
||||
if [ "$magic" != WWTEST_RESULT ] || [ "$version" != 1 ] \
|
||||
|| [ -z "$outcome" ] || [ -z "$result_rc" ] || [ -n "$extra" ]; then
|
||||
printf 'HARNESS %s (malformed terminal result)\n' "$name"
|
||||
harness_error=$((harness_error + 1))
|
||||
continue
|
||||
fi
|
||||
case $result_rc in
|
||||
''|*[!0-9]*)
|
||||
printf 'HARNESS %s (nonnumeric terminal rc)\n' "$name"
|
||||
harness_error=$((harness_error + 1))
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$outcome" = cached ]; then
|
||||
if [ "$expect_cached" -ne 1 ] || [ "$has_started" -ne 0 ] \
|
||||
|| [ "$result_rc" != 0 ]; then
|
||||
printf 'HARNESS %s (invalid cached result)\n' "$name"
|
||||
harness_error=$((harness_error + 1))
|
||||
continue
|
||||
fi
|
||||
printf 'cached %s\n' "$name"
|
||||
cached=$((cached + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
capture_error=0
|
||||
for suffix in out err; do
|
||||
if [ ! -f "$prefix.$suffix" ]; then
|
||||
printf 'HARNESS %s (missing %s capture)\n' "$name" "$suffix"
|
||||
capture_error=1
|
||||
fi
|
||||
done
|
||||
if [ ! -s "$prefix.dur" ]; then
|
||||
printf 'HARNESS %s (missing duration record)\n' "$name"
|
||||
capture_error=1
|
||||
fi
|
||||
if [ "$capture_error" -ne 0 ]; then
|
||||
harness_error=$((harness_error + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
completed=$((completed + 1))
|
||||
if [ "$expect_cached" -eq 1 ] || [ "$has_started" -ne 1 ]; then
|
||||
printf 'HARNESS %s (terminal result without matching start)\n' "$name"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
case $outcome:$result_rc in
|
||||
pass:0)
|
||||
printf 'ok %s\n' "$name"
|
||||
pass=$((pass + 1))
|
||||
;;
|
||||
skip:77)
|
||||
printf 'skip %s (rc=77)\n' "$name"
|
||||
skip=$((skip + 1))
|
||||
if [ ! -s "$prefix.out" ] && [ ! -s "$prefix.err" ]; then
|
||||
echo 'skip reason: exit 77 (no output)'
|
||||
fi
|
||||
;;
|
||||
timeout:124|timeout:137)
|
||||
printf 'TIMEOUT %s\n' "$name"
|
||||
timed_out=$((timed_out + 1))
|
||||
;;
|
||||
fail:*)
|
||||
printf 'FAIL %s (rc=%s)\n' "$name" "$result_rc"
|
||||
fail=$((fail + 1))
|
||||
;;
|
||||
harness_error:125|harness_error:126|harness_error:127)
|
||||
printf 'HARNESS %s (worker rc=%s)\n' "$name" "$result_rc"
|
||||
worker_harness_error=$((worker_harness_error + 1))
|
||||
harness_error=$((harness_error + 1))
|
||||
;;
|
||||
*)
|
||||
printf 'HARNESS %s (invalid outcome/rc %s/%s)\n' \
|
||||
"$name" "$outcome" "$result_rc"
|
||||
worker_harness_error=$((worker_harness_error + 1))
|
||||
harness_error=$((harness_error + 1))
|
||||
;;
|
||||
esac
|
||||
emit_output "$prefix" "$name"
|
||||
done < "$SELECTED_NAMES"
|
||||
|
||||
for record in "$RESULTS"/*.result "$RESULTS"/*.started; do
|
||||
[ -f "$record" ] || continue
|
||||
record_name=${record##*/}
|
||||
record_name=${record_name%.result}
|
||||
record_name=${record_name%.started}
|
||||
if ! grep -Fqx "$record_name" "$SELECTED_NAMES"; then
|
||||
printf 'HARNESS %s (unexpected worker record)\n' "$record_name"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
selected=$((discovered - excluded))
|
||||
planned=$((selected - cached))
|
||||
terminal=$((pass + fail + skip + timed_out + worker_harness_error))
|
||||
if [ "$preflight_error" -eq 0 ]; then
|
||||
if [ "$started" -ne "$planned" ]; then
|
||||
printf 'HARNESS accounting (started %d != planned %d)\n' \
|
||||
"$started" "$planned"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
if [ "$completed" -ne "$planned" ]; then
|
||||
printf 'HARNESS accounting (completed %d != planned %d)\n' \
|
||||
"$completed" "$planned"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
if [ "$terminal" -ne "$completed" ]; then
|
||||
printf 'HARNESS accounting (terminal outcomes %d != completed %d)\n' \
|
||||
"$terminal" "$completed"
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
if [ $((completed + cached + excluded)) -ne "$discovered" ]; then
|
||||
printf 'HARNESS accounting (discovered corpus is incomplete)\n'
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# A cache decision is valid only if its complete input fingerprint is still
|
||||
# current after all workers have finished. This applies to hits as well as to
|
||||
# newly published keys.
|
||||
if [ "${UNIT:-0}" != "1" ] && [ -n "$KEY" ]; then
|
||||
end_key=""
|
||||
if end_stream=$(testcache_key 2>/dev/null); then
|
||||
end_key=$(printf '%s\n' "$end_stream" | md5sum | cut -d' ' -f1)
|
||||
fi
|
||||
if [ -z "$end_key" ] || [ "$end_key" != "$KEY" ]; then
|
||||
echo 'HARNESS cache (inputs changed during test run)'
|
||||
harness_error=$((harness_error + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
not_run=$((discovered - completed - cached))
|
||||
run_end=$(date +%s.%N)
|
||||
echo '--- timing: per-test (sec, desc) ---'
|
||||
cat "$RESULTS"/*.dur 2>/dev/null | sort -rn
|
||||
echo '--- timing: phase walls (sec) ---'
|
||||
awk -v rs="$run_start" -v p1e="$p1_end" \
|
||||
-v ppar_s="$p2par_start" -v ppar_e="$p2par_end" \
|
||||
-v re="$run_end" 'BEGIN{
|
||||
printf "phase1_parallel\t%.3f\n", p1e - rs
|
||||
if (ppar_e != "") printf "phase2_parallel\t%.3f\n", ppar_e - ppar_s
|
||||
printf "total\t%.3f\n", re - rs
|
||||
}'
|
||||
|
||||
printf 'summary: discovered=%d started=%d completed=%d pass=%d fail=%d skip=%d timeout=%d harness_error=%d cached=%d not_run=%d\n' \
|
||||
"$discovered" "$started" "$completed" "$pass" "$fail" "$skip" \
|
||||
"$timed_out" "$harness_error" "$cached" "$not_run"
|
||||
|
||||
green=1
|
||||
if [ "$preflight_error" -ne 0 ] || [ "$launcher_error" -ne 0 ] \
|
||||
|| [ "$fail" -ne 0 ] || [ "$skip" -ne 0 ] || [ "$timed_out" -ne 0 ] \
|
||||
|| [ "$harness_error" -ne 0 ]; then
|
||||
green=0
|
||||
fi
|
||||
if [ "$preflight_error" -eq 0 ] && [ "$not_run" -ne "$excluded" ]; then
|
||||
green=0
|
||||
fi
|
||||
|
||||
if [ "$green" -eq 1 ] && [ "${UNIT:-0}" != "1" ] \
|
||||
&& [ "$CACHE_HIT" != 1 ] && [ -n "$KEY" ]; then
|
||||
if ! mkdir -p "$CACHE_DIR" \
|
||||
|| ! atomic_record "$CACHE_FILE" "$KEY"; then
|
||||
echo 'HARNESS cache (cannot publish last-green key)'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
[ "$green" -eq 1 ] || exit 1
|
||||
exit 0
|
||||
316
test/run_test.sh
316
test/run_test.sh
@@ -1,316 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Synthetic corpora keep harness failure-path coverage independent of compiler builds.
|
||||
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
RUN=$ROOT/test/run
|
||||
WORK=$(mktemp -d "${TMPDIR:-/tmp}/ww-run-test.XXXXXX")
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
trap 'exit 1' HUP INT TERM
|
||||
|
||||
fail()
|
||||
{
|
||||
printf 'run_test: FAIL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
new_case()
|
||||
{
|
||||
CASE=$WORK/$1
|
||||
mkdir -p "$CASE/corpus" "$CASE/bin"
|
||||
}
|
||||
|
||||
add_source()
|
||||
{
|
||||
: > "$CASE/corpus/$1.c"
|
||||
}
|
||||
|
||||
add_binary()
|
||||
{
|
||||
name=$1
|
||||
body=$2
|
||||
{
|
||||
printf '%s\n' '#!/bin/sh'
|
||||
printf '%s\n' "$body"
|
||||
} > "$CASE/bin/test_$name"
|
||||
chmod +x "$CASE/bin/test_$name"
|
||||
}
|
||||
|
||||
run_case()
|
||||
{
|
||||
want=$1
|
||||
shift
|
||||
set +e
|
||||
env BIN="$CASE/bin" JOBS=2 "$@" sh "$RUN" > "$CASE/output" 2>&1
|
||||
GOT=$?
|
||||
set -e
|
||||
[ "$GOT" -eq "$want" ] || {
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): exit $GOT, want $want"
|
||||
}
|
||||
}
|
||||
|
||||
run_case_from()
|
||||
{
|
||||
want=$1
|
||||
run_dir=$2
|
||||
shift 2
|
||||
set +e
|
||||
(
|
||||
cd "$run_dir"
|
||||
env BIN="$CASE/bin" JOBS=2 "$@" sh "$RUN"
|
||||
) > "$CASE/output" 2>&1
|
||||
GOT=$?
|
||||
set -e
|
||||
[ "$GOT" -eq "$want" ] || {
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): exit $GOT, want $want"
|
||||
}
|
||||
}
|
||||
|
||||
assert_line()
|
||||
{
|
||||
grep -Fqx "$1" "$CASE/output" || {
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): missing line: $1"
|
||||
}
|
||||
}
|
||||
|
||||
assert_text()
|
||||
{
|
||||
grep -Fq "$1" "$CASE/output" || {
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): missing text: $1"
|
||||
}
|
||||
}
|
||||
|
||||
assert_summary_field()
|
||||
{
|
||||
key=$1
|
||||
want=$2
|
||||
summary=$(grep '^summary:' "$CASE/output" || true)
|
||||
[ "$(printf '%s\n' "$summary" | grep -c '^summary:')" -eq 1 ] || {
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): expected one summary line"
|
||||
}
|
||||
case " $summary " in
|
||||
*" $key=$want "*) ;;
|
||||
*)
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): summary $key is not $want"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_summary_nonzero()
|
||||
{
|
||||
key=$1
|
||||
summary=$(grep '^summary:' "$CASE/output" || true)
|
||||
[ "$(printf '%s\n' "$summary" | grep -c '^summary:')" -eq 1 ] || {
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): expected one summary line"
|
||||
}
|
||||
case " $summary " in
|
||||
*" $key=0 "*)
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): summary $key is zero"
|
||||
;;
|
||||
*" $key="*) ;;
|
||||
*)
|
||||
cat "$CASE/output" >&2
|
||||
fail "$(basename "$CASE"): summary has no $key field"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
new_case all_pass
|
||||
add_source 001_alpha
|
||||
add_source 002_beta
|
||||
add_binary alpha 'exit 0'
|
||||
add_binary beta 'exit 0'
|
||||
run_case 0 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=2 UNIT=1
|
||||
assert_line 'ok 001_alpha'
|
||||
assert_line 'ok 002_beta'
|
||||
assert_line 'summary: discovered=2 started=2 completed=2 pass=2 fail=0 skip=0 timeout=0 harness_error=0 cached=0 not_run=0'
|
||||
|
||||
new_case cache_publish
|
||||
add_source 990_cache
|
||||
add_source 996_dynamic
|
||||
add_binary cache '[ -z "${WW_W6C+x}${WW_W6A+x}${WW_W6L+x}${WW_LIB+x}${WW_SRCLIB+x}${WW_PKGCACHE+x}" ] || exit 9; exit 0'
|
||||
add_binary dynamic 'exit 0'
|
||||
run_case 0 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=2 COMMIT=1 \
|
||||
WW_W6C=/first/w6c WW_W6A=/first/w6a WW_W6L=/first/w6l \
|
||||
WW_LIB=/first/lib WW_SRCLIB=/first/src WW_PKGCACHE=/first/cache
|
||||
assert_summary_field pass 2
|
||||
assert_summary_field harness_error 0
|
||||
assert_summary_field cached 0
|
||||
[ -s "$CASE/.testcache/last-green" ] || fail "cache_publish: no cache key"
|
||||
run_case 0 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=2 COMMIT=1 \
|
||||
WW_W6C=/second/w6c WW_W6A=/second/w6a WW_W6L=/second/w6l \
|
||||
WW_LIB=/second/lib WW_SRCLIB=/second/src WW_PKGCACHE=/second/cache
|
||||
assert_line 'cached 990_cache'
|
||||
assert_line 'ok 996_dynamic'
|
||||
assert_line 'summary: discovered=2 started=1 completed=1 pass=1 fail=0 skip=0 timeout=0 harness_error=0 cached=1 not_run=0'
|
||||
|
||||
new_case cache_deleted_tracked
|
||||
add_source 990_cache
|
||||
add_binary cache 'exit 0'
|
||||
mkdir -p "$CASE/repo"
|
||||
: > "$CASE/repo/Makefile"
|
||||
git -C "$CASE/repo" init -q
|
||||
git -C "$CASE/repo" add Makefile
|
||||
rm -f "$CASE/repo/Makefile"
|
||||
run_case_from 0 "$CASE/repo" TEST_DIR="$CASE/corpus" \
|
||||
TEST_EXPECTED_MIN=1 COMMIT=1
|
||||
assert_summary_field pass 1
|
||||
[ -s "$CASE/.testcache/last-green" ] || \
|
||||
fail "cache_deleted_tracked: no cache key"
|
||||
run_case_from 0 "$CASE/repo" TEST_DIR="$CASE/corpus" \
|
||||
TEST_EXPECTED_MIN=1 COMMIT=1
|
||||
assert_summary_field cached 1
|
||||
assert_summary_field started 0
|
||||
|
||||
new_case fail
|
||||
add_source 001_fail
|
||||
add_binary fail 'printf "%s\n" "synthetic failure" >&2; exit 3'
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 UNIT=1
|
||||
assert_line 'FAIL 001_fail (rc=3)'
|
||||
assert_text 'synthetic failure'
|
||||
assert_summary_field fail 1
|
||||
assert_summary_field harness_error 0
|
||||
|
||||
new_case skip
|
||||
add_source 001_skip
|
||||
add_binary skip 'printf "%s\n" "synthetic prerequisite unavailable"; exit 77'
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 UNIT=1
|
||||
assert_line 'skip 001_skip (rc=77)'
|
||||
assert_text 'synthetic prerequisite unavailable'
|
||||
assert_summary_field skip 1
|
||||
assert_summary_field pass 0
|
||||
|
||||
new_case timeout
|
||||
add_source 001_slow
|
||||
add_binary slow 'trap "" TERM; sleep 5; exit 0'
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 \
|
||||
TEST_TIMEOUT=1 TEST_KILL_AFTER=1 UNIT=1
|
||||
assert_line 'TIMEOUT 001_slow'
|
||||
assert_summary_field timeout 1
|
||||
assert_summary_field pass 0
|
||||
|
||||
new_case reserved_exit_codes
|
||||
for item in 001:124 002:125 003:126 004:127 005:137; do
|
||||
prefix=${item%%:*}
|
||||
rc=${item#*:}
|
||||
add_source "${prefix}_exit${rc}"
|
||||
add_binary "exit${rc}" "exit $rc"
|
||||
done
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=5 UNIT=1
|
||||
assert_summary_field fail 5
|
||||
assert_summary_field timeout 0
|
||||
assert_summary_field harness_error 0
|
||||
|
||||
new_case timeout_wrapper_failure
|
||||
add_source 001_probe
|
||||
add_binary probe 'exit 0'
|
||||
mkdir -p "$CASE/path"
|
||||
{
|
||||
printf '%s\n' '#!/bin/sh'
|
||||
printf '%s\n' 'exit 124'
|
||||
} > "$CASE/path/timeout"
|
||||
chmod +x "$CASE/path/timeout"
|
||||
run_case 1 PATH="$CASE/path:$PATH" TEST_DIR="$CASE/corpus" \
|
||||
TEST_EXPECTED_MIN=1 UNIT=1
|
||||
assert_summary_field timeout 0
|
||||
assert_summary_nonzero harness_error
|
||||
|
||||
new_case missing_binary
|
||||
add_source 001_missing
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 UNIT=1
|
||||
assert_text 'HARNESS 001_missing'
|
||||
assert_summary_field harness_error 1
|
||||
assert_summary_field completed 1
|
||||
|
||||
new_case empty
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=0 UNIT=1
|
||||
assert_summary_field discovered 0
|
||||
assert_summary_field started 0
|
||||
assert_summary_field completed 0
|
||||
assert_summary_field harness_error 1
|
||||
|
||||
new_case below_minimum
|
||||
add_source 001_only
|
||||
add_binary only 'exit 0'
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=2 UNIT=1
|
||||
assert_summary_field discovered 1
|
||||
assert_summary_field harness_error 1
|
||||
|
||||
new_case zero_timeout
|
||||
add_source 001_bounded
|
||||
add_binary bounded 'exit 0'
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 \
|
||||
TEST_TIMEOUT=0 UNIT=1
|
||||
assert_summary_field discovered 1
|
||||
assert_summary_field started 0
|
||||
assert_summary_field harness_error 1
|
||||
|
||||
new_case launcher_failure
|
||||
add_source 001_lost
|
||||
add_binary lost 'exit 0'
|
||||
mkdir -p "$CASE/path"
|
||||
{
|
||||
printf '%s\n' '#!/bin/sh'
|
||||
printf '%s\n' 'exit 23'
|
||||
} > "$CASE/path/xargs"
|
||||
chmod +x "$CASE/path/xargs"
|
||||
run_case 1 PATH="$CASE/path:$PATH" TEST_DIR="$CASE/corpus" \
|
||||
TEST_EXPECTED_MIN=1 UNIT=1
|
||||
assert_summary_field discovered 1
|
||||
assert_summary_field started 0
|
||||
assert_summary_field completed 0
|
||||
assert_summary_nonzero harness_error
|
||||
|
||||
new_case lost_result
|
||||
add_source 001_lost
|
||||
add_binary lost 'exit 0'
|
||||
{
|
||||
printf '%s\n' '#!/bin/sh'
|
||||
printf '%s\n' 'prefix=$2'
|
||||
printf '%s\n' "printf '%s\\n' 'WWTEST_STARTED 1' > \"\$prefix.started\""
|
||||
printf '%s\n' 'exit 0'
|
||||
} > "$CASE/worker"
|
||||
chmod +x "$CASE/worker"
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 \
|
||||
TEST_WORKER="$CASE/worker" UNIT=1
|
||||
assert_summary_field discovered 1
|
||||
assert_summary_field started 1
|
||||
assert_summary_field completed 0
|
||||
assert_summary_nonzero harness_error
|
||||
|
||||
new_case incomplete_result
|
||||
add_source 001_incomplete
|
||||
add_binary incomplete 'exit 0'
|
||||
{
|
||||
printf '%s\n' '#!/bin/sh'
|
||||
printf '%s\n' 'prefix=$2'
|
||||
printf '%s\n' "printf '%s\\n' 'WWTEST_STARTED 1' > \"\$prefix.started\""
|
||||
printf '%s\n' "printf '%s\\n' 'WWTEST_RESULT 1 pass 0' > \"\$prefix.result\""
|
||||
printf '%s\n' 'exit 0'
|
||||
} > "$CASE/worker"
|
||||
chmod +x "$CASE/worker"
|
||||
run_case 1 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=1 \
|
||||
TEST_WORKER="$CASE/worker" UNIT=1
|
||||
assert_summary_field discovered 1
|
||||
assert_summary_field completed 0
|
||||
assert_summary_field pass 0
|
||||
assert_summary_nonzero harness_error
|
||||
|
||||
new_case unit_exclusion
|
||||
add_source 001_fast
|
||||
add_source 990_phase
|
||||
add_binary fast 'exit 0'
|
||||
run_case 0 TEST_DIR="$CASE/corpus" TEST_EXPECTED_MIN=2 UNIT=1
|
||||
assert_line 'excluded 990_phase (test-unit phase-2)'
|
||||
assert_line 'summary: discovered=2 started=1 completed=1 pass=1 fail=0 skip=0 timeout=0 harness_error=0 cached=0 not_run=1'
|
||||
|
||||
printf 'run_test: all harness regressions passed\n'
|
||||
351
test/runww.ww
351
test/runww.ww
@@ -1,351 +0,0 @@
|
||||
// runww — ww-native behavior-test harness, the analog of Go's
|
||||
// test/run.go. Each case file under test/wcc/data/<name>/case.ww carries
|
||||
// a leading `//ww:` directive describing how to test it; runww builds (and
|
||||
// optionally runs) the case through the cstage `ww` driver and checks the
|
||||
// directive holds. This replaces the per-test C harnesses that embedded ww
|
||||
// source as C string literals and duplicated fork/exec/grep boilerplate.
|
||||
//
|
||||
// This is T1: the routine behavioral-correctness gate, cstage-only for the
|
||||
// RUN/RUNEXIT/COMPILE arms. The wwstage-behavior run is deliberately NOT here
|
||||
// — per the rob/USER tier+stage split, a wwstage miscompile is already caught
|
||||
// by T2 (cstage.s vs wwstage.s byte-id over the same case.ww corpus, strictly
|
||||
// more sensitive than re-running behavior) or by T1's own cstage run (the
|
||||
// both-wrong-identical blind spot is cstage being wrong, which only a
|
||||
// behavioral check on cstage catches). So rule-10 stage-symmetry is enforced
|
||||
// by T2 (a separate pre-push tool), not by runww; routine runs stay cstage-only.
|
||||
//
|
||||
// The ERROR arm is the one exception: it runs BOTH stages. A rejected program
|
||||
// emits no `.s`, so T2's `.s`-cmp gate structurally CANNOT cover wwstage-reject
|
||||
// (drew-fold3-spec §1a). The retired C reject twins (948/949/944) asserted both
|
||||
// w6c AND w6c_ww reject with the same diagnostic body, so runww's ERROR arm runs
|
||||
// w6c_ww directly on the same case.ww and asserts it ALSO fails with the
|
||||
// substring present (#20 non-vacuity) — the coverage-equivalence safety net for
|
||||
// reject-row migration.
|
||||
//
|
||||
// Directives (first `//ww:` line of the case):
|
||||
// //ww:run compile + execute, expect exit 0
|
||||
// //ww:run-exit N compile + execute, expect exit N
|
||||
// //ww:error "<sub>" compilation must FAIL with <sub> on stderr
|
||||
// //ww:compile compile must succeed (don't run)
|
||||
//
|
||||
// Invoke (pilot): `BIN=out/bin out/bin/ww run test/runww.ww <case.ww>...`.
|
||||
// Full `ww test` integration is a later fold.
|
||||
//
|
||||
// Spawn + stderr capture are lifted from the driver's procrun
|
||||
// (selfhost/cmd/ww/main.ww:154: fork+execve+wait4+real-exit-code); the
|
||||
// only addition is the child-side dup2(efd, 2) redirect, mirroring the C
|
||||
// harnesses' `2>errf` so the parent can grep the diagnostic. #20
|
||||
// non-vacuity: a `//ww:error` row passes ONLY if the build failed AND the
|
||||
// diagnostic substring is present — a crash (no diagnostic) FAILS the row.
|
||||
//
|
||||
// Env: the harness execve's the child with a nil envp, so a case that
|
||||
// `import`s a lib module (needing WW_SRCLIB/WW_LIB) is out of pilot scope
|
||||
// until os exposes an envp() forwarder; the pilot cases are self-contained.
|
||||
|
||||
package main;
|
||||
|
||||
import os;
|
||||
|
||||
type dkind = enum i32 {
|
||||
NONE = 0,
|
||||
RUN = 1,
|
||||
RUNEXIT = 2,
|
||||
ERROR = 3,
|
||||
COMPILE = 4,
|
||||
};
|
||||
|
||||
type dirv = struct {
|
||||
kind: dkind,
|
||||
code: i32, // expected exit for RUNEXIT
|
||||
subp: *u8, // diagnostic substring (view into the case buffer)
|
||||
subn: i32,
|
||||
};
|
||||
|
||||
fn pr(s: str) void = {
|
||||
os.write(1i32, s.ptr, s.len: u64);
|
||||
};
|
||||
|
||||
fn mkstr(p: *u8, n: i32) str = {
|
||||
let s: str;
|
||||
s.ptr = p;
|
||||
s.len = n;
|
||||
return s;
|
||||
};
|
||||
|
||||
// cstr — NUL-terminated heap copy for an execve argv element.
|
||||
fn cstr(s: str) *u8 = {
|
||||
let b: []u8 = alloc([], (s.len: u64) + 1u64)!;
|
||||
b.len = s.len + 1;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) { b[i] = s[i]; i += 1; };
|
||||
b[s.len] = 0u8;
|
||||
return b.ptr;
|
||||
};
|
||||
|
||||
fn joinp(dir: str, name: str) str = {
|
||||
let n: i32 = dir.len + name.len;
|
||||
let b: []u8 = alloc([], n: u64)!;
|
||||
b.len = n;
|
||||
let i: i32 = 0;
|
||||
for (i < dir.len) { b[i] = dir[i]; i += 1; };
|
||||
let j: i32 = 0;
|
||||
for (j < name.len) { b[dir.len + j] = name[j]; j += 1; };
|
||||
return mkstr(b.ptr, n);
|
||||
};
|
||||
|
||||
fn findsub(hp: *u8, hn: i32, np: *u8, nn: i32) i32 = {
|
||||
if (nn == 0) { return 0; };
|
||||
let i: i32 = 0;
|
||||
for (i + nn <= hn) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < nn) {
|
||||
if (hp[i + j] != np[j]) { ok = false; break; };
|
||||
j += 1;
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
fn contains(hp: *u8, hn: i32, needle: str) bool = {
|
||||
return findsub(hp, hn, needle.ptr, needle.len) >= 0;
|
||||
};
|
||||
|
||||
fn matchat(hp: *u8, hn: i32, off: i32, lit: str) bool = {
|
||||
if (off + lit.len > hn) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < lit.len) {
|
||||
if (hp[off + i] != lit[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// readfile — slurp `path` into `buf`, returning bytes read or -1.
|
||||
fn readfile(path: str, buf: []u8) i64 = {
|
||||
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||
if (fd < 0) { return -1i64; };
|
||||
let total: u64 = 0u64;
|
||||
for (true) {
|
||||
let r: i64 = os.read(fd, buf.ptr + total, (buf.len: u64) - total);
|
||||
if (r <= 0i64) { break; };
|
||||
total += r: u64;
|
||||
if (total >= buf.len: u64) { break; };
|
||||
};
|
||||
os.close(fd);
|
||||
return total: i64;
|
||||
};
|
||||
|
||||
fn parsedir(cp: *u8, cn: i32, out: *dirv) void = {
|
||||
out.kind = dkind.NONE;
|
||||
out.code = 0;
|
||||
out.subp = nil: *u8;
|
||||
out.subn = 0;
|
||||
|
||||
let i: i32 = findsub(cp, cn, "//ww:".ptr, 5i32);
|
||||
if (i < 0) { return; };
|
||||
let d: i32 = i + 5; // first byte after "//ww:"
|
||||
|
||||
let le: i32 = d; // end of the directive line
|
||||
for (le < cn) {
|
||||
if (cp[le] == 10u8) { break; }; // '\n'
|
||||
le += 1;
|
||||
};
|
||||
|
||||
if (matchat(cp, cn, d, "run-exit")) {
|
||||
out.kind = dkind.RUNEXIT;
|
||||
let k: i32 = d + 8;
|
||||
for (k < le) { if (cp[k] != 32u8) { break; }; k += 1; }; // skip ' '
|
||||
let v: i32 = 0;
|
||||
for (k < le) {
|
||||
let c: u8 = cp[k];
|
||||
if (c < 48u8) { break; };
|
||||
if (c > 57u8) { break; };
|
||||
v = v * 10 + (c - 48u8): i32;
|
||||
k += 1;
|
||||
};
|
||||
out.code = v;
|
||||
return;
|
||||
};
|
||||
if (matchat(cp, cn, d, "run")) { out.kind = dkind.RUN; return; };
|
||||
if (matchat(cp, cn, d, "compile")) { out.kind = dkind.COMPILE; return; };
|
||||
if (matchat(cp, cn, d, "error")) {
|
||||
out.kind = dkind.ERROR;
|
||||
// substring between the two double-quote bytes (34)
|
||||
let q1: i32 = -1;
|
||||
let k: i32 = d;
|
||||
for (k < le) { if (cp[k] == 34u8) { q1 = k; break; }; k += 1; };
|
||||
// #20 non-vacuity: an absent or empty substring would collapse the
|
||||
// ERROR check to a bare rc!=0, which a crash satisfies — the exact
|
||||
// hole this harness closes. Reject the directive loudly instead.
|
||||
if (q1 < 0) { out.kind = dkind.NONE; return; };
|
||||
let q2: i32 = -1;
|
||||
k = q1 + 1;
|
||||
for (k < le) { if (cp[k] == 34u8) { q2 = k; break; }; k += 1; };
|
||||
if (q2 < 0) { out.kind = dkind.NONE; return; };
|
||||
if (q2 == q1 + 1) { out.kind = dkind.NONE; return; };
|
||||
out.subp = cp + (q1 + 1): u64;
|
||||
out.subn = q2 - (q1 + 1);
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
// runcap — fork+execve `drv` with `argv`, child stderr redirected into
|
||||
// errpath. Returns the child's real exit code (1 on signal, -1 on
|
||||
// fork/wait failure). Lifted from selfhost/cmd/ww/main.ww:154 procrun.
|
||||
fn runcap(drv: str, argv: []*u8, errpath: str) i32 = {
|
||||
let pid: i32 = os.fork();
|
||||
if (pid < 0) { return -1; };
|
||||
if (pid == 0) {
|
||||
let efd: i32 = os.open(errpath,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (efd >= 0) { os.dup2(efd, 2i32); };
|
||||
os.execve(drv, argv.ptr, nil: **u8);
|
||||
os.exit(127);
|
||||
};
|
||||
let status: i32 = 0;
|
||||
let r: i32 = os.wait4(pid, &status, 0i32, nil: *void);
|
||||
if (r < 0) { return -1; };
|
||||
if ((status & 127i32) != 0) { return 1; };
|
||||
return (status >> 8i32) & 255i32;
|
||||
};
|
||||
|
||||
// runcase — build (and maybe run) one case through `drv`; return whether
|
||||
// the directive held.
|
||||
fn runcase(drv: str, casepath: str, d: *dirv, errpath: str, tmpout: str) bool = {
|
||||
let argv: []*u8 = alloc([], 6u64)!;
|
||||
if (d.kind == dkind.COMPILE) {
|
||||
argv.len = 6;
|
||||
argv[0] = cstr(drv);
|
||||
argv[1] = cstr("build");
|
||||
argv[2] = cstr("-o");
|
||||
argv[3] = cstr(tmpout);
|
||||
argv[4] = cstr(casepath);
|
||||
argv[5] = nil: *u8;
|
||||
} else {
|
||||
// RUN / RUNEXIT / ERROR all go through `ww run`: on a reject the
|
||||
// compile fails before execution, so the diagnostic still lands on
|
||||
// stderr and nothing is left to clean up.
|
||||
argv.len = 4;
|
||||
argv[0] = cstr(drv);
|
||||
argv[1] = cstr("run");
|
||||
argv[2] = cstr(casepath);
|
||||
argv[3] = nil: *u8;
|
||||
};
|
||||
|
||||
let rc: i32 = runcap(drv, argv, errpath);
|
||||
|
||||
if (d.kind == dkind.ERROR) {
|
||||
if (rc == 0) { return false; }; // built+ran ok → not a reject
|
||||
let eb: []u8 = alloc([], 65536u64)!;
|
||||
eb.len = 65536;
|
||||
let en: i64 = readfile(errpath, eb);
|
||||
if (en < 0i64) { return false; };
|
||||
// #20: rc!=0 AND the diagnostic body present (a crash has no body).
|
||||
return contains(eb.ptr, en: i32, mkstr(d.subp, d.subn));
|
||||
};
|
||||
if (d.kind == dkind.RUNEXIT) { return rc == d.code; };
|
||||
if (d.kind == dkind.RUN) { return rc == 0; };
|
||||
if (d.kind == dkind.COMPILE) { return rc == 0; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// wwerror — the ERROR arm's wwstage leg: invoke w6c_ww directly on the same
|
||||
// case.ww (mirroring the retired C twins' `$BIN/w6c_ww -o <out> <src>` direct
|
||||
// invocation, not the `ww` driver — w6c_ww is the bare frontend compiler) and
|
||||
// assert it ALSO hard-rejects with the shared diagnostic body present. #20
|
||||
// non-vacuity: passes ONLY if w6c_ww failed AND the substring is on stderr.
|
||||
fn wwerror(wdrv: str, casepath: str, d: *dirv, errpath: str, tmpout: str) bool = {
|
||||
let argv: []*u8 = alloc([], 5u64)!;
|
||||
argv.len = 5;
|
||||
argv[0] = cstr(wdrv);
|
||||
argv[1] = cstr("-o");
|
||||
argv[2] = cstr(tmpout);
|
||||
argv[3] = cstr(casepath);
|
||||
argv[4] = nil: *u8;
|
||||
|
||||
let rc: i32 = runcap(wdrv, argv, errpath);
|
||||
if (rc == 0) { return false; }; // w6c_ww accepted → wwstage-reject dropped
|
||||
let eb: []u8 = alloc([], 65536u64)!;
|
||||
eb.len = 65536;
|
||||
let en: i64 = readfile(errpath, eb);
|
||||
if (en < 0i64) { return false; };
|
||||
return contains(eb.ptr, en: i32, mkstr(d.subp, d.subn));
|
||||
};
|
||||
|
||||
export fn main() int = {
|
||||
let a: []str = os.args();
|
||||
if (a.len < 2) {
|
||||
pr("runww: usage: runww <case.ww>...\n");
|
||||
return 2;
|
||||
};
|
||||
|
||||
let bin: str = "out/bin";
|
||||
match (os.getenv("BIN")) {
|
||||
case let v: str => { bin = v; };
|
||||
case void => { };
|
||||
};
|
||||
let cdrv: str = joinp(bin, "/ww");
|
||||
// ERROR arm goes dual-stage: w6c_ww (the bare wwstage frontend) is
|
||||
// resolved off the SAME $BIN the C reject twins used ($BIN/w6c_ww).
|
||||
let wdrv: str = joinp(bin, "/w6c_ww");
|
||||
|
||||
// Fixed scratch paths: the pilot drives cases sequentially in one
|
||||
// process, so reuse is safe; per-pid uniqueness for parallel invocation
|
||||
// is a later wiring fold (task #29).
|
||||
let errpath: str = "/tmp/runww.err";
|
||||
let tmpout: str = "/tmp/runww.bin";
|
||||
|
||||
let total: i32 = 0;
|
||||
let fail: i32 = 0;
|
||||
|
||||
let ci: i32 = 1;
|
||||
for (ci < a.len) {
|
||||
let casepath: str = a[ci];
|
||||
|
||||
let cbuf: []u8 = alloc([], 65536u64)!;
|
||||
cbuf.len = 65536;
|
||||
let cn: i64 = readfile(casepath, cbuf);
|
||||
if (cn < 0i64) {
|
||||
pr("FAIL read "); pr(casepath); pr("\n");
|
||||
fail += 1; total += 1; ci += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
let d: dirv;
|
||||
parsedir(cbuf.ptr, cn: i32, &d);
|
||||
if (d.kind == dkind.NONE) {
|
||||
pr("FAIL no-directive "); pr(casepath); pr("\n");
|
||||
fail += 1; total += 1; ci += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
total += 1;
|
||||
let pass: bool = runcase(cdrv, casepath, &d, errpath, tmpout);
|
||||
if (!pass) {
|
||||
pr("FAIL cstage "); pr(casepath); pr("\n");
|
||||
fail += 1;
|
||||
} else if (d.kind == dkind.ERROR) {
|
||||
// cstage rejected; now demand wwstage rejects identically.
|
||||
if (wwerror(wdrv, casepath, &d, errpath, tmpout)) {
|
||||
pr("PASS dual "); pr(casepath); pr("\n");
|
||||
} else {
|
||||
pr("FAIL wwstage "); pr(casepath); pr("\n");
|
||||
fail += 1;
|
||||
};
|
||||
} else {
|
||||
pr("PASS cstage "); pr(casepath); pr("\n");
|
||||
};
|
||||
|
||||
ci += 1;
|
||||
};
|
||||
|
||||
if (fail > 0) {
|
||||
pr("runww: FAIL\n");
|
||||
return 1;
|
||||
};
|
||||
pr("runww: all ok\n");
|
||||
return 0;
|
||||
};
|
||||
@@ -1,374 +0,0 @@
|
||||
/*
|
||||
* 630_let_global — top-level mutable `let` end-to-end through the
|
||||
* full Cstage pipeline (w6c → w6a → w6l). Each fixture is a tiny
|
||||
* .ww program that exercises one path: literal-init read, plain
|
||||
* assignment, compound assignment, and address-of.
|
||||
*
|
||||
* Exit code = the value the runtime feeds to `exit(2)`. main's
|
||||
* return value flows into DI via rt/start.s and ends up as the
|
||||
* shell's $?.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
run_exit(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
build_and_run(const char *bin, const char *prog)
|
||||
{
|
||||
/* `ww run` builds with the driver (w6c → w6a → w6l + libwwrt.a)
|
||||
* and executes the result. The exit code propagates back as the
|
||||
* shell's $? so we just compare against the fixture's want. */
|
||||
char src[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/wwt_lg_%d.ww", getpid());
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
wwtest_fputs(prog, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, src);
|
||||
int rc = run_exit(cmd);
|
||||
unlink(src);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct fixture {
|
||||
const char *label;
|
||||
const char *prog;
|
||||
int want;
|
||||
};
|
||||
|
||||
static const struct fixture fixtures[] = {
|
||||
{
|
||||
"read",
|
||||
"let counter: i32 = 42;\n"
|
||||
"fn main() i32 = { return counter; };\n",
|
||||
42,
|
||||
},
|
||||
{
|
||||
"assign",
|
||||
"let counter: i32 = 0;\n"
|
||||
"fn main() i32 = { counter = 99; return counter; };\n",
|
||||
99,
|
||||
},
|
||||
{
|
||||
"compound",
|
||||
"let counter: i32 = 1;\n"
|
||||
"fn inc() void = { counter += 1; };\n"
|
||||
"fn main() i32 = { inc(); inc(); inc(); return counter; };\n",
|
||||
4,
|
||||
},
|
||||
{
|
||||
"zero-init",
|
||||
/* No initialiser → zero-filled DATAW slot. */
|
||||
"let counter: i32;\n"
|
||||
"fn main() i32 = { counter = 7; return counter; };\n",
|
||||
7,
|
||||
},
|
||||
{
|
||||
"addr-of",
|
||||
"let counter: i32 = 11;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet p: *i32 = &counter;\n"
|
||||
"\t*p = 55;\n"
|
||||
"\treturn counter;\n"
|
||||
"};\n",
|
||||
55,
|
||||
},
|
||||
{
|
||||
"u64-read",
|
||||
"let val: u64 = 123u64;\n"
|
||||
"fn main() i32 = { return val: i32; };\n",
|
||||
123,
|
||||
},
|
||||
{
|
||||
"str-zeroinit",
|
||||
/* `let msg: str;` zero-inits the {ptr,len} slot. Assigning
|
||||
* a strlit at runtime updates both halves; .len then reads
|
||||
* the stored length. */
|
||||
"let msg: str;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tmsg = \"hello\";\n"
|
||||
"\treturn msg.len: i32;\n"
|
||||
"};\n",
|
||||
5,
|
||||
},
|
||||
{
|
||||
"str-reassign",
|
||||
"let msg: str;\n"
|
||||
"fn set(s: str) void = { msg = s; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tset(\"abcdefg\");\n"
|
||||
"\treturn msg.len: i32;\n"
|
||||
"};\n",
|
||||
7,
|
||||
},
|
||||
{
|
||||
"str-literal-init",
|
||||
/* `let s: str = "literal";` — the ptr half is patched at
|
||||
* link time by an R_X86_64_64 reloc against the strlit's
|
||||
* data label; .len comes baked into the DATAW payload. */
|
||||
"let g: str = \"hello world\";\n"
|
||||
"fn main() i32 = { return g.len: i32; };\n",
|
||||
11,
|
||||
},
|
||||
{
|
||||
"str-literal-via-fn",
|
||||
/* Confirm the patched ptr really points at the right bytes
|
||||
* by reading the first byte of g.ptr through a pointer
|
||||
* cast. 'h' == 104. */
|
||||
"let g: str = \"hello\";\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet p: *u8 = g.ptr;\n"
|
||||
"\treturn p[0]: i32;\n"
|
||||
"};\n",
|
||||
104,
|
||||
},
|
||||
{
|
||||
"slice-zeroinit",
|
||||
/* Slice globals start as {nil, 0, 0}. .len and .cap both
|
||||
* read zero; .ptr is nil but we don't dereference it. */
|
||||
"let buf: []u8;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\treturn (buf.len + buf.cap): i32;\n"
|
||||
"};\n",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"slice-cap-via-raw-pointer",
|
||||
/* Drive the slice header through a raw u64 pointer cast.
|
||||
* Pre-fix for slice reassignment this was the only way to
|
||||
* fill the header from ww source; kept because the path
|
||||
* still has to work. */
|
||||
"let buf: []u8;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet p: *u64 = (&buf): *u64;\n"
|
||||
"\tp[2] = 99u64;\n"
|
||||
"\treturn buf.cap: i32;\n"
|
||||
"};\n",
|
||||
99,
|
||||
},
|
||||
{
|
||||
"slice-global-reassign-from-global",
|
||||
/* `dst = src` for slice globals — both halves of the
|
||||
* triple (ptr, len, cap) must propagate. */
|
||||
"let dst: []u8;\n"
|
||||
"let src: []u8;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet p: *u64 = (&src): *u64;\n"
|
||||
"\tp[0] = 0x1000u64;\n"
|
||||
"\tp[1] = 7u64;\n"
|
||||
"\tp[2] = 99u64;\n"
|
||||
"\tdst = src;\n"
|
||||
"\treturn dst.len: i32 + dst.cap: i32;\n"
|
||||
"};\n",
|
||||
106,
|
||||
},
|
||||
{
|
||||
"slice-local-reassign-from-slice-expr",
|
||||
/* `s = arr[lo:hi]` reassigns the slice local; cgexpr now
|
||||
* emits N_SLICE as a triple. Previously the let-init
|
||||
* specific N_SLICE path worked but reassignment dropped
|
||||
* len/cap. */
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet arr: [16]u8;\n"
|
||||
"\tlet i: i32 = 0;\n"
|
||||
"\tfor (i < 16) { arr[i] = i: u8; i += 1; };\n"
|
||||
"\tlet s: []u8 = arr[3:10];\n"
|
||||
"\ts = arr[1:5];\n"
|
||||
"\treturn s.len: i32;\n"
|
||||
"};\n",
|
||||
4,
|
||||
},
|
||||
{
|
||||
"slice-local-from-fn-return",
|
||||
/* fn returning []u8 leaves (AX,BX,CX) at return; the let-
|
||||
* init slice fallback stores the triple. */
|
||||
"fn mkslice() []u8 = {\n"
|
||||
"\tlet arr: [16]u8;\n"
|
||||
"\treturn arr[3:9];\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet s: []u8 = mkslice();\n"
|
||||
"\treturn s.len: i32;\n"
|
||||
"};\n",
|
||||
6,
|
||||
},
|
||||
{
|
||||
"struct-field-readwrite",
|
||||
/* Top-level struct global. Field writes hit the global
|
||||
* via LEAQ name(SB) + offset; reads pull each field
|
||||
* with the right width. */
|
||||
"type point = struct { x: i32, y: i32 };\n"
|
||||
"let p: point;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tp.x = 7;\n"
|
||||
"\tp.y = 35;\n"
|
||||
"\treturn p.x + p.y;\n"
|
||||
"};\n",
|
||||
42,
|
||||
},
|
||||
{
|
||||
"struct-field-compound",
|
||||
/* Compound += on a struct global field — load via
|
||||
* LEAQ+disp, push, eval rhs, combine, store. */
|
||||
"type counter = struct { n: i64 };\n"
|
||||
"let c: counter;\n"
|
||||
"fn bump(d: i64) void = { c.n += d; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tbump(10i64);\n"
|
||||
"\tbump(15i64);\n"
|
||||
"\tbump(17i64);\n"
|
||||
"\treturn c.n: i32;\n"
|
||||
"};\n",
|
||||
42,
|
||||
},
|
||||
{
|
||||
"f64-literal-init",
|
||||
/* `let pi: f64 = 3.14;` — DATAW bakes the 8 LE bytes of
|
||||
* the double directly. Read goes LEAQ name(SB),CX +
|
||||
* MOVSD (CX),X0; cast truncates to i32. */
|
||||
"let pi: f64 = 7.5;\n"
|
||||
"fn main() i32 = { return pi: i32; };\n",
|
||||
7,
|
||||
},
|
||||
{
|
||||
"f32-literal-init",
|
||||
/* f32 globals are 4-byte slots; literal init bakes the
|
||||
* single-precision bit pattern. */
|
||||
"let half: f32 = 4.25;\n"
|
||||
"fn main() i32 = { return half: i32; };\n",
|
||||
4,
|
||||
},
|
||||
{
|
||||
"f64-zero-init",
|
||||
"let z: f64;\n"
|
||||
"fn main() i32 = { return z: i32; };\n",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"f64-reassign",
|
||||
/* Reassigning an f64 global goes LEAQ name(SB),CX +
|
||||
* MOVSD X0,(CX); read-back through the same shape. */
|
||||
"let pi: f64 = 1.0;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tpi = 7.5;\n"
|
||||
"\treturn pi: i32;\n"
|
||||
"};\n",
|
||||
7,
|
||||
},
|
||||
{
|
||||
"f64-arith",
|
||||
"let a: f64 = 7.5;\n"
|
||||
"let b: f64 = 4.25;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet s: f64 = a + b;\n"
|
||||
"\treturn s: i32;\n"
|
||||
"};\n",
|
||||
11,
|
||||
},
|
||||
{
|
||||
"struct-tagged-field-i64",
|
||||
/* Tagged-union field on a struct global. Write goes
|
||||
* LEAQ name(SB),CX + MOVQ to slot+foff+8 (value) and
|
||||
* slot+foff+0 (tag). Read pulls AX=tag, DX=val0,
|
||||
* CX=val1 from those same slots and match dispatches. */
|
||||
"type tok = (i64 | str | void);\n"
|
||||
"type ent = struct { id: i32, t: tok };\n"
|
||||
"let e: ent;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\te.t = 42i64: tok;\n"
|
||||
"\tlet v: tok = e.t;\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let n: i64 => return n: i32;\n"
|
||||
"\tcase let s: str => return 1;\n"
|
||||
"\tcase void => return 2;\n"
|
||||
"\t};\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
42,
|
||||
},
|
||||
{
|
||||
"struct-tagged-field-str",
|
||||
"type tok = (i64 | str | void);\n"
|
||||
"type ent = struct { id: i32, t: tok };\n"
|
||||
"let e: ent;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\te.t = \"hello\";\n"
|
||||
"\tlet v: tok = e.t;\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let n: i64 => return 1;\n"
|
||||
"\tcase let s: str => return s.len: i32;\n"
|
||||
"\tcase void => return 2;\n"
|
||||
"\t};\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
5,
|
||||
},
|
||||
{
|
||||
"struct-tagged-field-void",
|
||||
"type tok = (i64 | str | void);\n"
|
||||
"type ent = struct { id: i32, t: tok };\n"
|
||||
"let e: ent;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\te.t = void;\n"
|
||||
"\tlet v: tok = e.t;\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let n: i64 => return 1;\n"
|
||||
"\tcase let s: str => return 2;\n"
|
||||
"\tcase void => return 7;\n"
|
||||
"\t};\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
7,
|
||||
},
|
||||
{
|
||||
"struct-narrow-field",
|
||||
/* u8 field on a struct global. Read uses MOVZBQ, store
|
||||
* uses MOVB. Pre-fix this would have stomped neighbouring
|
||||
* bytes by emitting MOVQ. */
|
||||
"type packet = struct { hdr: u8, body: u32 };\n"
|
||||
"let pkt: packet;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tpkt.hdr = 42u8;\n"
|
||||
"\tpkt.body = 999u32;\n"
|
||||
"\treturn pkt.hdr: i32;\n"
|
||||
"};\n",
|
||||
42,
|
||||
},
|
||||
{ NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
|
||||
int fail = 0, ran = 0;
|
||||
for (int i = 0; fixtures[i].label; i++, ran++) {
|
||||
int got = build_and_run(bin, fixtures[i].prog);
|
||||
if (got != fixtures[i].want) {
|
||||
fprintf(stderr, "let_global[%s]: exit=%d, want %d\n",
|
||||
fixtures[i].label, got, fixtures[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "let_global: %d/%d fixtures failed\n", fail, ran);
|
||||
return 1;
|
||||
}
|
||||
printf("let_global: %d/%d ok\n", ran, ran);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* 640_int_cast_signed — signed narrow `(i64): i8|i16|i32` must
|
||||
* sign-extend the narrowed value, not silently truncate. Each
|
||||
* fixture casts a high-bit-set source to a narrow signed type,
|
||||
* widens back to i64, and returns 42 on the expected match. A
|
||||
* cast that fails to sign-extend leaks the low bits and the
|
||||
* comparison falls through to the `0` arm.
|
||||
*
|
||||
* Exercises both stages via the user-facing `ww run` (cstage)
|
||||
* and `ww_ww run` (wwstage) when present; the loop runs each
|
||||
* driver in turn so a regression on either side is caught
|
||||
* here without spawning extra targets.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* i8 ← 0xFFFF_FF80: low byte is 0x80, expect -128 after
|
||||
* narrow + widen-back round trip. */
|
||||
{ "i8_high_bits_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0xFFFFFF80i64;\n"
|
||||
" let y: i8 = x: i8;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -128i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i8 ← 0x80: bare low byte still encodes -128 once narrowed. */
|
||||
{ "i8_low_byte_negative",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0x80i64;\n"
|
||||
" let y: i8 = x: i8;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -128i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i8 ← 0x7F: positive, no sign-extend. */
|
||||
{ "i8_positive_max",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0x7Fi64;\n"
|
||||
" let y: i8 = x: i8;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == 127i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i16 ← 0xFFFF_8000: low 16 bits encode -32768. */
|
||||
{ "i16_high_bits_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0xFFFF8000i64;\n"
|
||||
" let y: i16 = x: i16;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -32768i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i16 ← 0x8000: low half-word's sign bit alone. */
|
||||
{ "i16_low_word_negative",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0x8000i64;\n"
|
||||
" let y: i16 = x: i16;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -32768i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i32 ← 0xFFFFFFFF_80000000: low 32 bits encode INT32_MIN. */
|
||||
{ "i32_high_bits_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = -2147483648i64;\n"
|
||||
" let y: i32 = x: i32;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -2147483648i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i32 ← 0x0000_0000_8000_0000: low dword's sign bit alone. */
|
||||
{ "i32_low_dword_negative",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0x80000000i64;\n"
|
||||
" let y: i32 = x: i32;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -2147483648i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Compare against the narrow target's own range, not the
|
||||
* widened slot — i32 - 1 must roll over to INT32_MAX after
|
||||
* MOVSXD sign-extends the wrap-around value. */
|
||||
{ "i32_wrap_negative_to_positive",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0x80000000i64;\n"
|
||||
" let y: i32 = (x - 1i64): i32;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == 2147483647i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* `!T` error-flag wrapper must not block the narrow clamp.
|
||||
* Pins the wwstage N_TBANG peel in cgcast: without it, the
|
||||
* unsigned u8 mask was skipped and 0x1FF leaked through. */
|
||||
{ "u8bang_unsigned_narrow",
|
||||
"type u8x = !u8;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let y: u8x = 0x1FFu64: u8x;\n"
|
||||
" let z: u64 = y: u64;\n"
|
||||
" if (z == 0xFFu64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Signed `!T` companion — sign-extend must fire through the
|
||||
* bang wrapper so `(0xFF80): !i8 → i64` reads back -128. */
|
||||
{ "i8bang_signed_narrow",
|
||||
"type i8x = !i8;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let y: i8x = 0xFF80i64: i8x;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -128i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwic_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wwic_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wwic_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "int_cast_signed: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"int_cast_signed[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"int_cast_signed: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("int_cast_signed: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
/*
|
||||
* 650_dot_chain — chained dotted access through value-struct fields
|
||||
* must compile to a single load/store at (base + sum-of-offsets),
|
||||
* not silently lower to an undefined global symbol or shuffle stale
|
||||
* registers. Drew filed this from worker-bufio when the Hare-shaped
|
||||
* `scanner` embed in lib/bufio tripped the cgen.
|
||||
*
|
||||
* Each fixture exercises a different chain shape. The repro from
|
||||
* drew's bug report is row[0]; the rest pin: 3-deep chains, slice
|
||||
* pseudo-field tails (`s.buf.len`), global-rooted chains, and
|
||||
* mixed leaf widths (u8 / i32 / u32). `&o.i.a` address-of and i8
|
||||
* sign-extend leaves are deferred (tasks #9 / #10).
|
||||
*
|
||||
* Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage)
|
||||
* when present, so a regression on either side is caught here.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Drew's repro: 2-deep value-struct read AND write. Returns
|
||||
* o.i.a (=10) + o.x (=5) = 15. */
|
||||
{ "drew_2deep_read_write",
|
||||
"type inner = struct { a: i32, b: i32, c: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.a = 10; o.x = 5;\n"
|
||||
" return o.i.a + o.x;\n"
|
||||
"};\n",
|
||||
15 },
|
||||
/* 3-deep value-struct chain. Proves the spine walker is loop-
|
||||
* shaped, not hardcoded to depth 2. */
|
||||
{ "value_struct_3deep",
|
||||
"type a3 = struct { a: i32 };\n"
|
||||
"type a2 = struct { a: a3 };\n"
|
||||
"type a1 = struct { a: a2 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: a1;\n"
|
||||
" v.a.a.a = 7;\n"
|
||||
" return v.a.a.a;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* Slice pseudo-field tail through a value-struct field: write
|
||||
* .ptr/.len/.cap on s.buf where buf: []u8 is a struct field.
|
||||
* Read back .len through the same chain. */
|
||||
{ "slice_field_pseudo",
|
||||
"type bag = struct { buf: []u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8;\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < 8) { arr[i] = i: u8; i = i + 1; };\n"
|
||||
" let b: bag;\n"
|
||||
" b.buf.ptr = &arr[0];\n"
|
||||
" b.buf.len = 5;\n"
|
||||
" b.buf.cap = 8;\n"
|
||||
" b.x = 0;\n"
|
||||
" return b.buf.len;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* u8 leaf through a chained struct: round-trips a single byte
|
||||
* unchanged (no narrow cast in play — direct MOVB write +
|
||||
* MOVZBQ read). Pins the leaf-width dispatch in the walker. */
|
||||
{ "u8_leaf_through_chain",
|
||||
"type holder = struct { val: u8, pad: u8 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box;\n"
|
||||
" b.h.val = 99u8;\n"
|
||||
" b.h.pad = 0u8;\n"
|
||||
" b.tag = 0;\n"
|
||||
" return b.h.val: i32;\n"
|
||||
"};\n",
|
||||
99 },
|
||||
/* i32 leaf through a chained struct, value > 255 to confirm the
|
||||
* MOVL store width (not silently narrowed to MOVB). Returns 42
|
||||
* iff the full 4-byte value round-trips. */
|
||||
{ "i32_leaf_through_chain",
|
||||
"type holder = struct { val: i32, pad: i32 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box;\n"
|
||||
" b.h.val = 0x12345;\n"
|
||||
" b.h.pad = 0;\n"
|
||||
" b.tag = 0;\n"
|
||||
" if (b.h.val == 0x12345) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* u32 leaf through a chained struct. Distinct write + read at
|
||||
* the chained leaf, value chosen so the MOVL store + load
|
||||
* round-trip preserves all four bytes. */
|
||||
{ "u32_leaf_through_chain",
|
||||
"type holder = struct { val: u32, pad: u32 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box;\n"
|
||||
" b.h.val = 4321u32;\n"
|
||||
" b.h.pad = 0u32;\n"
|
||||
" b.tag = 0;\n"
|
||||
" if (b.h.val == 4321u32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Global-rooted chain: write+read through a top-level `let g`.
|
||||
* Pins the LEAQ name(SB), CX → MOVQ disp(CX) path on both
|
||||
* stages (the local-rooted rows above pin the BP-relative
|
||||
* path). 11 + 3 = 14. */
|
||||
{ "global_root_chain",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"let g: outer;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" g.i.a = 11; g.x = 3;\n"
|
||||
" return g.i.a + g.x;\n"
|
||||
"};\n",
|
||||
14 },
|
||||
/* `*T` parameter, single-dot read: `fn f(o: *outer) i32 = o.f`.
|
||||
* Already worked via the existing pointer-to-struct-field
|
||||
* branch; pinned here so future refactors don't regress it. */
|
||||
{ "ptr_root_single_read",
|
||||
"type outer = struct { f: i32, g: i32 };\n"
|
||||
"fn get(o: *outer) i32 = { return o.f; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: outer; x.f = 42; x.g = 0;\n"
|
||||
" return get(&x);\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* `*T` parameter, single-dot write: `fn f(o: *outer) void = …`.
|
||||
* Already worked via the via_ptr branch in N_ASSIGN; pinned
|
||||
* for the same reason. */
|
||||
{ "ptr_root_single_write",
|
||||
"type outer = struct { f: i32, g: i32 };\n"
|
||||
"fn set(o: *outer) void = { o.f = 42; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: outer; x.f = 0; x.g = 0;\n"
|
||||
" set(&x);\n"
|
||||
" return x.f;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Drew's report shape — `*T` parameter, 2-deep read.
|
||||
* Used to lower to `MOVQ a(SB), AX` (link-time undefined ref).
|
||||
* Now the spine walker dereferences the root pointer. */
|
||||
{ "ptr_root_2deep_read",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn get(o: *outer) i32 = { return o.i.a; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: outer; v.i.a = 7; v.i.b = 0; v.x = 0;\n"
|
||||
" return get(&v);\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* `*T` parameter, 2-deep write — bufio.init shape.
|
||||
* Previously silently dropped; now MOVQ off(BP), CX +
|
||||
* store at total_off(CX). */
|
||||
{ "ptr_root_2deep_write",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn set(o: *outer) void = { o.i.a = 5; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: outer; v.i.a = 0; v.i.b = 0; v.x = 0;\n"
|
||||
" set(&v);\n"
|
||||
" return v.i.a;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* 3-deep `*T`-rooted: proves the spine walker loops past the
|
||||
* pointer-root hop and isn't hardcoded at depth 2. */
|
||||
{ "ptr_root_3deep",
|
||||
"type a3 = struct { x: i32 };\n"
|
||||
"type a2 = struct { a: a3 };\n"
|
||||
"type a1 = struct { a: a2 };\n"
|
||||
"fn put(p: *a1) void = { p.a.a.x = 9; };\n"
|
||||
"fn get(p: *a1) i32 = { return p.a.a.x; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: a1; v.a.a.x = 0;\n"
|
||||
" put(&v);\n"
|
||||
" return get(&v);\n"
|
||||
"};\n",
|
||||
9 },
|
||||
/* `*T` *local* (not just param) — same emit path through
|
||||
* localfind, but exercised separately for symmetry. */
|
||||
{ "ptr_root_local",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: outer; v.i.a = 0; v.i.b = 0; v.x = 0;\n"
|
||||
" let p: *outer = &v;\n"
|
||||
" p.i.a = 13;\n"
|
||||
" return p.i.a;\n"
|
||||
"};\n",
|
||||
13 },
|
||||
/* str leaf field through a `*T`-rooted chain — exercises the
|
||||
* (AX=ptr, BX=len) convention through the CX-base load. */
|
||||
{ "ptr_root_str_leaf",
|
||||
"type holder = struct { s: str, pad: i32 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.s = \"abc\"; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box;\n"
|
||||
" let empty: str; b.h.s = empty; b.h.pad = 0; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" if (b.h.s.len == 3) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* slice pseudo-field on a `*T`-rooted chain. .len of an empty
|
||||
* slice round-trips through the CX-base load. */
|
||||
{ "ptr_root_slice_pseudo",
|
||||
"type bag = struct { buf: []u8, x: i32 };\n"
|
||||
"fn setlen(b: *bag, n: i32) void = { b.buf.len = n; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: bag;\n"
|
||||
" let nope: []u8; b.buf = nope; b.x = 0;\n"
|
||||
" setlen(&b, 5);\n"
|
||||
" return b.buf.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* f64 leaf field through a `*T`-rooted chain — exercises the
|
||||
* X0 spill / MOVSD store path off the CX base. */
|
||||
{ "ptr_root_f64_leaf",
|
||||
"type holder = struct { v: f64, pad: i32 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = 0.5f64; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0.0f64; b.h.pad = 0; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" if (b.h.v == 0.5f64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Sub-word leaf widths through a `*T`-rooted chain — pins
|
||||
* fldloadop / fldstoreop on the CX-base path. u8/i8/u16/i16/i32
|
||||
* all stored then read back through the inner pointer. */
|
||||
{ "ptr_root_subword_u8",
|
||||
"type holder = struct { v: u8, pad: u8 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = 99u8; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0u8; b.h.pad = 0u8; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" return b.h.v: i32;\n"
|
||||
"};\n",
|
||||
99 },
|
||||
{ "ptr_root_subword_i32",
|
||||
"type holder = struct { v: i32, pad: i32 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = 0x12345; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0; b.h.pad = 0; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" if (b.h.v == 0x12345) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "ptr_root_subword_i16",
|
||||
"type holder = struct { v: i16, pad: i16 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = 4321i16; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0i16; b.h.pad = 0i16; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" if (b.h.v == 4321i16) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Negative signed leaf widths through a `*T`-rooted chain.
|
||||
* Stored value's sign bit must round-trip — pins localloadop /
|
||||
* fldloadop sign-extension on the CX-base path so `*T`-rooted
|
||||
* chains don't silently widen to a positive bit pattern. i8, i16
|
||||
* and i32 each store −7 and check `< 0` after the chained read. */
|
||||
{ "ptr_root_subword_i8_neg",
|
||||
"type holder = struct { v: i8, pad: i8 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = -7i8; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0i8; b.h.pad = 0i8; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" let r: i32 = b.h.v: i32;\n"
|
||||
" if (r == -7) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "ptr_root_subword_i16_neg",
|
||||
"type holder = struct { v: i16, pad: i16 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = -7i16; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0i16; b.h.pad = 0i16; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" let r: i32 = b.h.v: i32;\n"
|
||||
" if (r == -7) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "ptr_root_subword_i32_neg",
|
||||
"type holder = struct { v: i32, pad: i32 };\n"
|
||||
"type box = struct { h: holder, tag: i32 };\n"
|
||||
"fn put(b: *box) void = { b.h.v = -7; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.h.v = 0; b.h.pad = 0; b.tag = 0;\n"
|
||||
" put(&b);\n"
|
||||
" if (b.h.v < 0) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Round-trip across the aliasing seam: caller writes via the
|
||||
* local value, callee reads via the `*T`-rooted chain — and
|
||||
* vice versa. Pins that both branches index the SAME slot
|
||||
* (same field offsets resolved off the right base). */
|
||||
{ "ptr_root_roundtrip_local_write_ptr_read",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn get(o: *outer) i32 = { return o.i.a; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.a = 0; o.i.b = 0; o.x = 0;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" o.i.a = 42;\n"
|
||||
" return get(p);\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "ptr_root_roundtrip_ptr_write_local_read",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.a = 0; o.i.b = 0; o.x = 0;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" p.i.a = 42;\n"
|
||||
" return o.i.a;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wdc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wdc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wdc_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "dot_chain: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"dot_chain[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"dot_chain: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dot_chain: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
/*
|
||||
* 660_field_signed — sub-word signed struct/tuple/index field LOAD
|
||||
* must sign-extend (MOVSBQ / MOVSWQ / MOVSXD), not zero-extend.
|
||||
* Companion to 640 (N_CAST narrow) and 650 (chained N_DOT spine);
|
||||
* pins task #10's field-load fix and task #5's TY_ENUM recursion
|
||||
* through the predicate. Each fixture round-trips a high-bit-set
|
||||
* value through a field, then widens back to i64 and compares.
|
||||
*
|
||||
* Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage)
|
||||
* when present, so a regression on either side is caught here.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* i8 field LOAD: write -1 (0xFF), widen back; must read -1, not 255. */
|
||||
{ "i8_field_load",
|
||||
"type box = struct { v: i8, pad: i8 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = -1i64: i8; b.pad = 0i64: i8;\n"
|
||||
" let z: i64 = b.v: i64;\n"
|
||||
" if (z == -1i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i16 field LOAD: write -32768 (0x8000), widen back; must read -32768. */
|
||||
{ "i16_field_load",
|
||||
"type box = struct { v: i16, pad: i16 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = -32768i64: i16; b.pad = 0i64: i16;\n"
|
||||
" let z: i64 = b.v: i64;\n"
|
||||
" if (z == -32768i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* i32 field LOAD: pins MOVSXD on the field-read path. */
|
||||
{ "i32_field_load",
|
||||
"type box = struct { v: i32, pad: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = -2147483648i64: i32; b.pad = 0i32;\n"
|
||||
" let z: i64 = b.v: i64;\n"
|
||||
" if (z == -2147483648i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* u8 field LOAD with high bit: must zero-extend (regression check
|
||||
* — the symmetric helper must NOT sign-extend u8). */
|
||||
{ "u8_field_load",
|
||||
"type box = struct { v: u8, pad: u8 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = 0xFFu64: u8; b.pad = 0u8;\n"
|
||||
" let z: u64 = b.v: u64;\n"
|
||||
" if (z == 0xFFu64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* u16 / u32 field LOAD: zero-extension on the field-read path. */
|
||||
{ "u16_field_load",
|
||||
"type box = struct { v: u16, pad: u16 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = 0xFFFFu64: u16; b.pad = 0u16;\n"
|
||||
" let z: u64 = b.v: u64;\n"
|
||||
" if (z == 0xFFFFu64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "u32_field_load",
|
||||
"type box = struct { v: u32, pad: u32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = 0xFFFFFFFFu64: u32; b.pad = 0u32;\n"
|
||||
" let z: u64 = b.v: u64;\n"
|
||||
" if (z == 0xFFFFFFFFu64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* bool field LOAD: zero-extends (0 or 1). The new helper must not
|
||||
* sign-extend bool just because it's not in typenameisunsigned. */
|
||||
{ "bool_field_load",
|
||||
"type box = struct { v: bool, pad: bool };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = true; b.pad = false;\n"
|
||||
" if (b.v) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Chained dotted load over an i8 leaf: spine walker must pick
|
||||
* MOVSBQ at the terminal. Complements 650's i32/u32 leaf rows. */
|
||||
{ "chained_i8_leaf_load",
|
||||
"type inner = struct { v: i8, pad: i8 };\n"
|
||||
"type outer = struct { i: inner, tag: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.v = -1i64: i8; o.i.pad = 0i8; o.tag = 0;\n"
|
||||
" let z: i64 = o.i.v: i64;\n"
|
||||
" if (z == -1i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Chained i16 leaf — pins MOVSWQ through the spine walker. */
|
||||
{ "chained_i16_leaf_load",
|
||||
"type inner = struct { v: i16, pad: i16 };\n"
|
||||
"type outer = struct { i: inner, tag: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.v = -32768i64: i16; o.i.pad = 0i16; o.tag = 0;\n"
|
||||
" let z: i64 = o.i.v: i64;\n"
|
||||
" if (z == -32768i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Enum-aliased signed sub-word: `type myflag = i8` field. The
|
||||
* principled predicate (task #5: TY_ENUM recursion in
|
||||
* type_isunsigned, alias recursion in fieldissignedc) is what
|
||||
* makes this fire MOVSBQ instead of MOVZBQ. */
|
||||
{ "enum_signed_i8_alias_field_load",
|
||||
"type myflag = i8;\n"
|
||||
"type box = struct { v: myflag, pad: i8 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = -1i64: myflag; b.pad = 0i8;\n"
|
||||
" let z: i64 = b.v: i64;\n"
|
||||
" if (z == -1i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* N_CAST narrow on enum-aliased source. Without TY_ENUM recursion
|
||||
* in type_isunsigned, the symmetric narrow gate misses this case
|
||||
* and the cast is a silent no-op. */
|
||||
{ "enum_alias_cast_narrow",
|
||||
"type myflag = i8;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0xFF80i64;\n"
|
||||
" let y: myflag = x: myflag;\n"
|
||||
" let z: i64 = y: i64;\n"
|
||||
" if (z == -128i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Enum-aliased unsigned narrow: the symmetric helper must
|
||||
* zero-extend an enum aliased to u8. */
|
||||
{ "enum_unsigned_u8_alias_field_load",
|
||||
"type byteflag = u8;\n"
|
||||
"type box = struct { v: byteflag, pad: u8 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: box; b.v = 0xFFu64: byteflag; b.pad = 0u8;\n"
|
||||
" let z: u64 = b.v: u64;\n"
|
||||
" if (z == 0xFFu64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* `*p OP= v` with *p:u32 and high bit set — pins the hidden
|
||||
* bug at cgen.c N_ASSIGN deref-compound (was hardcoded MOVSXD
|
||||
* for sz=4, sign-extending u32). Old path: load 0x80000001 →
|
||||
* MOVSXD → 0xFFFFFFFF80000001, SHRQ 1 → 0x7FFFFFFFC0000000,
|
||||
* MOVL store → 0xC0000000. New path: MOVL → 0x80000001, SHRQ 1
|
||||
* → 0x40000000 (the correct u32 logical right-shift result). */
|
||||
{ "deref_compound_u32_high_bit",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: u32 = 0x80000001u32;\n"
|
||||
" let p: *u32 = &x;\n"
|
||||
" *p >>= 1u32;\n"
|
||||
" if (x == 0x40000000u32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 pin (widen-deref). Natural shape `fn f(p: *i32)`
|
||||
* with `*p = -16i32` lowers to MOVL (4B store) into the
|
||||
* caller's 8B slot. The caller's slot was zero-init via MOVQ,
|
||||
* so the upper 4B stay zero; a later i64-widening read emits
|
||||
* MOVQ (8B raw) and returns 0x00000000_FFFFFFF0 = 4294967280
|
||||
* instead of -16. Surfaced inside dotchainresolve when
|
||||
* worker-retire-dotchain tried i32 out-params; the helper now
|
||||
* widens all numeric out-params to *i64 as a workaround until
|
||||
* #19 lands. This row pins the WORKAROUND: out-param typed
|
||||
* *i64, caller reads i64 → MOVQ store/load pair agree, -16
|
||||
* round-trips intact. */
|
||||
{ "i64_out_param_neg_widen_deref_workaround",
|
||||
"fn setneg(p: *i64) void = { *p = -16i64; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i64 = 0i64;\n"
|
||||
" setneg(&x);\n"
|
||||
" if (x == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 fix. Natural `*p: *i32` shape — `*p = -16i32` lowers
|
||||
* to MOVL (correct 4B store to a 4B pointee), and the caller's
|
||||
* later i64-widening read must MOVSXD the slot so the sign bit
|
||||
* propagates. Without the fix, the MOVQ read of the slot returns
|
||||
* 0x00000000_FFFFFFF0 (4294967280) and the equality fails. */
|
||||
{ "i32_out_param_neg_widen_deref",
|
||||
"fn setneg(p: *i32) void = { *p = -16i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = 0i32;\n"
|
||||
" setneg(&x);\n"
|
||||
" let z: i64 = x: i64;\n"
|
||||
" if (z == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 fix, i16 sibling — `*p: *i16 = -16i16` writes 2B,
|
||||
* caller widens to i64. Without MOVSWQ the read sees 0xFFF0
|
||||
* (65520). */
|
||||
{ "i16_out_param_neg_widen_deref",
|
||||
"fn setneg(p: *i16) void = { *p = -16i64: i16; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i16 = 0i16;\n"
|
||||
" setneg(&x);\n"
|
||||
" let z: i64 = x: i64;\n"
|
||||
" if (z == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 fix, i8 sibling — `*p: *i8 = -16i8` writes 1B, caller
|
||||
* widens to i64. Without MOVSBQ the read sees 0xF0 (240). */
|
||||
{ "i8_out_param_neg_widen_deref",
|
||||
"fn setneg(p: *i8) void = { *p = -16i64: i8; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i8 = 0i8;\n"
|
||||
" setneg(&x);\n"
|
||||
" let z: i64 = x: i64;\n"
|
||||
" if (z == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 fix, top-level let target. Same shape but the local
|
||||
* `x` is replaced by a top-level let so the read goes through
|
||||
* the RIP-relative path. Without the fix the read is MOVQ from
|
||||
* &x(SB) and sees zero-extended garbage. */
|
||||
{ "i32_global_neg_widen_deref",
|
||||
"let g: i32 = 0i32;\n"
|
||||
"fn setneg(p: *i32) void = { *p = -16i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" setneg(&g);\n"
|
||||
" let z: i64 = g: i64;\n"
|
||||
" if (z == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 fix, compound RMW on a narrow local that was last
|
||||
* written via a deref-store. Pins the cgassign restructure that
|
||||
* routes the load half of `x OP= v` through localloadop (so the
|
||||
* upper bytes feeding the combine come from a sign-extending
|
||||
* load, not raw 8B off the slot) and gates the direct-mem
|
||||
* ADDQ/SUBQ shortcut on `load_op == MOVQ`. Reads x as i32 to
|
||||
* exercise the in-band path without an explicit i64 widen. */
|
||||
{ "i32_compound_rmw_after_deref_store",
|
||||
"fn setv(p: *i32, v: i32) void = { *p = v; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = 0i32;\n"
|
||||
" setv(&x, 0x7FFFFFFEi32);\n"
|
||||
" x += 1i32;\n"
|
||||
" if (x == 0x7FFFFFFFi32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Task #19 fix, aliased narrow read. `type myinv = !i32` wraps
|
||||
* i32 with the err flag — its tnode is N_TBANG(N_TNAME("i32")).
|
||||
* Pins the TBANG/TENUM/TNAME → aliaslookup loop inside the
|
||||
* wwstage `localloadop`: without it `fieldsize` falls back to 8
|
||||
* on the bare alias name and the read picks MOVQ, so the deref-
|
||||
* stored -16 round-trips as 4294967280 instead of -16. cstage
|
||||
* resolves the alias through Type.size and type_isunsigned. */
|
||||
{ "alias_bang_i32_widen_deref",
|
||||
"type myinv = !i32;\n"
|
||||
"fn setneg(p: *myinv) void = { *p = -16i64: myinv; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: myinv = 0i64: myinv;\n"
|
||||
" setneg(&x);\n"
|
||||
" let z: i64 = x: i64;\n"
|
||||
" if (z == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfs_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wwfs_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wwfs_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "field_signed: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"field_signed[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"field_signed: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("field_signed: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
/*
|
||||
* 670_frame_argcount — function-frame size for functions with more
|
||||
* than six register-class args. wwstage's cgfn pre-scan used to add
|
||||
* a full slot per param regardless of class, then round up — so a
|
||||
* 7-arg i64 fn over-allocated by 16B (cstage $48, wwstage $64) and
|
||||
* broke the bootstrap byte-identity gate (tests 993/994/995). Fix:
|
||||
* pre-scan now mirrors cgfnparams' SysV class accounting (and cstage
|
||||
* cgen.c §5130-5223), so a stack-spilled param adds 0 to the frame
|
||||
* and never bumps the reg cursor.
|
||||
*
|
||||
* Each row asserts value correctness. Byte-identity is asserted at
|
||||
* the bootstrap level by 993/994/995, so this file pins the runtime
|
||||
* contract: a function that mixes reg and stack args must compute
|
||||
* the same answer in both stages.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 7-arg i64: 6 regs + 1 stack. Pins the frame size that used to
|
||||
* over-allocate by 16B in wwstage. Returns the sum (1..7 = 28). */
|
||||
{ "i64_7args",
|
||||
"fn s7(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, g: i64) i64 = {\n"
|
||||
" return a + b + c + d + e + f + g;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = s7(1i64, 2i64, 3i64, 4i64, 5i64, 6i64, 7i64);\n"
|
||||
" if (r == 28i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* 8-arg i64: 6 regs + 2 stack. Stack-cursor advances twice. */
|
||||
{ "i64_8args",
|
||||
"fn s8(a: i64, b: i64, c: i64, d: i64, e: i64,\n"
|
||||
" f: i64, g: i64, h: i64) i64 = {\n"
|
||||
" return a + b + c + d + e + f + g + h;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = s8(1i64, 2i64, 3i64, 4i64,\n"
|
||||
" 5i64, 6i64, 7i64, 8i64);\n"
|
||||
" if (r == 36i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Mixed-signed sum: an i64 thread through 7 args. */
|
||||
{ "i64_7args_signed",
|
||||
"fn s7(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, g: i64) i64 = {\n"
|
||||
" return a + b + c + d + e + f + g;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = s7(-1i64, -2i64, -3i64, -4i64, -5i64, -6i64, 49i64);\n"
|
||||
" if (r == 28i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* 9-arg f64: 8 XMM regs + 1 stack. Exercises fargi past 8. */
|
||||
{ "f64_9args",
|
||||
"fn s9f(a: f64, b: f64, c: f64, d: f64, e: f64,\n"
|
||||
" f: f64, g: f64, h: f64, i: f64) f64 = {\n"
|
||||
" return a + b + c + d + e + f + g + h + i;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: f64 = s9f(1.0, 2.0, 3.0, 4.0, 5.0,\n"
|
||||
" 6.0, 7.0, 8.0, 9.0);\n"
|
||||
" if ((r: i64) == 45i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Mixed: 5 ints fill ints 1..5, 3 floats fill XMM 1..3, then 4
|
||||
* more ints — int #6 goes to reg, ints #7..9 spill to stack. The
|
||||
* int and float classes have independent cursors, so XMM stays
|
||||
* at 3 even after the trailing 4 ints. */
|
||||
{ "mixed_5i_3f_4i",
|
||||
"fn mx(a: i64, b: i64, c: i64, d: i64, e: i64,\n"
|
||||
" f1: f64, f2: f64, f3: f64,\n"
|
||||
" g: i64, h: i64, i: i64, j: i64) i64 = {\n"
|
||||
" return a + b + c + d + e\n"
|
||||
" + (f1: i64) + (f2: i64) + (f3: i64)\n"
|
||||
" + g + h + i + j;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = mx(1i64, 2i64, 3i64, 4i64, 5i64,\n"
|
||||
" 6.0, 7.0, 8.0,\n"
|
||||
" 9i64, 10i64, 11i64, 12i64);\n"
|
||||
" if (r == 78i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Slice at the reg/stack straddle: 4 i64 + 1 slice. idx=4
|
||||
* entering slice (regs_left=2, nw=3). ptr+len go to R8+R9,
|
||||
* cap spills to +16(BP); cgfnparams stitches into one slot.
|
||||
* Used to segfault before task #11. */
|
||||
{ "slice_straddle_arg5",
|
||||
"fn ss(a: i64, b: i64, c: i64, d: i64, xs: []i64) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c + d;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc + (xs.cap: i64);\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]i64;\n"
|
||||
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
|
||||
" let s: []i64 = buf[0:3];\n"
|
||||
" let r: i64 = ss(1i64, 2i64, 3i64, 4i64, s);\n"
|
||||
" if (r == 613i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* str at the reg/stack straddle: 5 i64 + 1 str. idx=5 entering
|
||||
* str (regs_left=1, nw=2). ptr lands in R9, len spills to
|
||||
* +16(BP). Used to read garbage for len/ptr. */
|
||||
{ "str_straddle_arg6",
|
||||
"fn ts(a: i64, b: i64, c: i64, d: i64, e: i64, s: str) i64 = {\n"
|
||||
" return a + b + c + d + e + s.len: i64;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = ts(1i64, 2i64, 3i64, 4i64, 5i64, \"hello\");\n"
|
||||
" if (r == 20i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Straddle followed by a scalar: 4 i64 + 1 slice + 1 i64.
|
||||
* Pins stkcursor advancing correctly past the stitched cap
|
||||
* word — the trailing i64 must land in R9, not back on stack. */
|
||||
{ "slice_straddle_then_scalar",
|
||||
"fn ssx(a: i64, b: i64, c: i64, d: i64, xs: []i64, tail: i64) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c + d + tail;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]i64;\n"
|
||||
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
|
||||
" let s: []i64 = buf[0:3];\n"
|
||||
" let r: i64 = ssx(1i64, 2i64, 3i64, 4i64, s, 99i64);\n"
|
||||
" if (r == 709i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Slice at the *narrow* end of the straddle: 5 i64 + 1 slice.
|
||||
* idx=5 entering slice (regs_left=1, nw=3) — ptr lands in R9,
|
||||
* len and cap both spill to +16/+24(BP). Complement of
|
||||
* slice_straddle_arg5 which exercises regs_left=2/nw=3. */
|
||||
{ "slice_straddle_arg6",
|
||||
"fn ss(a: i64, b: i64, c: i64, d: i64, e: i64, xs: []i64) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c + d + e;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc + (xs.cap: i64);\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]i64;\n"
|
||||
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
|
||||
" let s: []i64 = buf[0:3];\n"
|
||||
" let r: i64 = ss(1i64, 2i64, 3i64, 4i64, 5i64, s);\n"
|
||||
" if (r == 618i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Str at the *wide* end before the straddle: 4 i64 + 1 str.
|
||||
* idx=4 entering str (regs_left=2, nw=2) — full fit in R8+R9,
|
||||
* no stitch needed. Complement of str_straddle_arg6 which
|
||||
* exercises regs_left=1/nw=2; pins that the in-reg branch is
|
||||
* untouched by the new stitch code. */
|
||||
{ "str_inreg_arg5",
|
||||
"fn ts(a: i64, b: i64, c: i64, d: i64, s: str) i64 = {\n"
|
||||
" return a + b + c + d + s.len: i64;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = ts(1i64, 2i64, 3i64, 4i64, \"hello\");\n"
|
||||
" if (r == 15i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Slice past the register window: 6 ints saturate argi, the
|
||||
* slice (3 eightbytes) goes wholly to the stack at positive BP
|
||||
* offsets via localaddstack. Pure-stack regression check —
|
||||
* the existing pure-stack branch should be untouched by the
|
||||
* straddle stitch. */
|
||||
{ "slice_purestack_arg7",
|
||||
"fn ss(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, xs: []i64) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c + d + e + f;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]i64;\n"
|
||||
" buf[0] = 100i64; buf[1] = 200i64; buf[2] = 300i64;\n"
|
||||
" let s: []i64 = buf[0:3];\n"
|
||||
" let r: i64 = ss(1i64, 2i64, 3i64, 4i64, 5i64, 6i64, s);\n"
|
||||
" if (r == 621i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Variadic `T...` fully in regs: 3 i64 + 1 variadic. idx=3
|
||||
* entering the synthesised slice (regs_left=3, nw=3) — DI/SI/DX
|
||||
* carry a/b/c, CX/R8/R9 carry ptr/len/cap. Full-reg-fit regression:
|
||||
* pins that the stitch addition doesn't shadow the `idx+3 <= 6`
|
||||
* branch. */
|
||||
{ "variadic_full_in_reg",
|
||||
"fn vs(a: i64, b: i64, c: i64, xs: i64...) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc + (xs.cap: i64);\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = vs(1i64, 2i64, 3i64,\n"
|
||||
" 10i64, 20i64, 30i64);\n"
|
||||
" if (r == 69i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Variadic `T...` at the reg/stack straddle: 5 i64 + 1 variadic.
|
||||
* idx=5 entering the synthesised slice (regs_left=1, nw=3) — ptr
|
||||
* lands in R9, len and cap spill to +16/+24(BP). The variadic
|
||||
* branch in cgfnparams must stitch identically to the slice branch
|
||||
* (task #12). The body sums the fixed args, the variadic
|
||||
* elements, and adds cap to pin the third header word. */
|
||||
{ "variadic_straddle",
|
||||
"fn vs(a: i64, b: i64, c: i64, d: i64, e: i64, xs: i64...) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c + d + e;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc + (xs.cap: i64);\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = vs(1i64, 2i64, 3i64, 4i64, 5i64,\n"
|
||||
" 10i64, 20i64, 30i64);\n"
|
||||
" if (r == 78i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Variadic `T...` past the register window: 6 i64 saturate argi,
|
||||
* the synthesised slice (3 eightbytes) goes wholly to the stack
|
||||
* via localaddstack. Pure-stack regression check — pins that the
|
||||
* new stitch code doesn't bleed into the existing pure-stack
|
||||
* branch (regs_left=0, nw=3). */
|
||||
{ "variadic_purestack",
|
||||
"fn vs(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64,\n"
|
||||
" xs: i64...) i64 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" let acc: i64 = a + b + c + d + e + f;\n"
|
||||
" for (i < xs.len: i32) { acc = acc + xs[i]; i = i + 1; };\n"
|
||||
" return acc + (xs.cap: i64);\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = vs(1i64, 2i64, 3i64, 4i64, 5i64, 6i64,\n"
|
||||
" 10i64, 20i64, 30i64);\n"
|
||||
" if (r == 84i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* str past the register window: 6 ints saturate argi, the str
|
||||
* (2 eightbytes) goes wholly to the stack via localaddstack.
|
||||
* Both .ptr and .len must round-trip. */
|
||||
{ "str_purestack_arg7",
|
||||
"fn ts(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, s: str) i64 = {\n"
|
||||
" return a + b + c + d + e + f + s.len: i64;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: i64 = ts(1i64, 2i64, 3i64, 4i64, 5i64, 6i64, \"hello\");\n"
|
||||
" if (r == 26i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfa_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wwfa_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wwfa_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "frame_argcount: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"frame_argcount[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"frame_argcount: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("frame_argcount: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* 680_arr_elem_field — `arr[i].field` lowers to a single (idx*esz +
|
||||
* base) → field-load sequence, not a fall-through that returns the
|
||||
* element value (or address) as the dot's result. Filed from task #8
|
||||
* (originally framed as a wwstage bug; investigation flipped — cstage's
|
||||
* N_DOT had no N_INDEX-lhs branch and fell through, leaving the
|
||||
* caller's (AX, BX) shape junk for str/scalar leaf consumers).
|
||||
*
|
||||
* The cgenutil dotchainresolve workaround spills via `let nd = stk[i];
|
||||
* nd.str` to keep cstage from seeing the direct shape; this test pins
|
||||
* the direct shape so the spill can be retired in a follow-up.
|
||||
*
|
||||
* Coverage — both `[N]*Struct` and `[N]Struct` shapes, str / i32 / u8
|
||||
* fields. Cstage and wwstage each on every fixture; wwstage gated on
|
||||
* access(X_OK).
|
||||
*
|
||||
* `&arr[i].field` (TK_AMP through chained N_DOT N_INDEX) is task #9
|
||||
* territory and intentionally out of scope here. Write-side
|
||||
* `arr[i].field = v` is covered elsewhere — the wwstage write gap
|
||||
* surfaces separately.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* [N]*Struct, str field at non-zero offset. The original
|
||||
* task-#8 repro — stk[0].name where stk: [16]*nd.
|
||||
* Returns len("hello") = 5. */
|
||||
{ "ptr_arr_str_field",
|
||||
"type nd = struct {\n"
|
||||
" a: i32, b: i32,\n"
|
||||
" name: str,\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.a = 1; v.b = 2; v.name = \"hello\";\n"
|
||||
" let stk: [16]*nd; stk[0] = &v;\n"
|
||||
" let s: str = stk[0].name;\n"
|
||||
" return s.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* [N]*Struct, scalar i32 field. Pins the non-str leaf load
|
||||
* path through fldloadop (MOVSXD here for i32). Returns 42. */
|
||||
{ "ptr_arr_i32_field",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.name = \"hi\"; v.x = 42;\n"
|
||||
" let stk: [16]*nd; stk[0] = &v;\n"
|
||||
" return stk[0].x;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* [N]*Struct, u8 field — pins the sub-word zero-extend (MOVZBQ)
|
||||
* branch. Returns 7. */
|
||||
{ "ptr_arr_u8_field",
|
||||
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.tag = 7u8; v.pad = 0u8; v.x = 99;\n"
|
||||
" let stk: [8]*nd; stk[0] = &v;\n"
|
||||
" return stk[0].tag: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* [N]Struct (value array), str field. Element address goes
|
||||
* straight into AX; field load reads at foff(AX). Returns
|
||||
* len("world") = 5. */
|
||||
{ "val_arr_str_field",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn setit(p: *nd, s: str, vv: i32) void = { p.name = s; p.x = vv; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" setit(&arr[1], \"world\", 100);\n"
|
||||
" let s: str = arr[1].name;\n"
|
||||
" return s.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* [N]Struct, scalar i32 field. Pins the value-array path for a
|
||||
* non-str leaf — same address compute, MOVL load. Returns 100. */
|
||||
{ "val_arr_i32_field",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn setit(p: *nd, s: str, vv: i32) void = { p.name = s; p.x = vv; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" setit(&arr[2], \"q\", 100);\n"
|
||||
" return arr[2].x;\n"
|
||||
"};\n",
|
||||
100 },
|
||||
/* [N]Struct, u8 leaf — pins the value-array sub-word path
|
||||
* (no MOVQ-to-deref, direct address arithmetic into AX) then
|
||||
* fldloadop's MOVZBQ. Returns 9. */
|
||||
{ "val_arr_u8_field",
|
||||
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
||||
"fn setit(p: *nd, t: u8, vv: i32) void = { p.tag = t; p.x = vv; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" setit(&arr[2], 9u8, 50);\n"
|
||||
" return arr[2].tag: i32;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
/* [N]*Struct, i8 leaf with a negative value — pins that
|
||||
* fldloadop sign-extends (MOVBQSX) through the new branch
|
||||
* rather than the old MOVZBQ. Compare against -3 in i32 to
|
||||
* avoid the WEXITSTATUS truncation collision (sign-ext: -3,
|
||||
* zero-ext: 253 — both clash mod 256 if returned directly). */
|
||||
{ "ptr_arr_i8_signed",
|
||||
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.tag = -3i8; v.pad = 0u8; v.x = 0;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" let t: i32 = stk[0].tag: i32;\n"
|
||||
" if (t == -3) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Idx is a non-zero variable expression — pins the IMULQ path
|
||||
* and confirms the address compute doesn't accidentally fold
|
||||
* to a constant displacement. Returns len("third") = 5. */
|
||||
{ "ptr_arr_dyn_idx",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: nd; a.name = \"first\"; a.x = 1;\n"
|
||||
" let b: nd; b.name = \"two\"; b.x = 2;\n"
|
||||
" let c: nd; c.name = \"third\"; c.x = 3;\n"
|
||||
" let stk: [4]*nd;\n"
|
||||
" stk[0] = &a; stk[1] = &b; stk[2] = &c;\n"
|
||||
" let i: i32 = 2;\n"
|
||||
" let s: str = stk[i].name;\n"
|
||||
" return s.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wae_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wae_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wae_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "arr_elem_field: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"arr_elem_field[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_elem_field: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_elem_field: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,578 +0,0 @@
|
||||
/*
|
||||
* 681_arr_elem_field_write — `arr[i].field = v` (write-side counterpart
|
||||
* of 680_arr_elem_field). Both stages had a silent store-drop:
|
||||
*
|
||||
* cstage's chained-pointer-field-write branch (cgen.c near 1986) caught
|
||||
* `[N]*Struct` writes via its `!= N_IDENT` guard but skipped `[N]Struct`
|
||||
* value-arrays (TY_STRUCT element fails the TY_PTR guard).
|
||||
*
|
||||
* wwstage had no N_DOT(N_INDEX,...) lhs branch at all in cgassign
|
||||
* (cgenexpr.ww). Both shapes silently emitted no store.
|
||||
*
|
||||
* Closed in task #16 by mirroring task #8's read-side N_INDEX-lhs branch
|
||||
* onto the write side (fldstoreop, str/float leaf coverage, deref-or-not
|
||||
* for `*Struct` vs `Struct` element).
|
||||
*
|
||||
* Coverage — both array shapes, scalar/sub-word/str/float leaves, dyn
|
||||
* idx, and read-modify-write compound (`arr[i].x += 5`). Cstage and
|
||||
* wwstage on every fixture; wwstage gated on access(X_OK).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* [N]*Struct, i32 field write. Sole write into stk[0].x; read it
|
||||
* back. Returns 42. */
|
||||
{ "ptr_arr_i32_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.name = \"hi\"; v.x = 0;\n"
|
||||
" let stk: [16]*nd; stk[0] = &v;\n"
|
||||
" stk[0].x = 42;\n"
|
||||
" return stk[0].x;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* [N]*Struct, u8 field write. Sub-word store via MOVB through
|
||||
* fieldstoreop. Returns 9. */
|
||||
{ "ptr_arr_u8_write",
|
||||
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.tag = 0u8; v.pad = 0u8; v.x = 0;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" stk[0].tag = 9u8;\n"
|
||||
" return stk[0].tag: i32;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
/* [N]*Struct, str field write — 16B store of both ptr+len.
|
||||
* Returns len("hello") = 5. */
|
||||
{ "ptr_arr_str_write",
|
||||
"type nd = struct { a: i32, b: i32, name: str };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.a = 0; v.b = 0; v.name = \"old\";\n"
|
||||
" let stk: [16]*nd; stk[0] = &v;\n"
|
||||
" stk[0].name = \"hello\";\n"
|
||||
" return stk[0].name.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* [N]Struct, i32 field write (value-array). The cstage chained-
|
||||
* pointer-field branch's TY_PTR guard skips this; without the new
|
||||
* value-array path the store silently drops. Returns 50. */
|
||||
{ "val_arr_i32_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[2].x = 50;\n"
|
||||
" return arr[2].x;\n"
|
||||
"};\n",
|
||||
50 },
|
||||
/* [N]Struct, u8 field write — value-array sub-word store. Returns 7. */
|
||||
{ "val_arr_u8_write",
|
||||
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].tag = 7u8;\n"
|
||||
" return arr[1].tag: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* [N]Struct, str field write — value-array 16B store. Returns
|
||||
* len("world") = 5. */
|
||||
{ "val_arr_str_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].name = \"world\";\n"
|
||||
" return arr[1].name.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* Sub-word signed: write -3i8 then read back as i32. If MOVB
|
||||
* stored the truncated low byte and the field's fldloadop sign-
|
||||
* extends correctly, the i32 read returns -3. Returns 42 when
|
||||
* the equality check passes, 0 otherwise. */
|
||||
{ "ptr_arr_i8_signed_write",
|
||||
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.tag = 0i8; v.pad = 0u8; v.x = 0;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" stk[0].tag = -3i8;\n"
|
||||
" let t: i32 = stk[0].tag: i32;\n"
|
||||
" if (t == -3) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Value-array sub-word signed: distinct from ptr_arr_i8_signed —
|
||||
* this path takes LEAQ &arr[i] (no MOVQ-to-deref) and stores the
|
||||
* truncated byte via fldstoreop MOVB. Read back via fldloadop
|
||||
* MOVSBQ sign-extends to i32; -3 round-trips. Returns 42. */
|
||||
{ "val_arr_i8_signed_write",
|
||||
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].tag = -3i8;\n"
|
||||
" let t: i32 = arr[1].tag: i32;\n"
|
||||
" if (t == -3) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* f64 field write through value-array — pins MOVSD from X0 into
|
||||
* field offset of &arr[i]. Returns 23. */
|
||||
{ "val_arr_f64_write",
|
||||
"type nd = struct { x: i32, d: f64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].d = 23.0f64;\n"
|
||||
" let v: f64 = arr[1].d;\n"
|
||||
" return v: i32;\n"
|
||||
"};\n",
|
||||
23 },
|
||||
/* Dyn idx through [N]*Struct write — index isn't a literal, so
|
||||
* the IMULQ path fires. Returns 99. */
|
||||
{ "ptr_arr_dyn_idx_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: nd; a.name = \"a\"; a.x = 0;\n"
|
||||
" let b: nd; b.name = \"b\"; b.x = 0;\n"
|
||||
" let c: nd; c.name = \"c\"; c.x = 0;\n"
|
||||
" let stk: [4]*nd;\n"
|
||||
" stk[0] = &a; stk[1] = &b; stk[2] = &c;\n"
|
||||
" let i: i32 = 2;\n"
|
||||
" stk[i].x = 99;\n"
|
||||
" return stk[2].x;\n"
|
||||
"};\n",
|
||||
99 },
|
||||
/* Read-modify-write compound on [N]Struct — exercises BOTH the
|
||||
* read (load old field) and the write (store combined). For
|
||||
* `arr[1].x += 5` with arr[1].x = 37 → 42. */
|
||||
{ "val_arr_compound_plus",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].x = 37;\n"
|
||||
" arr[1].x += 5;\n"
|
||||
" return arr[1].x;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Non-PLUSEQ compound (CARETEQ) on [N]Struct — exercises the XORQ
|
||||
* arm of the compound switch. Worker wired all six integer
|
||||
* compound ops; this pins one of the non-PLUSEQ arms so a future
|
||||
* regression in the switch table is caught. 0x2A ^ 0x14 = 0x3E
|
||||
* (62), then return 62 - 20 = 42. */
|
||||
{ "val_arr_compound_xor",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[2].x = 42;\n"
|
||||
" arr[2].x ^= 20;\n"
|
||||
" return arr[2].x - 20;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Compound through [N]*Struct — same shape but viaptr is true,
|
||||
* so the address compute deref-loads (BX), and the load_op picks
|
||||
* MOVSXD for i32. 10 += 22 → 32. */
|
||||
{ "ptr_arr_compound_plus",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.name = \"a\"; v.x = 10;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" stk[0].x += 22;\n"
|
||||
" return stk[0].x;\n"
|
||||
"};\n",
|
||||
32 },
|
||||
/* Whole-slice rhs to struct field, direct struct local (task #24).
|
||||
* cgen N_ASSIGN had a TY_STR branch that stored ptr+len at +0/+8
|
||||
* but no TY_SLICE branch; the fallthrough generic store wrote only
|
||||
* AX (ptr), silently dropping .len and .cap. Fixed by a parallel
|
||||
* TY_SLICE branch that stores AX/BX/CX at +0/+8/+16. The bug bit
|
||||
* bufio.init's `b.rbuf = rbuf` (first stdlib struct with a []u8
|
||||
* field). Returns 42 when len, cap, and a trailing scalar field
|
||||
* all survived the store. */
|
||||
{ "slice_field_value_write",
|
||||
"type bs = struct { rbuf: []u8, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8; raw[0] = 0u8;\n"
|
||||
" let rbuf: []u8 = raw[0:5];\n"
|
||||
" let b: bs;\n"
|
||||
" b.mark = 7;\n"
|
||||
" b.rbuf = rbuf;\n"
|
||||
" if (b.rbuf.len != 5) { return 1; };\n"
|
||||
" if (b.rbuf.cap != 8) { return 2; };\n"
|
||||
" if (b.mark != 7) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Same bug via pointer to struct — the bufio scenario exactly
|
||||
* (`init(b: *bstream, rbuf: []u8) { b.rbuf = rbuf; }`). The via_ptr
|
||||
* arm of the new TY_SLICE branch stages the struct addr in DX to
|
||||
* avoid clobbering CX (cap). */
|
||||
{ "slice_field_ptr_write",
|
||||
"type bs = struct { rbuf: []u8, mark: i32 };\n"
|
||||
"fn init(b: *bs, rbuf: []u8) void = {\n"
|
||||
" b.rbuf = rbuf;\n"
|
||||
" b.mark = 9;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8; raw[0] = 0u8;\n"
|
||||
" let b: bs;\n"
|
||||
" init(&b, raw[0:5]);\n"
|
||||
" if (b.rbuf.len != 5) { return 1; };\n"
|
||||
" if (b.rbuf.cap != 8) { return 2; };\n"
|
||||
" if (b.mark != 9) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Distinct byte-pattern check — independent verification that
|
||||
* the AX/BX/CX MOVQs landed at +0/+8/+16 rather than passing by
|
||||
* coincidence with neighbouring zero memory. Distinct byte
|
||||
* patterns at raw[0] (0xAA) and raw[5] (0xFF) are read back
|
||||
* through b.rbuf[i], which only succeeds if .ptr (AX) survived
|
||||
* at +0. Trailing mark uses a distinctive value (0x33) so a
|
||||
* stray CX store one slot too far is caught separately. Here
|
||||
* .len=6 (hi-lo) and .cap=16 (base_cap-lo; raw is [16]u8) per
|
||||
* project #20, so this row pins .ptr survival and additionally
|
||||
* confirms .len and .cap land as distinct words (they no longer
|
||||
* coincide), while the other rows pin .len/.cap distinctness
|
||||
* from the zero default. */
|
||||
{ "slice_field_distinct_bytes",
|
||||
"type bs = struct { rbuf: []u8, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [16]u8;\n"
|
||||
" raw[0] = 0xAAu8; raw[1] = 0xBBu8; raw[2] = 0xCCu8;\n"
|
||||
" raw[3] = 0xDDu8; raw[4] = 0xEEu8; raw[5] = 0xFFu8;\n"
|
||||
" let b: bs;\n"
|
||||
" b.mark = 0x33;\n"
|
||||
" b.rbuf = raw[0:6];\n"
|
||||
" if (b.rbuf.len != 6) { return 1; };\n"
|
||||
" if (b.rbuf.cap != 16) { return 2; };\n"
|
||||
" if (b.mark != 0x33) { return 3; };\n"
|
||||
" if (b.rbuf[0] != 0xAAu8) { return 4; };\n"
|
||||
" if (b.rbuf[5] != 0xFFu8) { return 5; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Str-field regression for the parallel TY_STR branch right above
|
||||
* the new TY_SLICE branch — pin that store path so a future refactor
|
||||
* doesn't drop it. Direct struct local; `b.s = "hello"` stores
|
||||
* AX/BX at +0/+8, and the trailing i32 mark survives. */
|
||||
{ "str_field_value_write_regression",
|
||||
"type bs = struct { s: str, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: bs;\n"
|
||||
" b.mark = 11;\n"
|
||||
" b.s = \"hello\";\n"
|
||||
" if (b.s.len != 5) { return 1; };\n"
|
||||
" if (b.mark != 11) { return 2; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Whole-struct rhs to struct field (task #25). cgen N_ASSIGN had
|
||||
* special-cases for TY_STR (2 words) and TY_SLICE (3 words) but no
|
||||
* TY_STRUCT branch — the fallthrough fldstoreop wrote only AX,
|
||||
* dropping every trailing word. Fixed by a word-copy branch that
|
||||
* runs MOVQ from rhs slot to dest field, plus a 4/1-byte ragged
|
||||
* tail. The 16B row is the BORDER case: same total size as TY_STR
|
||||
* but must NOT take the str branch (str field has 16B header; this
|
||||
* is a 2-word struct value, structurally identical but type-
|
||||
* dispatched). Distinct byte values per word so a stray store at
|
||||
* the wrong offset surfaces as a misvalue, not coincidental zero. */
|
||||
{ "struct_field_value_write_16B",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type outer = struct { i: inner, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: inner;\n"
|
||||
" o.a = 0x1122334455667788i64;\n"
|
||||
" o.b = 0x99aabbccddeeff00i64;\n"
|
||||
" let x: outer;\n"
|
||||
" x.mark = 0x55;\n"
|
||||
" x.i = o;\n"
|
||||
" if (x.i.a != 0x1122334455667788i64) { return 1; };\n"
|
||||
" if (x.i.b != 0x99aabbccddeeff00i64) { return 2; };\n"
|
||||
" if (x.mark != 0x55) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* 24B struct rhs (the original #25 probe case). Three words at +0/+8/+16
|
||||
* exercise the word-copy loop with no ragged tail. Distinct byte
|
||||
* patterns pin each word independently. */
|
||||
{ "struct_field_value_write_24B",
|
||||
"type inner = struct { a: i64, b: i64, c: i64 };\n"
|
||||
"type outer = struct { i: inner, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: inner;\n"
|
||||
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
||||
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
||||
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
||||
" let x: outer;\n"
|
||||
" x.mark = 7;\n"
|
||||
" x.i = o;\n"
|
||||
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
||||
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
||||
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
||||
" if (x.mark != 7) { return 4; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* 32B struct rhs — four-word copy, no ragged tail. Trailing mark in
|
||||
* the outer struct catches a stray fifth-word store. */
|
||||
{ "struct_field_value_write_32B",
|
||||
"type inner = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
||||
"type outer = struct { i: inner, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: inner;\n"
|
||||
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
||||
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
||||
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
||||
" o.d = 0x3d3d3d3d3d3d3d3di64;\n"
|
||||
" let x: outer;\n"
|
||||
" x.mark = 0x33;\n"
|
||||
" x.i = o;\n"
|
||||
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
||||
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
||||
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
||||
" if (x.i.d != 0x3d3d3d3d3d3d3d3di64) { return 4; };\n"
|
||||
" if (x.mark != 0x33) { return 5; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Nested struct field write through chained N_DOT spine
|
||||
* (`b.inner.deep = other_deep`). Confirms #22's spine walker
|
||||
* handles a TY_STRUCT terminal, not just scalar/str/slice. The
|
||||
* 24B inner struct payload must survive both the spine walk and
|
||||
* the word-copy. */
|
||||
{ "struct_field_value_write_nested_chain",
|
||||
"type deep = struct { a: i64, b: i64, c: i64 };\n"
|
||||
"type mid = struct { d: deep, mark: i32 };\n"
|
||||
"type outer = struct { m: mid, tag: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let other: deep;\n"
|
||||
" other.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
||||
" other.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
||||
" other.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
||||
" let v: outer;\n"
|
||||
" v.m.mark = 5;\n"
|
||||
" v.tag = 9;\n"
|
||||
" v.m.d = other;\n"
|
||||
" if (v.m.d.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
||||
" if (v.m.d.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
||||
" if (v.m.d.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
||||
" if (v.m.mark != 5) { return 4; };\n"
|
||||
" if (v.tag != 9) { return 5; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Whole-struct rhs to a struct field via a *struct base — exercises
|
||||
* the via_ptr arm of the new TY_STRUCT branch. Stages the dest addr
|
||||
* in BX (MOVQ off(BP),BX) before iterating, then writes at fi.foff+k(BX). */
|
||||
{ "struct_field_value_write_via_ptr",
|
||||
"type inner = struct { a: i64, b: i64, c: i64 };\n"
|
||||
"type outer = struct { i: inner, mark: i32 };\n"
|
||||
"fn fill(x: *outer) void = {\n"
|
||||
" let o: inner;\n"
|
||||
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
||||
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
||||
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
||||
" x.mark = 7;\n"
|
||||
" x.i = o;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: outer;\n"
|
||||
" fill(&x);\n"
|
||||
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
||||
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
||||
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
||||
" if (x.mark != 7) { return 4; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Ragged tail — 12B inner struct (3 i32 fields, maxalign=4, no 8B
|
||||
* round-up) lands the word-copy loop at k=8 with 4 bytes left and
|
||||
* pins the MOVL tail branch. Tail-only structs are reachable for
|
||||
* any TY_STRUCT whose maxalign < 8: this is the smallest such
|
||||
* shape. The trailing i32 mark in the outer struct catches a stray
|
||||
* MOVQ tail (which would overwrite +4 of the mark slot). The 5/3/2
|
||||
* byte tails (struct of just i8 fields) fall back to MOVQ in cgen
|
||||
* and would overwrite past the field boundary; the language permits
|
||||
* align==1 structs but they're not exercised here — filed as a
|
||||
* sub-followup if a real callsite surfaces. */
|
||||
{ "struct_field_value_write_ragged_tail_12B",
|
||||
"type inner = struct { a: i32, b: i32, c: i32 };\n"
|
||||
"type outer = struct { i: inner, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: inner;\n"
|
||||
" o.a = 0x11223344;\n"
|
||||
" o.b = 0x55667788;\n"
|
||||
" o.c = 0x29aabbcc;\n"
|
||||
" let x: outer;\n"
|
||||
" x.mark = 0x33;\n"
|
||||
" x.i = o;\n"
|
||||
" if (x.i.a != 0x11223344) { return 1; };\n"
|
||||
" if (x.i.b != 0x55667788) { return 2; };\n"
|
||||
" if (x.i.c != 0x29aabbcc) { return 3; };\n"
|
||||
" if (x.mark != 0x33) { return 4; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Whole-tagged rhs to struct tagged-union field (task #26). cgen
|
||||
* N_ASSIGN had a TY_TAGGED branch that called cg_tag_for_variant
|
||||
* with the rhs type — when the rhs was the tagged union itself
|
||||
* (not a concrete variant), the function returned -1 and the
|
||||
* branch wrote literal 0 as the tag plus only AX as the payload,
|
||||
* dropping the original tag and trailing payload words. Fixed
|
||||
* by routing through cg_widen_tagged_store, which copies all slot
|
||||
* words from the source's tagged slot and runs cg_widen_tag_remap.
|
||||
*
|
||||
* Verification: the write must not bleed into the trailing `mark`
|
||||
* field. Direct struct local. Reading x.e back via match is a
|
||||
* separate code path (gated on a wwstage read-side bug for
|
||||
* tagged N_DOT — filed below as a follow-up); these rows pin the
|
||||
* write-side bug and let mark act as a canary for stray stores
|
||||
* past the field boundary. */
|
||||
{ "tagged_field_value_write_local_mark_canary",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: ev = 33: i32;\n"
|
||||
" let x: holder;\n"
|
||||
" x.mark = 0x3a3a3a3a;\n"
|
||||
" x.e = v;\n"
|
||||
" if (x.mark != 0x3a3a3a3a) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Same shape, concrete-variant str widening. The str payload
|
||||
* occupies +8/+16 (ptr/len); a missing store to +16 (the pre-#26
|
||||
* shape that wrote only AX) would leave .len at whatever the
|
||||
* neighbouring memory held. mark canary at +24 catches a stray
|
||||
* AX-only path that wrote past 16B into the mark slot. */
|
||||
{ "tagged_field_value_write_str_variant_mark_canary",
|
||||
"type ev = (i32 | str);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: holder;\n"
|
||||
" x.mark = 0x5e5e5e5e;\n"
|
||||
" x.e = (\"hello\": ev);\n"
|
||||
" if (x.mark != 0x5e5e5e5e) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Pointer-rooted dst — `(*p).e = v` exercises the base-reg
|
||||
* parameterisation added to cg_widen_tagged_store in #26. The
|
||||
* wrapper spills BX (the *struct pointer), routes the body
|
||||
* through a BP-rooted scratch, reloads BX, and word-copies the
|
||||
* scratch to (BX, foff). Without that path the via_ptr arm of
|
||||
* the new TY_TAGGED branch would either trample BX during cgexpr
|
||||
* or write to a stale slot address. mark canary at +24 catches
|
||||
* a stray spill that wrote past the e field. */
|
||||
{ "tagged_field_value_write_via_ptr_mark_canary",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn fill(h: *holder) void = {\n"
|
||||
" let v: ev = 33: i32;\n"
|
||||
" h.mark = 0x77777777;\n"
|
||||
" h.e = v;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: holder;\n"
|
||||
" fill(&x);\n"
|
||||
" if (x.mark != 0x77777777) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/waew_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/waew_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "arr_elem_field_write: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"arr_elem_field_write[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_elem_field_write: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_elem_field_write: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* 690_amp_dot — address-of through a DOT chain.
|
||||
*
|
||||
* cstage's TK_AMP early-exit historically only handled `&ident` and
|
||||
* `&base[i]`; everything else fell through to a silent-drop fallback,
|
||||
* so `&o.i.a` left AX undefined (not even the value — undefined).
|
||||
* Filed as task #9 from worker-chained-dot judgement #3. Pinned here:
|
||||
*
|
||||
* - single-DOT `&o.f` on a value-struct local AND a global root,
|
||||
* - chained `&o.i.a` on a value-struct (depth 2),
|
||||
* - 3-deep `&o.a.b.c` (confirms the walker is loop-shaped, not
|
||||
* hardcoded to depth 2),
|
||||
* - pointer-field `&p.f` where p:*T,
|
||||
* - slice-header `&s.len`: write through it (`*&s.len = 0;`) and
|
||||
* read back via `s.len`. This is the motivating Hare-slice-header
|
||||
* poke pattern — the whole reason this gap got filed.
|
||||
* - address agrees with read: `*&o.i.a == o.i.a` round-trips so the
|
||||
* spine walk's offset arithmetic matches the read path's.
|
||||
*
|
||||
* Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage) when
|
||||
* present.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* &o.f single-DOT on a value-struct local. Round-trips a write
|
||||
* through the address, returns the field directly. */
|
||||
{ "amp_dot_single_local",
|
||||
"type pt = struct { x: i32, y: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: pt;\n"
|
||||
" let p: *i32 = &o.x;\n"
|
||||
" *p = 21;\n"
|
||||
" return o.x;\n"
|
||||
"};\n",
|
||||
21 },
|
||||
/* &g.f single-DOT on a global (top-level let) struct root.
|
||||
* Pins the LEAQ name(SB), CX → LEAQ disp(CX), AX form. */
|
||||
{ "amp_dot_single_global",
|
||||
"type pt = struct { x: i32, y: i32 };\n"
|
||||
"let g: pt;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let p: *i32 = &g.x;\n"
|
||||
" *p = 33;\n"
|
||||
" return g.x;\n"
|
||||
"};\n",
|
||||
33 },
|
||||
/* &o.i.a — Drew's chained value-struct shape. Address-of side
|
||||
* of task #6's read fix. */
|
||||
{ "amp_dot_chain_2deep",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer;\n"
|
||||
" let p: *i32 = &o.i.a;\n"
|
||||
" *p = 17;\n"
|
||||
" return o.i.a;\n"
|
||||
"};\n",
|
||||
17 },
|
||||
/* 3-deep chain: confirms the spine walker is loop-shaped, not
|
||||
* hardcoded to depth 2. Same shape as 650's value_struct_3deep
|
||||
* but going through &. */
|
||||
{ "amp_dot_chain_3deep",
|
||||
"type a3 = struct { a: i32 };\n"
|
||||
"type a2 = struct { a: a3 };\n"
|
||||
"type a1 = struct { a: a2 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: a1;\n"
|
||||
" let p: *i32 = &v.a.a.a;\n"
|
||||
" *p = 9;\n"
|
||||
" return v.a.a.a;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
/* &p.f where p:*T (pointer-field). Spine walker aborts on the
|
||||
* *T base; the pointer-field fallback should fire. */
|
||||
{ "amp_dot_ptr_field",
|
||||
"type pt = struct { x: i32, y: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: pt;\n"
|
||||
" o.x = 0; o.y = 0;\n"
|
||||
" let p: *pt = &o;\n"
|
||||
" let q: *i32 = &p.y;\n"
|
||||
" *q = 55;\n"
|
||||
" return o.y;\n"
|
||||
"};\n",
|
||||
55 },
|
||||
/* The motivating idiom: write through `&s.len` on a slice header
|
||||
* to truncate without re-allocating. Mirrors the Hare slice-
|
||||
* header poke pattern that bufio will eventually want. The slice
|
||||
* header's .len slot is 8B even though the surface type is i32,
|
||||
* so `&s.len` is `*i64` (matches storage); deref-store hits all
|
||||
* 8B, and the i64 load reads back the value the user wrote. */
|
||||
{ "amp_dot_slice_len_writethrough",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = &arr[0];\n"
|
||||
" s.len = 4;\n"
|
||||
" s.cap = 4;\n"
|
||||
" let q: *i64 = &s.len;\n"
|
||||
" *q = 0i64;\n"
|
||||
" return s.len: i32;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Width-stress for `&s.len`: write a value whose lower-32B
|
||||
* differs from upper-32B and confirm the upper bytes don't
|
||||
* leak from the prior 8B store of `s.len = 4`. Before the fix,
|
||||
* `&s.len` was `*i32` and `*q = v` lowered to MOVL, leaving
|
||||
* the upper 4B at whatever the MOVQ store of 4 left there
|
||||
* (zero — pass by accident). With a non-zero stale upper or a
|
||||
* fresh write that fills both halves, the i64 read of s.len
|
||||
* exposes the mismatch. We write 0xFFFF_FFFF_FFFF_FFFF and
|
||||
* return 1 iff s.len reads back as -1 (i64). */
|
||||
{ "amp_dot_slice_len_width_stress",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = &arr[0];\n"
|
||||
" s.len = 4;\n"
|
||||
" s.cap = 4;\n"
|
||||
" let q: *i64 = &s.len;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = s.len: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* Same shape for `&s.cap`. cap lives at +16 in the slice header
|
||||
* and was the second pseudo-field broken by the same width bug. */
|
||||
{ "amp_dot_slice_cap_width_stress",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = &arr[0];\n"
|
||||
" s.len = 4;\n"
|
||||
" s.cap = 4;\n"
|
||||
" let q: *i64 = &s.cap;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = s.cap: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* Same shape for str.len. str header is (ptr, len) with len at
|
||||
* +8, also 8B storage. */
|
||||
{ "amp_dot_str_len_width_stress",
|
||||
"fn main() i32 = {\n"
|
||||
" let s: str = \"abcd\";\n"
|
||||
" let q: *i64 = &s.len;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = s.len: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* `&s.ptr` write-through. ptr lives at +0 and is `*T` (not a
|
||||
* pseudo-i32), so `&s.ptr` types as `**T` — already the right
|
||||
* width pre-fix. Pin it so a future regression doesn't slip the
|
||||
* other way. */
|
||||
{ "amp_dot_slice_ptr_writethrough",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [2]u8;\n"
|
||||
" arr[0] = 13; arr[1] = 0;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.len = 2; s.cap = 2;\n"
|
||||
" let pp: **u8 = &s.ptr;\n"
|
||||
" *pp = &arr[0];\n"
|
||||
" return s.ptr[0]: i32;\n"
|
||||
"};\n",
|
||||
13 },
|
||||
/* Chained `&w.s.len` — slice WRAPPED in a struct. The pseudo-
|
||||
* field width override gates on the leaf .len/.cap with base
|
||||
* type TY_SLICE/TY_STR; for `&w.s.len` the base of the leaf
|
||||
* dot is `w.s` (type []u8), so the override must still fire
|
||||
* and the deref-store must hit all 8B at the wrapped slice
|
||||
* header's len slot (offset of .s + 8). */
|
||||
{ "amp_dot_slice_field_len_width",
|
||||
"type wrap = struct { s: []u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let w: wrap;\n"
|
||||
" w.s.ptr = &arr[0];\n"
|
||||
" w.s.len = 4; w.s.cap = 4;\n"
|
||||
" let q: *i64 = &w.s.len;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = w.s.len: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* &o.s.ptr — slice-FIELD of a struct: combines shape 1 (walk
|
||||
* into struct field at .s) and shape 3 (slice pseudo-tail
|
||||
* .ptr at offset 0 of the header). Proves the spine walker's
|
||||
* slice_delta fold composes with the struct-field offset; the
|
||||
* write through `*&o.s.ptr` must land at the slice header's
|
||||
* ptr slot inside the enclosing struct. */
|
||||
{ "amp_dot_slice_field_ptr",
|
||||
"type wrap = struct { s: []u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let w: wrap;\n"
|
||||
" w.s.len = 4; w.s.cap = 4;\n"
|
||||
" let pp: **u8 = &w.s.ptr;\n"
|
||||
" *pp = &arr[0];\n"
|
||||
" arr[0] = 71;\n"
|
||||
" return w.s.ptr[0]: i32;\n"
|
||||
"};\n",
|
||||
71 },
|
||||
/* Address agrees with read: `*&o.i.a == o.i.a`. The deref load
|
||||
* of the address must reproduce the same value the read path
|
||||
* lowers — proves the spine walker's offset sum is identical
|
||||
* across both directions. */
|
||||
{ "amp_dot_addr_eq_read",
|
||||
"type inner = struct { a: i32, b: i32 };\n"
|
||||
"type outer = struct { i: inner, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer;\n"
|
||||
" o.i.a = 41;\n"
|
||||
" let p: *i32 = &o.i.a;\n"
|
||||
" if (*p == o.i.a) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wad_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wad_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wad_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1100];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1100];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "amp_dot: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"amp_dot[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"amp_dot: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("amp_dot: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/*
|
||||
* 691_dot_slice_arg — pass `<expr>.slicefield` as a call argument
|
||||
* (and return one too).
|
||||
*
|
||||
* Task #29: the cgen N_DOT-slice-field paths previously loaded only
|
||||
* .ptr into AX, leaving BX/CX stale. The slice-arg push at the call
|
||||
* site then pushed three words from AX/BX/CX, so .len/.cap silently
|
||||
* came from whatever the prior expression left in those registers.
|
||||
* Surfaced as `998_bufio_run`'s bstreamunread mid-read len mismatch.
|
||||
*
|
||||
* Each fixture passes (or returns) a slice header through a call and
|
||||
* reads .len / .cap / .ptr on the callee side to prove all three
|
||||
* words round-trip. Rows cover:
|
||||
* - single dot through `*T` root (`p.sl`)
|
||||
* - chained through `*T` root (`p.inner.sl`) — pins
|
||||
* task #22's spine walker still works with the slice-leaf fix
|
||||
* in place
|
||||
* - local value-struct root, single dot (`o.sl`)
|
||||
* - local value-struct root, chained (`o.inner.sl`)
|
||||
* - direct slice arg (no dot) — baseline / negative
|
||||
* control: confirms the existing fast-path still works
|
||||
* - slice + int neighbor arg — pins call-arg eval
|
||||
* order doesn't drop the integer slot beside the slice
|
||||
* - slice mutation through `.ptr` — pins `.ptr` half
|
||||
* still arrives correctly post-fix
|
||||
* - N_RETURN of slice N_DOT (probe 3) — exercises the same
|
||||
* cgen N_DOT slice-load path on the return-stmt site, not
|
||||
* just the call-arg site
|
||||
*
|
||||
* Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Single dot through *T root. `slen(p.sl)` should see len=5,
|
||||
* cap=8, ptr[0]=7 (we set arr[0]=7 in main). */
|
||||
{ "ptr_root_slice_arg",
|
||||
"type outer = struct { sl: []u8, x: i32 };\n"
|
||||
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
|
||||
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
|
||||
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8; arr[0] = 7u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.sl.ptr = &arr[0]; o.sl.len = 5; o.sl.cap = 8;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" if (slen(p.sl) != 5) { return 1; };\n"
|
||||
" if (scap(p.sl) != 8) { return 2; };\n"
|
||||
" if (sfirst(p.sl) != 7) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Chained through *T root with a value-struct hop — the #22
|
||||
* spine walker shape, leaf is a slice. */
|
||||
{ "ptr_root_chained_slice_arg",
|
||||
"type inner = struct { sl: []u8, pad: i32 };\n"
|
||||
"type outer = struct { i: inner, tag: i32 };\n"
|
||||
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
|
||||
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
|
||||
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [16]u8; arr[0] = 11u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.i.sl.ptr = &arr[0]; o.i.sl.len = 9; o.i.sl.cap = 16;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" if (slen(p.i.sl) != 9) { return 1; };\n"
|
||||
" if (scap(p.i.sl) != 16) { return 2; };\n"
|
||||
" if (sfirst(p.i.sl) != 11) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Local value-struct rooted, single dot. The previous bug was
|
||||
* present here too — the "real struct field" cgen branch loaded
|
||||
* only AX. */
|
||||
{ "local_root_slice_arg",
|
||||
"type outer = struct { sl: []u8, x: i32 };\n"
|
||||
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
|
||||
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
|
||||
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8; arr[0] = 3u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.sl.ptr = &arr[0]; o.sl.len = 4; o.sl.cap = 8;\n"
|
||||
" if (slen(o.sl) != 4) { return 1; };\n"
|
||||
" if (scap(o.sl) != 8) { return 2; };\n"
|
||||
" if (sfirst(o.sl) != 3) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Local value-struct rooted, chained value-struct hop. Spine
|
||||
* walker without ptr_root. */
|
||||
{ "local_root_chained_slice_arg",
|
||||
"type inner = struct { sl: []u8, pad: i32 };\n"
|
||||
"type outer = struct { i: inner, tag: i32 };\n"
|
||||
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
|
||||
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [16]u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.i.sl.ptr = &arr[0]; o.i.sl.len = 6; o.i.sl.cap = 16;\n"
|
||||
" if (slen(o.i.sl) != 6) { return 1; };\n"
|
||||
" if (scap(o.i.sl) != 16) { return 2; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Chained through a `*struct` mid-hop (`o.i.sl` where `i: *inner`).
|
||||
* Distinct cgen site from rows 2/4: the spine walker (dotchain-
|
||||
* resolve) only walks value-struct intermediate hops, so a *struct
|
||||
* mid-hop falls through to the "chained N_DOT through *struct
|
||||
* field" branch (cgen.c ~4832, cgenexpr.ww ~1834) — the fourth
|
||||
* TY_SLICE site touched by this commit. AX is the *struct base
|
||||
* after `cgexpr(o.i)`, so the load order must end with .ptr → AX. */
|
||||
{ "ptr_field_chained_slice_arg",
|
||||
"type inner = struct { sl: []u8, pad: i32 };\n"
|
||||
"type outer = struct { i: *inner, tag: i32 };\n"
|
||||
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
|
||||
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
|
||||
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8; arr[0] = 17u8;\n"
|
||||
" let inn: inner;\n"
|
||||
" inn.sl.ptr = &arr[0]; inn.sl.len = 3; inn.sl.cap = 8;\n"
|
||||
" let o: outer; o.i = &inn; o.tag = 0;\n"
|
||||
" if (slen(o.i.sl) != 3) { return 1; };\n"
|
||||
" if (scap(o.i.sl) != 8) { return 2; };\n"
|
||||
" if (sfirst(o.i.sl) != 17) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Negative control: direct slice arg, no dot. Pre-existing fast
|
||||
* path; confirms the fix didn't disturb it. */
|
||||
{ "direct_slice_arg_baseline",
|
||||
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
|
||||
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
|
||||
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8; arr[0] = 9u8;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = &arr[0]; s.len = 5; s.cap = 8;\n"
|
||||
" if (slen(s) != 5) { return 1; };\n"
|
||||
" if (scap(s) != 8) { return 2; };\n"
|
||||
" if (sfirst(s) != 9) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Mixed args: pass a slice-field next to another arg, so the
|
||||
* fix has to leave neighbouring arg-slot registers undisturbed.
|
||||
* AX/BX/CX are clobbered while building the slice; the integer
|
||||
* `tag` arg goes in DI on SysV, so this pins the call-arg eval
|
||||
* order doesn't drop the tag. */
|
||||
{ "ptr_root_slice_with_neighbor",
|
||||
"type outer = struct { sl: []u8, x: i32 };\n"
|
||||
"fn slen_tag(s: []u8, t: i32) i32 = { return s.len: i32 + t; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.sl.ptr = &arr[0]; o.sl.len = 5; o.sl.cap = 8;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" let v: i32 = slen_tag(p.sl, 37);\n"
|
||||
" if (v != 42) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Mutation through the passed slice: callee writes via s[0],
|
||||
* caller reads arr[0] after. Proves the .ptr half made it
|
||||
* across — even before the fix this *would have* worked (only
|
||||
* .len/.cap dropped), but co-testing it guards against a future
|
||||
* regression that swaps ptr for len. */
|
||||
{ "ptr_root_slice_mutate_ptr",
|
||||
"type outer = struct { sl: []u8, x: i32 };\n"
|
||||
"fn poke(s: []u8) void = { s[0] = 0x42u8; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8; arr[0] = 0u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.sl.ptr = &arr[0]; o.sl.len = 1; o.sl.cap = 8;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" poke(p.sl);\n"
|
||||
" if (arr[0]: i32 != 0x42) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Probe 3: N_RETURN of a slice N_DOT — a function returns
|
||||
* `p.sl` directly. cgen's return-stmt site also has to leave
|
||||
* (AX=ptr, BX=len, CX=cap), or the caller's `let s: []u8 =
|
||||
* fetch(p)` will lose .len/.cap. Distinct site from the call-
|
||||
* arg push but exercises the same cgen N_DOT slice-load path. */
|
||||
{ "ptr_root_slice_return",
|
||||
"type outer = struct { sl: []u8, x: i32 };\n"
|
||||
"fn fetch(p: *outer) []u8 = { return p.sl; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [8]u8; arr[0] = 19u8;\n"
|
||||
" let o: outer;\n"
|
||||
" o.sl.ptr = &arr[0]; o.sl.len = 6; o.sl.cap = 8;\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" let s: []u8 = fetch(p);\n"
|
||||
" if (s.len: i32 != 6) { return 1; };\n"
|
||||
" if (s.cap: i32 != 8) { return 2; };\n"
|
||||
" if (s[0]: i32 != 19) { return 3; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wdsa_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wdsa_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wdsa_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "dot_slice_arg: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"dot_slice_arg[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"dot_slice_arg: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dot_slice_arg: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* 692_dot_str_chained_arg — pass `<expr>.strfield` as a call argument.
|
||||
*
|
||||
* Task #30: selfhost's `nodeisstr` surface check used `dotinnerstructptr`
|
||||
* to resolve the base of a chained N_DOT, which only walked *struct
|
||||
* fields — value-struct hops fell through. The call-arg path then
|
||||
* pushed only 1 word (`.ptr`) instead of 2 (`.ptr`+`.len`), silently
|
||||
* dropping the str length at the call site.
|
||||
*
|
||||
* Cstage was unaffected because its `node_isstr` reads `n->type` from
|
||||
* the checker rather than re-walking the AST. The bug only bit when
|
||||
* the wwstage was the driver — surfaced via task #29's parallel
|
||||
* slice-field fix when a chained-str test row failed only on wwstage.
|
||||
*
|
||||
* Each fixture passes a str header through a call and reads `.len`
|
||||
* on the callee side to prove both header words round-trip. Rows
|
||||
* cover:
|
||||
* - chained `*T`-rooted through value-struct hop (`p.inner.s`)
|
||||
* — the failing shape filed as #30
|
||||
* - single dot through `*T` root (`p.s`)
|
||||
* — pre-existing path; baseline / negative control
|
||||
* - chained local-rooted value-struct (`o.inner.s`)
|
||||
* — same surface gap as #30 but rooted at a local rather
|
||||
* than a `*T` param
|
||||
*
|
||||
* Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Baseline: single dot through `*T` root. Pre-existing path —
|
||||
* `nodeisstr` resolves the base local's `*T` tnode directly,
|
||||
* no dot-chain walker involved. Pinned so a future refactor
|
||||
* of either branch can't regress it silently. */
|
||||
{ "ptr_root_str_arg",
|
||||
"type outer = struct { s: str, x: i32 };\n"
|
||||
"fn slen(s: str) i32 = { return s.len: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.s = \"hello\";\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" if (slen(p.s) != 5) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* #30 repro: chained through `*T` root with a value-struct
|
||||
* hop. Without the fix, the wwstage pushed only `.ptr` —
|
||||
* the called function then read whatever was in BX from the
|
||||
* prior expression as the length. */
|
||||
{ "ptr_root_chained_str_arg",
|
||||
"type inner = struct { s: str, pad: i32 };\n"
|
||||
"type outer = struct { i: inner, tag: i32 };\n"
|
||||
"fn slen(s: str) i32 = { return s.len: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.s = \"abcdef\";\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" if (slen(p.i.s) != 6) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Local value-struct rooted, chained value-struct hop —
|
||||
* same surface gap as the #30 repro above, but rooted on a
|
||||
* local rather than a `*T` param. dotchainresolve walks
|
||||
* both shapes; both go through the same fallback in
|
||||
* `nodeisstr`. */
|
||||
{ "local_root_chained_str_arg",
|
||||
"type inner = struct { s: str, pad: i32 };\n"
|
||||
"type outer = struct { i: inner, tag: i32 };\n"
|
||||
"fn slen(s: str) i32 = { return s.len: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer; o.i.s = \"qrstuvw\";\n"
|
||||
" if (slen(o.i.s) != 7) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wdsc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wdsc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wdsc_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "dot_str_chained_arg: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"dot_str_chained_arg[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"dot_str_chained_arg: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dot_str_chained_arg: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
/*
|
||||
* 693_dot_tagged_source — `s.f` where `f` is a tagged-union field
|
||||
* (read-side counterpart of 681's tagged-field write rows). Both stages
|
||||
* previously misread the SysV classification of the source slot:
|
||||
*
|
||||
* cstage's direct-struct / global TY_STRUCT N_DOT branch loaded only
|
||||
* AX (+0=tag), DX (+8=val0), and CX (+16=val1 when size > 16). The
|
||||
* R8 (+24=val2) word was never read, so slice-payload variants (slot
|
||||
* 32B) consumed garbage in the high word.
|
||||
*
|
||||
* cstage's TY_PTR N_DOT branch (via_ptr) had no TY_TAGGED handler at
|
||||
* all — fell through to fldloadop, loading only the tag into AX. The
|
||||
* N_DOT scrutinee spill path in N_MATCH likewise stored only AX.
|
||||
*
|
||||
* wwstage's cgdot had no TY_TAGGED handler in the direct, via_ptr,
|
||||
* or top-level-global branches. cgmatch's non-ident-scrutinee path
|
||||
* never recognised N_DOT, so the dispatch always computed
|
||||
* want = 0 and the spill scratch was hardcoded 24B (overflowing for
|
||||
* slice-payload variants). rhstaggedabicall didn't accept N_DOT, so
|
||||
* `let copy: ev = h.e;` and call-arg pushes of `h.e` fell into the
|
||||
* scalar/struct/str/slice branches.
|
||||
*
|
||||
* Closed in task #28 by:
|
||||
* - cstage cgen.c: extending the direct-struct tagged branch to also
|
||||
* load R8 for size > 24, adding a TY_TAGGED handler in the via_ptr
|
||||
* branch, and extending the N_DOT scrutinee spill in N_MATCH to
|
||||
* write DX/CX/R8 alongside AX.
|
||||
* - wwstage cgenutil.ww: new helpers dotfieldtnode + cgloadtaggedfield,
|
||||
* and a new N_DOT branch in rhstaggedabicall.
|
||||
* - wwstage cgenexpr.ww: TY_TAGGED branches in cgdot for direct
|
||||
* struct local / *struct deref / top-level-global, plus a new
|
||||
* N_DOT branch in cgmatch's non-ident-scrutinee spill with the
|
||||
* scratch sized by the resolved scrutinee type.
|
||||
*
|
||||
* Coverage — three variant shapes (16B i64, 24B str, 32B slice) read
|
||||
* from a direct local struct, a *struct param, and a top-level global,
|
||||
* plus a let-init round-trip that exercises rhstaggedabicall's N_DOT
|
||||
* acceptance. The non-tagged str/slice/scalar rows pin that the new
|
||||
* TY_TAGGED branch doesn't shadow the existing field-load paths.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 16B slot, scalar payload, direct local struct. ev = (i64 | i32)
|
||||
* puts i64 at variant 0 so h.e = 42i64 sets tag = 0 and word0 =
|
||||
* 42. match on h.e in the i64 arm reads the value. Pre-#28
|
||||
* cgdot dropped DX; the match-spill stored a stale DX and the
|
||||
* bind returned garbage. Now returns 42. */
|
||||
{ "local_struct_i64_variant",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 42i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* 24B slot, str payload, direct local struct. ev = (i32 | str)
|
||||
* — str at variant 1. Reading h.e must leave AX=tag, DX=ptr,
|
||||
* CX=len for the match dispatch+bind to copy 16B (ptr,len)
|
||||
* into v. Returns v.len = 5. */
|
||||
{ "local_struct_str_variant",
|
||||
"type ev = (i32 | str);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (\"hello\": ev);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let s: str => { return s.len: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* 32B slot, slice payload, direct local struct. ev = (i32 | []u8)
|
||||
* — slice at variant 1, slot 8 + 24 = 32B. Reading h.e must
|
||||
* leave AX=tag, DX=ptr, CX=len, R8=cap; pre-#28 R8 was never
|
||||
* loaded. The match scratch must also be sized to 32B, or the
|
||||
* R8 spill writes past the allocated slot. The arm verifies
|
||||
* v.cap (which traces back through R8/spill+24) — testing only
|
||||
* v.len wouldn't notice an R8 drop because v.len comes from CX
|
||||
* which was already loaded pre-#28. */
|
||||
{ "local_struct_slice_variant",
|
||||
"type ev = (i32 | []u8);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 1u8; raw[1] = 2u8; raw[2] = 3u8;\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (raw[0:3]: ev);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: []u8 => {\n"
|
||||
" if (v.cap != 8) { return 1; };\n"
|
||||
" return v.len: i32;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
3 },
|
||||
/* *struct base, 16B slot, scalar payload — exercises the via_ptr
|
||||
* arm of cstage's TY_PTR N_DOT branch (newly added in #28) and
|
||||
* the wwstage TPTR cgdot branch's tagged handler. The match
|
||||
* spill path through cgmatch's N_DOT fallback also fires here
|
||||
* (h: *holder makes `match (h.e)` not an ident scrutinee). */
|
||||
{ "via_ptr_i64_variant",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn f(h: *holder) i32 = {\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 42i64;\n"
|
||||
" return f(&h);\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* *struct base, 24B slot, str payload — verifies CX is loaded for
|
||||
* the via_ptr path. */
|
||||
{ "via_ptr_str_variant",
|
||||
"type ev = (i32 | str);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn f(h: *holder) i32 = {\n"
|
||||
" match (h.e) {\n"
|
||||
" case let s: str => { return s.len: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (\"hello\": ev);\n"
|
||||
" return f(&h);\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* *struct base, 32B slot, slice payload — exercises R8 (cap) load
|
||||
* in cstage's via_ptr TY_TAGGED branch and the wwstage cgdot
|
||||
* via_ptr cgloadtaggedfield slice case. The cstage N_DOT-scrutinee
|
||||
* spill in N_MATCH (the inner else when bu->kind != TY_STRUCT)
|
||||
* also has to store R8 to spill+24; without it v.cap reads stack
|
||||
* garbage. v.cap is verified in the arm — testing only v.len would
|
||||
* mask an R8 drop. */
|
||||
{ "via_ptr_slice_variant",
|
||||
"type ev = (i32 | []u8);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn f(h: *holder) i32 = {\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: []u8 => {\n"
|
||||
" if (v.cap != 8) { return 1; };\n"
|
||||
" return v.len: i32;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 1u8; raw[1] = 2u8; raw[2] = 3u8;\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (raw[0:3]: ev);\n"
|
||||
" return f(&h);\n"
|
||||
"};\n",
|
||||
3 },
|
||||
/* let-init round-trip, 16B slot — `let copy = h.e` exercises the
|
||||
* cgwidentaggedstore tagged-source path. Without rhstaggedabicall
|
||||
* recognising N_DOT, src would have fallen into the scalar
|
||||
* branch and only AX (the tag) would have been spilled into
|
||||
* copy's slot. Variant order matters for the write side too —
|
||||
* (i64 | i32) makes i64 = variant 0 so the write side picks the
|
||||
* right tag for the literal i64. */
|
||||
{ "letinit_roundtrip",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 42i64;\n"
|
||||
" let copy: ev = h.e;\n"
|
||||
" match (copy) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* let-init round-trip, 32B slice slot — the only test row that
|
||||
* actually fires cstage's direct-local cgdot TY_TAGGED branch on
|
||||
* the 32B path (rows 1-3's `match (h.e)` is bypassed by cstage's
|
||||
* N_DOT-in-N_IDENT specialization, which reads tag/value directly
|
||||
* from h's stack slot without invoking cgdot). The let-init forces
|
||||
* cgexpr → cgdot → AX/DX/CX/R8 register shape; cgwidentaggedstore
|
||||
* then spills all four into copy's slot. Without the R8 load in
|
||||
* cgdot, copy.cap is whatever R8 held going in. */
|
||||
{ "letinit_slice_roundtrip",
|
||||
"type ev = (i32 | []u8);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 1u8; raw[1] = 2u8; raw[2] = 3u8;\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (raw[0:3]: ev);\n"
|
||||
" let copy: ev = h.e;\n"
|
||||
" match (copy) {\n"
|
||||
" case let v: []u8 => {\n"
|
||||
" if (v.cap != 8) { return 1; };\n"
|
||||
" return v.len: i32;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
3 },
|
||||
/* Top-level global with slice-variant tagged field — exercises
|
||||
* wwstage cgdot's third new TY_TAGGED branch (top-level global,
|
||||
* base reg = CX via LEAQ name(SB)) and cstage's direct-struct
|
||||
* tagged branch with base_reg = CX (the same R8 load with global
|
||||
* rooting). The let-init copy fires cgdot on g.e in expression
|
||||
* context — `match (g.e)` directly doesn't address globals in
|
||||
* cstage's N_DOT-in-N_IDENT match specialization. The write side
|
||||
* routes through `*p` because the direct global LHS path
|
||||
* `g.e = (slice: ev)` is a separate pre-existing wwstage gap
|
||||
* (filed as a follow-up); isolating the read keeps #28's coverage
|
||||
* intent clean. */
|
||||
{ "top_level_global_slice",
|
||||
"type ev = (i32 | []u8);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"let g: holder;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 1u8; raw[1] = 2u8; raw[2] = 3u8;\n"
|
||||
" let p: *holder = &g;\n"
|
||||
" p.mark = 99;\n"
|
||||
" p.e = (raw[0:3]: ev);\n"
|
||||
" let copy: ev = g.e;\n"
|
||||
" match (copy) {\n"
|
||||
" case let v: []u8 => {\n"
|
||||
" if (v.cap != 8) { return 1; };\n"
|
||||
" return v.len: i32;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
3 },
|
||||
/* Negative control: untagged scalar struct field — pin that the
|
||||
* new TY_TAGGED branch doesn't fire on plain i32 reads (would
|
||||
* pollute DX/CX with random struct bytes). Mirror of the
|
||||
* scalar-field regression rows from earlier read-side tasks. */
|
||||
{ "untagged_i32_field_regression",
|
||||
"type holder = struct { a: i32, b: i32, c: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.a = 10; h.b = 20; h.c = 12;\n"
|
||||
" return h.a + h.b + h.c;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Negative control: str field, direct local — pin that the
|
||||
* str-field branch still fires (it sits below the new tagged
|
||||
* branch in the field-walk; an over-broad istaggedtype guard
|
||||
* could mask it). Reads len through the existing str-rhs
|
||||
* convention. */
|
||||
{ "untagged_str_field_regression",
|
||||
"type holder = struct { s: str, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.s = \"hello\";\n"
|
||||
" return h.s.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* Negative control: slice field via *struct — verifies the new
|
||||
* tagged via_ptr branch doesn't intercept slice-field reads,
|
||||
* which already have a dedicated load path. */
|
||||
{ "untagged_slice_field_via_ptr_regression",
|
||||
"type holder = struct { rbuf: []u8, mark: i32 };\n"
|
||||
"fn f(h: *holder) i32 = { return h.rbuf.len: i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 0u8;\n"
|
||||
" let h: holder;\n"
|
||||
" h.rbuf = raw[0:5];\n"
|
||||
" return f(&h);\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/waew_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/waew_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "dot_tagged_source: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"dot_tagged_source[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"dot_tagged_source: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dot_tagged_source: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
/*
|
||||
* 694_tagged_store_intlit — typed-int literal assigned into a tagged-
|
||||
* union slot must select the variant index matching the literal's
|
||||
* type suffix, not "first non-str variant". Read-side counterpart is
|
||||
* 693_dot_tagged_source (covers the AX/DX/CX/R8 ABI on reads); this
|
||||
* test is the symmetric write-side.
|
||||
*
|
||||
* cstage parser stamps N_INTLIT.tsuffix from the lexer (parse.c
|
||||
* parseprimary TK_INT), then the checker stamps node.type = ty_i64
|
||||
* for `42i64`. cg_widen_tagged_store calls cg_tag_for_variant(du,
|
||||
* src->type), which walks variants and matches structurally via
|
||||
* type_eq(ty_i64, vt) — exact, picking the i64 slot.
|
||||
*
|
||||
* wwstage parser dropped tsuffix on the floor (parseprimary in
|
||||
* lib/ww/parse/expr.ww never copied curtsuffix → node), and there is
|
||||
* no separate checker stage to re-stamp. cgwidentaggedstore →
|
||||
* taggedvariantindex → rhstargetname inspected node.tsuffix and
|
||||
* always saw "", then fell through to "first variant whose
|
||||
* isstr matches the rhs". For a numeric rhs in (i32 | i64) that's
|
||||
* always tag 0 (i32). The slot got the i32 tag but stored 8 bytes
|
||||
* of value, so a match arm on i64 was bypassed entirely and the i32
|
||||
* arm bound a truncated value.
|
||||
*
|
||||
* `-42i64` (N_UN MINUS over N_INTLIT) is an additional gap: even
|
||||
* after plumbing tsuffix, rhstargetname returned "" for N_UN because
|
||||
* it didn't peel sign/bitwise-not unary operators. cstage's checker
|
||||
* stamps N_UN's type from cunop's inner walk so the inner i64
|
||||
* propagates naturally; wwstage needs an explicit peel to match.
|
||||
*
|
||||
* Closed in task #10 by:
|
||||
* - lib/ww/parse/parse.ww + selfhost/cmd/{w6c,wwdump}/main.combined.ww:
|
||||
* parser struct gains curtsuffix; refill copies t.tsuffix into it.
|
||||
* - lib/ww/parse/expr.ww + the combined mirrors: parseprimary TK_INT
|
||||
* / TK_FLOAT branches copy curtsuffix into the new node before
|
||||
* advance, mirroring cmd/wcc/parse.c parseprimary.
|
||||
* - selfhost/cmd/wcc/cgenutil.ww + the combined mirrors: rhstargetname
|
||||
* peels N_UN of TK_MINUS / TK_PLUS / TK_TILDE and recurses, mirroring
|
||||
* cstage's cunop returning the inner type for these ops.
|
||||
*
|
||||
* Coverage — typed literals across variant orderings ((i32|i64),
|
||||
* (i32|i64|str) for tail-position str fallback, (u8|i32|i64) for
|
||||
* head- and tail-position numerics), a negated i64 literal to exercise
|
||||
* the N_UN peel plus sign extension through the match-arm bind, and a
|
||||
* direct `let x: (i32|i64) = 42i64;` to fire cglet's tagged-init
|
||||
* (separate write-site from the field-assign path). One negative
|
||||
* control pins the str-fallback path so the fix doesn't shadow it.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* (i32 | i64) with `42i64` rhs — tag must be 1, value 42. Pre-fix
|
||||
* wwstage wrote tag = 0 and the i64 arm was bypassed; the i32
|
||||
* arm bound the low 32 bits and returned that. */
|
||||
{ "field_i64_in_i32_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 42i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* (i32 | i64) with `42i32` rhs — tag must be 0. Pins that the
|
||||
* fix doesn't flip the existing-working i32 case. */
|
||||
{ "field_i32_in_i32_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 42i32;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return -1; };\n"
|
||||
" case let z: i32 => { return z; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* (i32 | i64 | str) — same i64 lit, but the variant list now has
|
||||
* a trailing str. Without tsuffix plumbing the fallback also has
|
||||
* to skip past i32 (visstr == wantstr=false matches i32 first);
|
||||
* with tsuffix it goes straight to tag 1. */
|
||||
{ "field_i64_in_i32_i64_str",
|
||||
"type ev = (i32 | i64 | str);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 1234567890i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return (v - 1234567848i64): i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" case let s: str => { return -2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* (u8 | i32 | i64) — typed lit at head of variant list (u8). Pre-
|
||||
* fix the fallback picked tag 0 (u8, the first numeric) by accident
|
||||
* regardless of the rhs suffix; this row would coincidentally pass
|
||||
* for u8 but mask the bug. We verify u8 binds the right value AND
|
||||
* the high 56 bits weren't smeared from the 8B store. */
|
||||
{ "field_u8_in_u8_i32_i64",
|
||||
"type ev = (u8 | i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 7u8;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let b: u8 => { return b: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" case let v: i64 => { return -2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* (u8 | i32 | i64) with i64 lit — tag must be 2 (last). Pre-fix
|
||||
* fell through to tag 0 (u8); the u8 arm bound the low byte (42)
|
||||
* and the test would return 42 for the WRONG reason. We bind in
|
||||
* the i64 arm and add 100 to distinguish: 42+100 = 142 only if
|
||||
* the i64 arm fired. The u8 arm returns -1. */
|
||||
{ "field_i64_in_u8_i32_i64",
|
||||
"type ev = (u8 | i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = 42i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let b: u8 => { return -1; };\n"
|
||||
" case let z: i32 => { return -2; };\n"
|
||||
" case let v: i64 => { return (v + 100i64): i32; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
142 },
|
||||
/* Negative-int literal in (i32 | i64). `-42i64` parses as N_UN
|
||||
* MINUS over N_INTLIT(42, tsuffix="i64"). The N_UN peel in
|
||||
* rhstargetname routes through to "i64" so the i64 variant is
|
||||
* selected; the value side must store full 8B so the bind reads
|
||||
* back -42 (not 0xFFFFFFFFFFFFFFD6 truncated to 0xFFFFFFD6 or
|
||||
* any other partial-extend nonsense). Returns 42 only if the
|
||||
* i64 arm fires AND v == -42i64. */
|
||||
{ "field_negative_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = -42i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => {\n"
|
||||
" if (v != -42i64) { return 50; };\n"
|
||||
" return (0i64 - v): i32;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Unary `+` over a typed-i64 literal. Parses as N_UN(PLUS,
|
||||
* N_INTLIT(42, "i64")); cstage cunop returns the inner type so
|
||||
* `+42i64: i64`. Wwstage's rhstargetname peel covers TK_PLUS
|
||||
* alongside TK_MINUS — pin it. Without the peel rhstargetname
|
||||
* stops at N_UN and the i32 fallback fires; this row would
|
||||
* return -1 pre-fix. */
|
||||
{ "field_plus_unary_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = +42i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Unary `~` (bitwise not) over a typed-i64 literal. Parses as
|
||||
* N_UN(TILDE, N_INTLIT(0, "i64")); cstage cunop returns the
|
||||
* inner type for TK_TILDE so `~0i64: i64`. Mirrors the peel's
|
||||
* third op. `~0i64 == -1i64`; we bind in the i64 arm and check
|
||||
* the value to rule out a half-store. */
|
||||
{ "field_tilde_unary_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = ~0i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => {\n"
|
||||
" if (v != -1i64) { return 50; };\n"
|
||||
" return 42;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Nested unary `- -42i64`. Parses as N_UN(MINUS, N_UN(MINUS,
|
||||
* N_INTLIT(42, "i64"))) since parseunary recurses on the
|
||||
* operand. The peel must recurse too; without recursion the
|
||||
* outer N_UN's lhs is another N_UN and rhstargetname falls
|
||||
* through to "". Numerically the value is 42, so a wrong
|
||||
* variant binding can only be detected via the arm we hit. */
|
||||
{ "field_nested_unary_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = - -42i64;\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Direct `let x: ev = 42i64;` — different write site than the
|
||||
* struct-field assign (cglet's tagged-init branch in cgenstmt.ww,
|
||||
* not cgassign's field-write branch in cgenexpr.ww). Both routes
|
||||
* eventually call cgwidentaggedstore so taggedvariantindex must
|
||||
* agree; this row pins the let-init path. */
|
||||
{ "letinit_i64_in_i32_i64",
|
||||
"type ev = (i32 | i64);\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: ev = 42i64;\n"
|
||||
" match (x) {\n"
|
||||
" case let v: i64 => { return v: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Negative control: untagged str field still resolves through the
|
||||
* isstr fallback. The new N_UN peel in rhstargetname must not
|
||||
* shadow this path. Reads h.s.len. */
|
||||
{ "untagged_str_fallback_regression",
|
||||
"type ev = (i32 | str);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (\"hello\": ev);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let s: str => { return s.len: i32; };\n"
|
||||
" case let z: i32 => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/waew_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/waew_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tagged_store_intlit: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"tagged_store_intlit[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tagged_store_intlit: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_store_intlit: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* 695_match_bind_struct — `case let v: T => ...` where T is a TY_STRUCT
|
||||
* variant of a tagged-union scrutinee. wwstage's cgmatch (and the
|
||||
* matching scanlocals frame pre-scan) hardcoded the bind-slot size to
|
||||
*
|
||||
* str -> 16
|
||||
* []T -> 24
|
||||
* else -> 8
|
||||
*
|
||||
* so a struct variant collapsed to a single 8B word: only the first
|
||||
* quadword reached the per-arm bind slot, and (worse) the prologue's
|
||||
* SUBQ underbooked the frame, so localalloc at emit time then ran the
|
||||
* SP cursor off the bottom of the function's frame and the bind's
|
||||
* subsequent stores smashed whatever was past it.
|
||||
*
|
||||
* cstage cgen.c is fine: the else-branch falls through to
|
||||
* `bsz = (int)bu->size` and the SP cursor / cg_frame counter both
|
||||
* ride the same `bu->size`. Worker-28 noted this while landing #28,
|
||||
* and a reread of cstage's cgmatch (cgen.c ~3998-4017) and stack-
|
||||
* frame setup confirms — no cstage mirror needed.
|
||||
*
|
||||
* Closed in task #31 by:
|
||||
* - selfhost/cmd/wcc/cgenexpr.ww: cgmatch bsz table replaced with
|
||||
* `slotsize(c, pat)`. slotsize already covers N_TNAME named struct
|
||||
* (returns si.totsize), str (16), []T (24), aliases, tuples, and
|
||||
* primitives (8). Defensive nwords = (bsz + 7) / 8 mirrors cstage.
|
||||
* - selfhost/cmd/wcc/cgendecl.ww: scanlocals N_MCASE pre-scan
|
||||
* mirrored — must agree with cgmatch's bind-slot size so the
|
||||
* prologue SUBQ reserves enough frame.
|
||||
* - selfhost/cmd/{w6c,wwdump}/main.combined.ww: combined-file mirrors
|
||||
* of both sites kept in lockstep.
|
||||
*
|
||||
* Coverage — seven rows: four structurally distinct TY_STRUCT shapes
|
||||
* plus three negative controls. The 3xi64 headline repro, a 4-i64
|
||||
* struct via let-init scrutinee (exercise > 24B payload, so the
|
||||
* historical 24-cap doesn't quietly pass), a mixed-quadword struct
|
||||
* with i32 alignment + i64 tail (size still pads to a multiple of 8),
|
||||
* str/slice/i32 negative controls (the existing sized paths must not
|
||||
* regress), and a direct `let x: tag = (pair{...}: tag);` scrutinee
|
||||
* (different from `match (h.e)` — exercises cglet's tagged-init
|
||||
* route to the same bind site).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Headline repro. 3xi64 = 24B struct as a tagged-union variant.
|
||||
* Bind copies all three quadwords; sum to 31. Pre-fix only v.x
|
||||
* reached the bind and v.y/v.z were uninitialized stack — exit
|
||||
* was nondeterministic but never 0. */
|
||||
{ "pair_3xi64",
|
||||
"type pair = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"type tag = (pair | i32);\n"
|
||||
"type holder = struct { e: tag, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" let p: pair = pair { x = 7i64, y = 11i64, z = 13i64 };\n"
|
||||
" h.e = (p: tag);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: pair => {\n"
|
||||
" let s: i64 = v.x + v.y + v.z;\n"
|
||||
" if (s != 31i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* 4xi64 = 32B struct payload, exercising a >24B bind copy via the
|
||||
* direct-let-init scrutinee shape (`match (x)` over an ident,
|
||||
* bypassing the N_DOT spill path). Hardcoding a 24B cap (an
|
||||
* obvious fix-sketch hack — "if struct then 24") would drop
|
||||
* v.d and the sum would miss 24, returning 1 not 0.
|
||||
*
|
||||
* The N_DOT route through `match (h.t)` for a 32B-payload variant
|
||||
* is a separate, unrelated gap: cgmatch's scrut spill only writes
|
||||
* AX/DX/CX/R8 (4 registers, one of which is the tag), so the 4th
|
||||
* payload quadword is dropped at spill time — not at bind time.
|
||||
* Cstage sidesteps this with a direct-field addressing special-
|
||||
* case in cgmatch (cgen.c ~3858-3876) that skips the spill
|
||||
* entirely; wwstage lacks that path. Filing that as a follow-up;
|
||||
* the let-init scrutinee already exercises the bind size for
|
||||
* payloads > 24B without entangling the two bugs. */
|
||||
{ "wide_4xi64_letinit",
|
||||
"type wide = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
||||
"type tag = (wide | i32);\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: tag = (wide { a = 1i64, b = 2i64, c = 4i64,\n"
|
||||
" d = 24i64 }: tag);\n"
|
||||
" match (x) {\n"
|
||||
" case let v: wide => {\n"
|
||||
" let s: i64 = v.a + v.b + v.c + v.d;\n"
|
||||
" if (s != 31i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Mixed-quadword: i32+i32+i64+i64. registerstruct packs the two
|
||||
* i32s into one quadword (x@0, y@4, z@8, w@16), totsize = 24
|
||||
* (after the trailing pad-to-8). Bind must copy all three
|
||||
* quadwords; reading w must see the right value. */
|
||||
{ "mixed_i32_i32_i64_i64",
|
||||
"type mix = struct { x: i32, y: i32, z: i64, w: i64 };\n"
|
||||
"type tag = (mix | i32);\n"
|
||||
"type holder = struct { t: tag, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" let m: mix = mix { x = 3, y = 5, z = 11i64, w = 12i64 };\n"
|
||||
" h.t = (m: tag);\n"
|
||||
" match (h.t) {\n"
|
||||
" case let v: mix => {\n"
|
||||
" if (v.x != 3) { return 10; };\n"
|
||||
" if (v.y != 5) { return 11; };\n"
|
||||
" if (v.z != 11i64) { return 12; };\n"
|
||||
" if (v.w != 12i64) { return 13; };\n"
|
||||
" return 0;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Negative control: str variant in (str | pair). Pins that the
|
||||
* fix didn't shadow the existing 16B str bind. */
|
||||
{ "str_neg_control",
|
||||
"type pair = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"type tag = (str | pair);\n"
|
||||
"type holder = struct { e: tag, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (\"hello\": tag);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let s: str => { return s.len: i32; };\n"
|
||||
" case let v: pair => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* Negative control: []u8 variant in ([]u8 | pair). Pins 24B slice
|
||||
* bind. */
|
||||
{ "slice_neg_control",
|
||||
"type pair = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"type tag = ([]u8 | pair);\n"
|
||||
"type holder = struct { e: tag, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let raw: [8]u8;\n"
|
||||
" raw[0] = 1u8; raw[1] = 2u8; raw[2] = 3u8;\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (raw[0:3]: tag);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: []u8 => {\n"
|
||||
" if (v.cap != 8) { return 20; };\n"
|
||||
" return v.len: i32;\n"
|
||||
" };\n"
|
||||
" case let p: pair => { return -1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
3 },
|
||||
/* Negative control: primitive i32 variant — pins the 8B fall-
|
||||
* through. */
|
||||
{ "i32_neg_control",
|
||||
"type pair = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"type tag = (pair | i32);\n"
|
||||
"type holder = struct { e: tag, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let h: holder;\n"
|
||||
" h.mark = 99;\n"
|
||||
" h.e = (42i32: tag);\n"
|
||||
" match (h.e) {\n"
|
||||
" case let v: pair => { return -1; };\n"
|
||||
" case let z: i32 => { return z; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Direct `let x: tag = (struct: tag);` scrutinee — different
|
||||
* write site than the holder-field assign route, but the bind
|
||||
* read-side hits the same cgmatch table. Pins the let-init
|
||||
* shape. */
|
||||
{ "letinit_direct_scrutinee",
|
||||
"type pair = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"type tag = (pair | i32);\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: tag = (pair { x = 7i64, y = 11i64, z = 13i64 }: tag);\n"
|
||||
" match (x) {\n"
|
||||
" case let v: pair => {\n"
|
||||
" let s: i64 = v.x + v.y + v.z;\n"
|
||||
" if (s != 31i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
" };\n"
|
||||
" case let z: i32 => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wmbs_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wmbs_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wmbs_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "match_bind_struct: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"match_bind_struct[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"match_bind_struct: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("match_bind_struct: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
/*
|
||||
* 709_localoff_scope — cgen localoff/localadd no longer dedups stack
|
||||
* slots by name across disjoint scopes (task #27).
|
||||
*
|
||||
* Pre-fix: cstage `localoff` and wwstage `localadd` both keyed slot
|
||||
* lookup on name alone. Two `let a: T` in disjoint scopes within one
|
||||
* fn collapsed to a single slot, sized by whoever was allocated first.
|
||||
* The smaller of the two then ran with an offset that, when used at
|
||||
* its declared size, walked past the SUBQ'd frame and into the saved
|
||||
* frame zero / saved RIP / caller stack. Silent stack corruption.
|
||||
*
|
||||
* The bug fired in either direction:
|
||||
* - inner small allocated FIRST, outer big allocated SECOND →
|
||||
* outer's writes near the end of its declared size land at
|
||||
* positive BP offsets (past the saved RIP) → SIGSEGV.
|
||||
* - outer big allocated FIRST, inner small allocated SECOND →
|
||||
* inner's full-size store stomps the outer's first bytes.
|
||||
*
|
||||
* worker-19's commit (c9bbfcb) sidestepped one instance in selfhost/
|
||||
* cmd/w6a/main.ww by renaming an outer `let asm: asm_;` to `s` so the
|
||||
* inner `let a: *u8 = ...;` (in the for-loop) wouldn't share a slot.
|
||||
* That rename can be reverted once this fix lands (sibling cleanup).
|
||||
*
|
||||
* Fix: drop the dedup. Every `let` allocates a fresh slot (cstage
|
||||
* localoff / wwstage localadd). localfind walks head-first, so the
|
||||
* most-recent (innermost) binding still wins lookups inside its
|
||||
* scope. Wwstage scanlocals stops deduping user-let names in lockstep
|
||||
* so the prologue SUBQ matches the emit-time offsets. Synthetic
|
||||
* scratch slots (`@tagscr`, `@retscr`, `@tagbase`) keep the per-fn
|
||||
* dedup via an `@`-prefix carve-out — they're sized identically at
|
||||
* every call site and intended to be shared.
|
||||
*
|
||||
* row | shape | gate
|
||||
* --------------------------+------------------------------------+---------
|
||||
* inner_first_outer_bigger | 8B inner, then 128B outer; write | exit=99
|
||||
* | a[127]=99 in outer scope. Pre-fix |
|
||||
* | SEGV; post-fix slot is 128B. |
|
||||
* outer_first_inner_writes | 64B outer first, nested 8B inner | exit=7
|
||||
* | writes -1; outer's a[0] still the |
|
||||
* | original 7. |
|
||||
* nested_3_deep | same name `x` at three nesting | exit=6
|
||||
* | depths, returns the sum of values | (1+2+3)
|
||||
* | read at each scope. |
|
||||
* same_name_diff_type | `let a: i32 = 5;` then disjoint | exit=2
|
||||
* | block `let a: str = "hi";`. Returns|
|
||||
* | a.len from the str scope. |
|
||||
* (removed) | `let a: i32 = 1; let a: i32 = 9;` | --
|
||||
* | post-#32 the checker rejects this; |
|
||||
* | covered by 712_redecl's |
|
||||
* | neg_let_same_block row. |
|
||||
* defer_shadow | outer `a`, deferred call captures | exit=42
|
||||
* | &outer-a, inner-block shadow `a`, |
|
||||
* | return outer a. Pins both: cgfn |
|
||||
* | body-bypass (defer's cgexpr after |
|
||||
* | body iteration must still resolve |
|
||||
* | outer `a`) and inner-block |
|
||||
* | save/restore (inner shadow can't |
|
||||
* | leak past `}`). |
|
||||
* forrange_body_shadow | for (let x .. s) iterates [1,2,3], | exit=47
|
||||
* | body reads x then declares `let | (1+2+3)
|
||||
* | x: i32 = 99;` then reads x again. | +99-3*8
|
||||
* | First read = iter, second = inner. | =6+47-24
|
||||
* | Pins that the iter slot's per-iter |
|
||||
* | load (cgforrange cached offset) is |
|
||||
* | unaffected by the body's shadow, |
|
||||
* | AND that the body's pre-shadow |
|
||||
* | reads still find the iter. |
|
||||
* if_body_shadow | outer `a=7`, then `if (..) | exit=7
|
||||
* | { let a=99; }`, return a. Pins the |
|
||||
* | if-arm's N_BLOCK save/restore pops |
|
||||
* | cleanly so the return reads outer. |
|
||||
*
|
||||
* Per-row exit-code agreement across cstage + wwstage drivers is the
|
||||
* contract this test pins. Asm byte-identity is NOT diffed here: no
|
||||
* wwstage divergence specific to this fix; 995_self_rebuild covers
|
||||
* cross-stage drift broadly.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. Inner allocated first (in for-loop body), outer allocated
|
||||
* second and bigger. Pre-fix: outer dedups onto inner's 8B slot
|
||||
* at -8(BP); a[127] = 99 emits MOVB AX, 119(BP) — past saved
|
||||
* RIP — SEGV. Post-fix: outer gets its own 128B slot, write
|
||||
* lands inside the slot, exit = 99. */
|
||||
{ "inner_first_outer_bigger",
|
||||
"fn main() i32 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < 3) { let a: i64 = 5i64; i += 1; };\n"
|
||||
" let a: [128]u8 = [0u8...];\n"
|
||||
" a[127] = 99u8;\n"
|
||||
" return a[127]: i32;\n"
|
||||
"};\n",
|
||||
99 },
|
||||
/* 2. Outer allocated first (64B), inner inside a nested block
|
||||
* writes an 8B i64. Pre-fix: inner's full 8B store overwrites
|
||||
* outer's bytes 0..7; outer's a[0] read after the inner block
|
||||
* returns the low byte of -1 (= 0xff = 255). Post-fix: inner
|
||||
* gets its own slot, outer's a[0] keeps the value 7 written
|
||||
* before the inner block. */
|
||||
{ "outer_first_inner_writes",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [64]u8 = [0u8...];\n"
|
||||
" a[0] = 7u8;\n"
|
||||
" {\n"
|
||||
" let a: i64 = 0i64 - 1i64;\n"
|
||||
" if (a == 0i64) { return 99i32; };\n"
|
||||
" };\n"
|
||||
" return a[0]: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* 3. Same name `x` redeclared at three nesting depths, each with
|
||||
* a distinct value. Inside each scope, x reads its own binding
|
||||
* (head-first localfind). The inner reads happen WHILE the
|
||||
* outer slots are still live, so the test catches both
|
||||
* cross-scope slot collision (corrupt outer) and miss-up-the-
|
||||
* chain lookup (returns wrong inner value). Sum is 1 + 2 + 3 = 6. */
|
||||
{ "nested_3_deep",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = 1;\n"
|
||||
" let s1: i32 = x;\n"
|
||||
" let s2: i32 = 0;\n"
|
||||
" let s3: i32 = 0;\n"
|
||||
" {\n"
|
||||
" let x: i32 = 2;\n"
|
||||
" s2 = x;\n"
|
||||
" {\n"
|
||||
" let x: i32 = 3;\n"
|
||||
" s3 = x;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" return s1 + s2 + s3;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
/* 4. Same name, different types in disjoint scopes. Outer
|
||||
* `let a: i32 = 5;` (8B slot, scalar), inner `let a: str = "hi"`
|
||||
* (16B slot, ptr+len). Pre-fix: inner reuses outer's 8B slot
|
||||
* and the str ptr/len writes land at -8/0(BP) — corrupting
|
||||
* the saved BP. Post-fix: inner gets its own 16B slot.
|
||||
* Returns inner a.len = 2. */
|
||||
{ "same_name_diff_type",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i32 = 5;\n"
|
||||
" let r: i32 = 0;\n"
|
||||
" {\n"
|
||||
" let a: str = \"hi\";\n"
|
||||
" r = a.len: i32;\n"
|
||||
" };\n"
|
||||
" return r;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
/* 5. Same-block re-declaration moved to test/wcc/712_redecl
|
||||
* (`neg_let_same_block`) when #32 made it a build-time error.
|
||||
* The pin's purpose — exercising the localoff fresh-stub path on
|
||||
* a same-name same-block dup — is now an upstream-rejected shape,
|
||||
* so the codegen branch it covered is no longer reachable through
|
||||
* legal source. Row slot kept empty for stability of the
|
||||
* surrounding row numbering. */
|
||||
/* 6. Defer + inner-block shadow + outer-scope post-defer read.
|
||||
*
|
||||
* `defer touch(&a)` queues the call; at fn-exit the defer's
|
||||
* cgexpr runs (resolving `&a`) BEFORE the return value is
|
||||
* loaded. We expect &a to bind to the OUTER a (the one in
|
||||
* scope at fn-exit), so touch sets outer a to 42 and the
|
||||
* return reads 42.
|
||||
*
|
||||
* Three independent invariants gate this row:
|
||||
* (i) cgfn body-bypass: cgstmt(N_BLOCK) on fn->body would
|
||||
* restore c.locals to params-only before the deferred
|
||||
* cgexpr runs. With the bypass cgfn iterates fn.body.list
|
||||
* directly so c.locals stays populated for the defer's
|
||||
* cgexpr — &a resolves to outer a's slot.
|
||||
* (ii) cgblock save/restore: the inner block's `let a: i32 = 99`
|
||||
* prepends a stub to c.locals. Without restore, the inner
|
||||
* stub leaks past `}` and head-first localfind picks it
|
||||
* up; touch then writes to the inner slot and the return
|
||||
* reads outer a → 7, not 42.
|
||||
* (iii) localadd always-fresh: even with save/restore, if
|
||||
* outer & inner share one slot (pre-#27), touch writing
|
||||
* 42 stomps the (deceased) inner slot which IS the outer
|
||||
* — so this gate alone happens to land at 42 either way.
|
||||
* Combined with (ii), exit=42 means BOTH (ii) and (iii)
|
||||
* hold; either regressing flips the gate.
|
||||
*
|
||||
* touch(&a) returns 0 to keep its own scalar exit out of the
|
||||
* way; only the side effect through *p matters. */
|
||||
{ "defer_shadow",
|
||||
"fn touch(p: *i32) i32 = { *p = 42i32; return 0i32; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i32 = 7;\n"
|
||||
" defer touch(&a);\n"
|
||||
" {\n"
|
||||
" let a: i32 = 99;\n"
|
||||
" if (a == 0i32) { return 1i32; };\n"
|
||||
" };\n"
|
||||
" return a;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* 7. Forrange body shadow. Distinct codegen path from N_FOR
|
||||
* (cstage cgforrange / wwstage cgenstmt cgforrange + cgendecl
|
||||
* scanlocals N_FORRANGE arm). The iter binding `x` is added to
|
||||
* c.locals via localadd before the body is emitted; the body
|
||||
* declares its own `let x: i32 = 7;` partway through. Two
|
||||
* observations per iteration:
|
||||
* - first `sum += x` (pre-shadow): finds iter x via head-first
|
||||
* localfind on c.locals at that point (no inner stub yet).
|
||||
* Sums to 10 + 20 + 30 = 60 across iterations.
|
||||
* - second `sum += x` (post-shadow): finds inner x = 7.
|
||||
* Sums to 7 * 3 = 21 across iterations.
|
||||
* Total: 81. The iter slot's per-iter rewrite is driven by
|
||||
* cgforrange via cached offset, NOT by name lookup, so the
|
||||
* shadow can't hijack the iter-write — but a regression in
|
||||
* scanlocals' N_FORRANGE arm (e.g. forgetting to append a stub)
|
||||
* would still surface here as the pre-shadow read failing to
|
||||
* resolve `x`. */
|
||||
{ "forrange_body_shadow",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let sum: i32 = 0;\n"
|
||||
" for (let x .. arr) {\n"
|
||||
" sum += x;\n"
|
||||
" let x: i32 = 7;\n"
|
||||
" sum += x;\n"
|
||||
" };\n"
|
||||
" return sum;\n"
|
||||
"};\n",
|
||||
81 },
|
||||
/* 8. If-body shadow. The if's body is N_BLOCK and reaches
|
||||
* cgblock via cgstmt — distinct visual path from a bare
|
||||
* `{ ... }` at fn-body level (rows 2/3/4). `touched` stashes
|
||||
* the inner-a value so we confirm the if-arm actually executed
|
||||
* before checking the outer-scope visibility. With cgblock
|
||||
* save/restore: return reads outer a = 7. Without: head-first
|
||||
* localfind picks up the inner stub still on the chain → 99. */
|
||||
{ "if_body_shadow",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i32 = 7;\n"
|
||||
" let touched: i32 = 0;\n"
|
||||
" if (a > 0i32) {\n"
|
||||
" let a: i32 = 99;\n"
|
||||
" touched = a;\n"
|
||||
" };\n"
|
||||
" if (touched != 99i32) { return 1i32; };\n"
|
||||
" return a;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wclo_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wclo_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wclo_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "localoff_scope: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"localoff_scope[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"localoff_scope: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("localoff_scope: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
/*
|
||||
* 711_arrlit_str_full — `let xs: [N]str = [...]` writes BOTH halves
|
||||
* of every element (task #21).
|
||||
*
|
||||
* Pre-fix: cgen's N_LET / N_ARRLIT branch dispatched its per-element
|
||||
* store off a single esz / mop pair derived from `lu->sub->size`
|
||||
* (cstage) or `primsize(elem.str)` (wwstage). Both fell through to a
|
||||
* single MOVQ AX, off+i*esz(BP) for a str element — leaving the .len
|
||||
* half (off+i*esz+8) as whatever stack residue the prologue's SUBQ
|
||||
* happened to land on. Wwstage was worse: primsize("str") returns 0,
|
||||
* so esz collapsed to 8, and the per-element stride was wrong too —
|
||||
* element i+1's MOVQ overwrote what should have been element i's
|
||||
* .len half.
|
||||
*
|
||||
* Fix: detect str-element arrays at letslotsize / slotsize / cgen
|
||||
* dispatch and emit both halves per element — MOVQ AX, off+i*16(BP)
|
||||
* for .ptr, MOVQ BX, off+i*16+8(BP) for .len. Symmetric across cstage
|
||||
* cgen.c (N_LET / N_ARRLIT, type_isstr dispatch) and wwstage
|
||||
* cgenstmt.ww + cgenutil.ww (slotsize / letslotsize TNAME-"str"
|
||||
* special-case, cgenstmt isstrel branch). bool / rune / iN element
|
||||
* arrays were never broken — their primitive store widths covered
|
||||
* the full element — but they're pinned here as regressions so a
|
||||
* future esz refactor can't quietly redo the slot-rounding gap.
|
||||
*
|
||||
* What this test pins (runtime only):
|
||||
* - [3]str literal: every element's .len reads back correctly
|
||||
* (pre-fix: zero or stack residue).
|
||||
* - [3]str literal: every element's .ptr → first byte reads back
|
||||
* correctly (the ptr half was always right; sanity).
|
||||
* - [3]bool literal: regression-pin true / false / true.
|
||||
* - [3]rune literal: regression-pin primitive size 4 element.
|
||||
* - [3]i32 / [3]i64 literal: regression-pin primitive widths.
|
||||
* - [5]str = ["x", ...] repeat marker: all 5 slots get full 16B
|
||||
* stores (pre-fix: trailing slots' .len = zero).
|
||||
*
|
||||
* Slice / struct / tuple / tagged element arrays are NOT covered:
|
||||
* the read side (cgindex of an [N]slice) has its own truncating-to-
|
||||
* ptr bug, so an end-to-end repro surfaces sub-issues. Tracked as a
|
||||
* follow-up.
|
||||
*
|
||||
* Asm byte-identity across stages is NOT diffed here: 995_self_rebuild
|
||||
* covers the broader cross-stage drift surface, and the str / repeat
|
||||
* rows produce identical MOVQ pairs across stages by construction.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. [3]str — every .len reads back correctly. Exit code is
|
||||
* 100*a[0].len + 10*a[1].len + a[2].len = 100*2 + 10*6 + 1 = 261
|
||||
* mod 256 = 5. Pre-fix: zero (all .len halves uninit). */
|
||||
{ "str_lens_3el",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]str = [\"hi\", \"byebye\", \"x\"];\n"
|
||||
" let p: i32 = a[0].len: i32;\n"
|
||||
" let q: i32 = a[1].len: i32;\n"
|
||||
" let r: i32 = a[2].len: i32;\n"
|
||||
" return p * 100i32 + q * 10i32 + r;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* 2. [3]str — every .ptr is reachable. Reads element 0's first
|
||||
* byte through `.ptr` to confirm the ptr half wasn't broken by
|
||||
* the .len-half fix. 'h' (104) - 100 = 4. */
|
||||
{ "str_ptrs_3el",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]str = [\"hi\", \"by\", \"x\"];\n"
|
||||
" let p: *u8 = a[0].ptr;\n"
|
||||
" let c: u8 = *p;\n"
|
||||
" return (c: i32) - 100i32;\n"
|
||||
"};\n",
|
||||
4 },
|
||||
/* 3. [5]str = ["x", ...] — repeat marker fills all 5 slots with
|
||||
* a full 16B store, not just .ptr. Sum each .len: 5*1 = 5.
|
||||
* Pre-fix: a[0].len = 1 from explicit init, a[1..4].len = stack
|
||||
* residue (often 0, but unspecified). */
|
||||
{ "str_repeat_5el",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [5]str = [\"x\"...];\n"
|
||||
" let s: i32 = 0i32;\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < 5) {\n"
|
||||
" s += a[i].len: i32;\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return s;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* 4. [3]bool — regression-pin. Pre-fix and post-fix: true/false/
|
||||
* true with element width 1. Exit = 4*b[0] + 2*b[1] + b[2] =
|
||||
* 4 + 0 + 1 = 5. */
|
||||
{ "bool_3el_regression",
|
||||
"fn main() i32 = {\n"
|
||||
" let b: [3]bool = [true, false, true];\n"
|
||||
" let s: i32 = 0i32;\n"
|
||||
" if (b[0]) { s += 4i32; };\n"
|
||||
" if (b[1]) { s += 2i32; };\n"
|
||||
" if (b[2]) { s += 1i32; };\n"
|
||||
" return s;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* 5. [3]rune — regression-pin primitive size 4. 'b' - 'a' = 1. */
|
||||
{ "rune_3el_regression",
|
||||
"fn main() i32 = {\n"
|
||||
" let r: [3]rune = ['a', 'b', 'c'];\n"
|
||||
" let v: rune = r[1];\n"
|
||||
" return (v: i32) - 97i32;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* 6. [3]i32 — regression-pin primitive size 4. */
|
||||
{ "i32_3el_regression",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" return a[0] + a[1] - a[2];\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* 7. [3]i64 — regression-pin primitive size 8. Sum 1+2+3 = 6. */
|
||||
{ "i64_3el_regression",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i64 = [1i64, 2i64, 3i64];\n"
|
||||
" return (a[0] + a[1] + a[2]): i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
/* 8. Bare-let [N]str index as a call arg (task #34). Pre-#34
|
||||
* wwstage emitted PUSHQ AX only for argv[i] — the str value's
|
||||
* BX=len half was dropped, and streq's `a.len` parameter read
|
||||
* stack residue. Symptom under getopttest: rc=11 pre-#21, then
|
||||
* 139 once #21 doubled the slot stride. Now byte-identical to
|
||||
* cstage at the call site. streq("files.txt",argv[2]) → 0 (eq)
|
||||
* → return 0. Pre-fix wwstage returned 11. */
|
||||
{ "barelet_index_call_arg",
|
||||
"fn streq(a: str, b: str) bool = {\n"
|
||||
" if (a.len != b.len) { return false; };\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < a.len) {\n"
|
||||
" if (a[i] != b[i]) { return false; };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return true;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let argv: [3]str;\n"
|
||||
" argv[0] = \"ls\";\n"
|
||||
" argv[1] = \"-Fahs\";\n"
|
||||
" argv[2] = \"files.txt\";\n"
|
||||
" if (!streq(argv[2], \"files.txt\")) { return 11; };\n"
|
||||
" if (!streq(argv[1], \"-Fahs\")) { return 12; };\n"
|
||||
" if (!streq(argv[0], \"ls\")) { return 13; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* 9. Nested call: `f(g(argv[i]))` exercises the full call-arg
|
||||
* packer with an N_INDEX-of-str inside an N_CALL inside another
|
||||
* N_CALL. Pins that the inner N_INDEX recognition rides through
|
||||
* the same pushargsrev path that the simple row uses. dup1 here
|
||||
* is identity-on-str so the outer streq receives g(argv[2]),
|
||||
* which must be 9 bytes ("files.txt"). */
|
||||
{ "nested_call_index_arg",
|
||||
"fn dup1(s: str) str = { return s; };\n"
|
||||
"fn streq(a: str, b: str) bool = {\n"
|
||||
" if (a.len != b.len) { return false; };\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < a.len) {\n"
|
||||
" if (a[i] != b[i]) { return false; };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return true;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let argv: [3]str;\n"
|
||||
" argv[0] = \"a\";\n"
|
||||
" argv[1] = \"bb\";\n"
|
||||
" argv[2] = \"files.txt\";\n"
|
||||
" if (!streq(dup1(argv[2]), \"files.txt\")) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* 10. Index .len as a call arg via a one-arg consumer. Confirms
|
||||
* cgindex's (AX, BX) load still drives a single-i32 push when
|
||||
* the field selector picks the .len half off the str element.
|
||||
* Different code path from rows 8-9 (no str-arg push) but
|
||||
* uses the same N_INDEX base. 5 chars in "hello". */
|
||||
{ "barelet_index_len_arg",
|
||||
"fn ident(n: i32) i32 = { return n; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let xs: [2]str;\n"
|
||||
" xs[0] = \"hi\";\n"
|
||||
" xs[1] = \"hello\";\n"
|
||||
" return ident(xs[1].len: i32);\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcrarrs_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wcrarrs_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wcrarrs_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "arrlit_str_full: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"arrlit_str_full[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arrlit_str_full: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arrlit_str_full: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* 713_struct_field_index — wwstage N_INDEX through a struct-field base
|
||||
* carries the element type. Three sister gaps in wwstage cgen's untyped
|
||||
* AST defaulted to esz=8 / scalar-push when the base was N_DOT with the
|
||||
* field a *T or []T. Surfaced by getopttest wwstage build (#37
|
||||
* investigation, originally misattributed as a symbol-mangling bug).
|
||||
*
|
||||
* Pre-fix sites (selfhost/cmd/wcc/):
|
||||
*
|
||||
* 1. cgenutil.ww indexbaseesz `.ptr` pseudo-field on a slice field:
|
||||
* `&p.ptr[i]` where p: *[]S fell through `elemsizeof(innert)`
|
||||
* which returned 8 (named-struct element), not sizeof(S). So
|
||||
* `IMULQ $8, AX` instead of `IMULQ $24, AX` — every cross-element
|
||||
* addressing landed inside element 0. Fix: route slice case
|
||||
* through elemsizeofc so structlookup resolves the stride.
|
||||
*
|
||||
* 2. cgenexpr.ww cgun TK_AMP N_INDEX with N_DOT base: only
|
||||
* base.kind == N_IDENT was handled; N_DOT base left esz=8 from
|
||||
* the function-top default. `&p.ptr[i]` for any p.ptr (slice ptr
|
||||
* or *T field) hit this. Fix: add the N_DOT arm calling
|
||||
* indexbaseesz.
|
||||
*
|
||||
* 3. cgenutil.ww nodeisstr N_INDEX with N_DOT base: only N_IDENT
|
||||
* base was handled; `cmd.argsptr[i]` (argsptr: *str) returned
|
||||
* false, so pushargsrev pushed only AX and the call's str arg
|
||||
* lost its .len half to stack residue. Sister to #34's
|
||||
* [N]str-through-N_IDENT fix. Fix: walk the struct field's
|
||||
* element type and return isstrtype.
|
||||
*
|
||||
* All three are the same root pattern as #34 / #36: the untyped AST
|
||||
* loses the type through a struct field, so the per-shape recognizer
|
||||
* has to walk N_DOT → struct → field → element manually. A typed AST
|
||||
* (#11 wwstage check pass) would replace all three with one query.
|
||||
*
|
||||
* Rows are runtime-only (no asm byte-id): wwstage's local layout
|
||||
* legitimately differs from cstage's (slot offsets shift by 8), and
|
||||
* 995_self_rebuild already covers cross-stage drift on driver corpora.
|
||||
* The semantic invariant — values round-trip correctly — is what we
|
||||
* pin here.
|
||||
*
|
||||
* row | what it pins
|
||||
* -----------------------------+----------------------------------
|
||||
* amp_ptr_index_struct_stride | &p.ptr[i] esz = sizeof(S)=24,
|
||||
* | not 8 (fix 1+2)
|
||||
* dot_ptr_index_str_arg | cmd.argsptr[i] as str arg pushes
|
||||
* | both halves (fix 3)
|
||||
* amp_ptr_index_str_pair | &p.ptr[i] esz=16 for *[]str —
|
||||
* | sister regression-pin to fix 1
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. `&p.ptr[i]` where p: *[]S, S size 24 (i32 + str). Pre-fix
|
||||
* wwstage: IMULQ $8 → store landed inside element 0. Post-fix:
|
||||
* IMULQ $24 → store lands in element i. Reads back element 1's
|
||||
* `a` = 22 (mod 256). */
|
||||
{ "amp_ptr_index_struct_stride",
|
||||
"type S = struct { a: i32, b: str };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]S;\n"
|
||||
" buf[0].a = 10i32; buf[0].b = \"x\";\n"
|
||||
" buf[1].a = 22i32; buf[1].b = \"yy\";\n"
|
||||
" buf[2].a = 30i32; buf[2].b = \"zzz\";\n"
|
||||
" let xs: []S;\n"
|
||||
" xs.ptr = &buf[0];\n"
|
||||
" xs.len = 3;\n"
|
||||
" xs.cap = 3;\n"
|
||||
" let p: *[]S = &xs;\n"
|
||||
" let q: *S = &p.ptr[1];\n"
|
||||
" return q.a;\n"
|
||||
"};\n",
|
||||
22 },
|
||||
/* 2. `cmd.argsptr[i]` where argsptr: *str passed as str arg.
|
||||
* Pre-fix wwstage: nodeisstr returned false → call-arg push
|
||||
* dropped .len → streq received .len from stack residue → no
|
||||
* match. Post-fix: full str round-trip. */
|
||||
{ "dot_ptr_index_str_arg",
|
||||
"fn streq(a: str, b: str) i32 = {\n"
|
||||
" if (a.len != b.len) { return 0i32; };\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < a.len) {\n"
|
||||
" if (a[i] != b[i]) { return 0i32; };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return 1i32;\n"
|
||||
"};\n"
|
||||
"type cmd = struct { argsptr: *str, argslen: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let xs: [2]str;\n"
|
||||
" xs[0] = \"first\";\n"
|
||||
" xs[1] = \"second\";\n"
|
||||
" let c: cmd;\n"
|
||||
" c.argsptr = &xs[0];\n"
|
||||
" c.argslen = 2;\n"
|
||||
" if (streq(c.argsptr[0], \"first\") == 0i32) { return 1; };\n"
|
||||
" if (streq(c.argsptr[1], \"second\") == 0i32) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* 3. `&p.ptr[i]` for *[]str — sister to row 1 with str (16B)
|
||||
* elements. Reads the .len half of element 1 to assert the
|
||||
* stride lands on the right slot. Pre-fix cgun N_DOT-base
|
||||
* (fix 2) left esz=8 even though elemsizeof would have returned
|
||||
* 16 for str directly, so `&p.ptr[1]` pointed at element 0's
|
||||
* `.len` half instead of element 1's `.ptr`. element 1's .len
|
||||
* = 2 ("yy"). */
|
||||
{ "amp_ptr_index_str_pair",
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]str;\n"
|
||||
" buf[0] = \"a\";\n"
|
||||
" buf[1] = \"yy\";\n"
|
||||
" buf[2] = \"zzz\";\n"
|
||||
" let xs: []str;\n"
|
||||
" xs.ptr = &buf[0];\n"
|
||||
" xs.len = 3;\n"
|
||||
" xs.cap = 3;\n"
|
||||
" let p: *[]str = &xs;\n"
|
||||
" let q: *str = &p.ptr[1];\n"
|
||||
" return q.len: i32;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcrsfi_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wcrsfi_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wcrsfi_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "struct_field_index: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"struct_field_index[%s] row[%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label, got,
|
||||
rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"struct_field_index: %d/%d row(s) failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("struct_field_index: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
/*
|
||||
* 714_tagged_return_scratch — wwstage @tagscr scratch slot sized
|
||||
* dynamically to the per-function max across all reservation sites.
|
||||
*
|
||||
* Pre-fix every site (cgreturn / pushargsrev / cgindex / cgassign-via
|
||||
* cgwidentaggedstore) hardcoded 24B for @tagscr. A `(void | T)` return
|
||||
* where T is 24B has slot_size = 32 (8B tag + 24B payload); the
|
||||
* outer zero loop in cgreturn (cgenstmt.ww) then walked 32 bytes
|
||||
* starting at the slot's BP offset, spilling 8B past the slot's end
|
||||
* into the adjacent local — typically the very `T` being returned.
|
||||
* The pre-zero stomped on its kind/flag bytes, so the subsequent
|
||||
* field-by-field copy out delivered zeros in the AX/DX/CX/R8 return
|
||||
* ABI's DX slot. cstage was unaffected (its inline mklabel(c, "tagscr")
|
||||
* allocates a fresh slot of the requested size per call site).
|
||||
*
|
||||
* Fix (single source of truth via c.tagscrsz):
|
||||
* - cgen.ww: cgen struct gains `tagscrsz: i32`; cgeninit resets.
|
||||
* - cgendecl.ww: scanlocals's four @tagscr-reservation sites call
|
||||
* tagscrbump(c, slotsize(c, T)) which raises c.tagscrsz to the
|
||||
* max needed and returns the delta to add to the frame total.
|
||||
* - cgenstmt.ww / cgenutil.ww / cgenexpr.ww: every emit-time
|
||||
* localadd("@tagscr", _, nil) passes c.tagscrsz so the first
|
||||
* allocation in the fn lands a slot sized for every later user.
|
||||
* - cgenutil.ww (cgwidentaggedstore pointer-rooted): switches from
|
||||
* local slot_sz to c.tagscrsz for the same lockstep reason.
|
||||
*
|
||||
* Closes STATUS latent #1 (`@tagscr` shared 24B reservation across all
|
||||
* tagged scratch sites). Surfaced through getopttest's errortable
|
||||
* scenario, where `tryparse` returns a 24B `error` value.
|
||||
*
|
||||
* Rows (runtime semantic round-trip; no asm byte-id — wwstage local
|
||||
* layouts legitimately differ from cstage's and 995_self_rebuild
|
||||
* covers cross-stage drift):
|
||||
*
|
||||
* row | what it pins
|
||||
* ----------------------------+----------------------------------
|
||||
* ret_struct24_adjacent | (void | err24); local `e: err`
|
||||
* | sits right above @tagscr; pre-zero
|
||||
* | clobbered e+0..e+7 → kind/flag came
|
||||
* | back zero. Post-fix: full payload
|
||||
* | round-trips through the tagged
|
||||
* | return.
|
||||
* ret_struct24_via_match | scrutinee match arm reads kind +
|
||||
* | flag + a + b from the case binding;
|
||||
* | exercises both the cgreturn write
|
||||
* | and the match-arm spill read.
|
||||
* ret_mixed_sizes_one_fn | rob's lockstep pin — one fn body
|
||||
* | holds TWO tagged-scratch sites at
|
||||
* | different sizes: a 16B pushargsrev
|
||||
* | widen (call into `(void | small)`)
|
||||
* | and a 32B cgreturn widen (return
|
||||
* | `(void | err24)`). scanlocals must
|
||||
* | raise c.tagscrsz from 16→32 via
|
||||
* | tagscrbump, and both emit sites must
|
||||
* | dedupe to the same 32B slot. The
|
||||
* | smaller pushargsrev site is the
|
||||
* | FIRST '@' localadd (so it allocates
|
||||
* | the slot); the larger cgreturn site
|
||||
* | dedupes by name. If either side ever
|
||||
* | regressed to a per-site size, the
|
||||
* | 32B cgreturn zero loop would walk
|
||||
* | past the 16B slot and clobber e+0..
|
||||
* | e+7 — same shape as row 1's adjacency
|
||||
* | repro, but now staged through the
|
||||
* | shared-slot path.
|
||||
* ret_struct24_param_widen | call-site struct-payload widen into
|
||||
* | a (void | err24) param uses the
|
||||
* | same @tagscr; pushargsrev's slot
|
||||
* | must hold all 32B.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. Direct repro of #38. Local `e: err` (24B) is declared before
|
||||
* the return; scanlocals lays it out adjacent to @tagscr. Pre-fix
|
||||
* (24B @tagscr) the outer zero loop wrote 32B and clobbered e+0.
|
||||
* Returns 11 + 22 + 33 + 44 = 110. */
|
||||
{ "ret_struct24_adjacent",
|
||||
"type err = struct { kind: i32, flag: i32, a: i64, b: i64 };\n"
|
||||
"fn outer() (void | err) = {\n"
|
||||
" let e: err;\n"
|
||||
" e.kind = 11; e.flag = 22; e.a = 33i64; e.b = 44i64;\n"
|
||||
" return e;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: (void | err) = outer();\n"
|
||||
" match (r) {\n"
|
||||
" case void => return 99;\n"
|
||||
" case let v: err => return v.kind + v.flag + (v.a: i32) + (v.b: i32);\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
110 },
|
||||
|
||||
/* 2. Same shape but caller iterates fields through the match
|
||||
* binding (no early-return-from-arm shortcut). Cross-checks that
|
||||
* the match-arm spill reads each field at the expected offset
|
||||
* after the tagged return ABI lands AX=tag, DX=kind+flag,
|
||||
* CX=a, R8=b. Expected: 7 (kind=1) + 8 (flag=2) + 9 (a=3)
|
||||
* + 10 (b=4) = 34. */
|
||||
{ "ret_struct24_via_match",
|
||||
"type err = struct { kind: i32, flag: i32, a: i64, b: i64 };\n"
|
||||
"fn make() (void | err) = {\n"
|
||||
" let e: err;\n"
|
||||
" e.kind = 1; e.flag = 2; e.a = 3i64; e.b = 4i64;\n"
|
||||
" return e;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: (void | err) = make();\n"
|
||||
" let acc: i32 = 0;\n"
|
||||
" match (r) {\n"
|
||||
" case void => return 99;\n"
|
||||
" case let v: err => {\n"
|
||||
" acc += v.kind + 6;\n"
|
||||
" acc += v.flag + 6;\n"
|
||||
" acc += (v.a: i32) + 6;\n"
|
||||
" acc += (v.b: i32) + 6;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};\n",
|
||||
34 },
|
||||
|
||||
/* 3. Rob's lockstep pin: TWO tagged-scratch sites of different
|
||||
* sizes in ONE fn body. `mixed` has a pushargsrev widen (16B
|
||||
* slot, bare call into a `(void | small)` param) and a cgreturn
|
||||
* widen (32B slot, returning `e: err`).
|
||||
*
|
||||
* Source order is chosen so the local layout pre-fix lands the
|
||||
* cgreturn zero-loop overflow on the returned value's own bytes.
|
||||
* `consume_small` returns void and is called bare (no let-bind),
|
||||
* so @tagscr is the LAST localalloc and `e` sits immediately
|
||||
* above it:
|
||||
*
|
||||
* localalloc s → -8 (8B)
|
||||
* localalloc e → -32 (24B; e.kind/flag at -32, e.a/-24, e.b/-16)
|
||||
* localadd @tagscr → -56 (pushargsrev hits first; pre-fix 24B slot)
|
||||
* cgreturn @tagscr → dedupes to -56
|
||||
* cgreturn zero loop walks rsz=32 → writes -56,-48,-40,-32:
|
||||
* -32 lands on e.kind/e.flag → both zero before
|
||||
* cgwidentaggedstore reads e to fill scratch → AX/DX ABI
|
||||
* ships kind=0 instead of 100.
|
||||
*
|
||||
* Post-fix: scanlocals raises c.tagscrsz from 16 → 32 via
|
||||
* tagscrbump across the two sites; localadd allocates a 32B
|
||||
* slot (-64), zero loop stays in bounds, e survives, main
|
||||
* returns 100. Also pins the '@'-prefix dedup contract: both
|
||||
* sites must see the same slot, not fork into separate
|
||||
* allocations of their own per-site sizes. */
|
||||
{ "ret_mixed_sizes_one_fn",
|
||||
"type err = struct { kind: i32, flag: i32, a: i64, b: i64 };\n"
|
||||
"type small = struct { v: i32 };\n"
|
||||
"fn consume_small(r: (void | small)) void = {\n"
|
||||
" match (r) {\n"
|
||||
" case void => return;\n"
|
||||
" case let v: small => return;\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"fn mixed() (void | err) = {\n"
|
||||
" let s: small;\n"
|
||||
" s.v = 7;\n"
|
||||
" let e: err;\n"
|
||||
" e.kind = 100; e.flag = 0; e.a = 0i64; e.b = 0i64;\n"
|
||||
" consume_small(s);\n"
|
||||
" return e;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: (void | err) = mixed();\n"
|
||||
" match (r) {\n"
|
||||
" case void => return 91;\n"
|
||||
" case let v: err => return v.kind;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
100 },
|
||||
|
||||
/* 4. Call-site struct-payload widen: pass an err24 value to a fn
|
||||
* whose param is (void | err24). pushargsrev materialises the
|
||||
* payload in @tagscr (slot 32B) and pushes. Sister of row 1 on
|
||||
* the call-arg side. Expected: 50 (kind) + 60 (flag) = 110. */
|
||||
{ "ret_struct24_param_widen",
|
||||
"type err = struct { kind: i32, flag: i32, a: i64, b: i64 };\n"
|
||||
"fn consume(r: (void | err)) i32 = {\n"
|
||||
" match (r) {\n"
|
||||
" case void => return 91;\n"
|
||||
" case let v: err => return v.kind + v.flag;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let e: err;\n"
|
||||
" e.kind = 50; e.flag = 60; e.a = 0i64; e.b = 0i64;\n"
|
||||
" return consume(e);\n"
|
||||
"};\n",
|
||||
110 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wctrs_%d_d_%d", getpid(), i);
|
||||
snprintf(src, sizeof src, "%s/wctrs_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wctrs_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tagged_return_scratch: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"tagged_return_scratch[%s] row[%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label, got,
|
||||
rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tagged_return_scratch: %d/%d row(s) failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_return_scratch: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* 715_tagged_widen_f64 — variant-widen into a tagged-union slot must
|
||||
* route f64/f32 sources through the SSE register (X0) instead of the
|
||||
* integer AX. Pre-fix cg_widen_tagged_store / cgwidentaggedstorebp fell
|
||||
* through to a single "scalar payload" arm that always emitted
|
||||
* `MOVQ AX, slot+8(BP)`; for an f64 source cgexpr leaves the bit pattern
|
||||
* in X0 only and AX holds the pre-conversion integer (or any prior
|
||||
* temp). The literal-f64 case worked by coincidence because TK_FLOAT
|
||||
* lowering happens to load the f64 bit pattern into AX before the
|
||||
* MOVSD into X0; every runtime-f64 shape (cast, call, unary, ident,
|
||||
* struct-field load) silently miscompiled.
|
||||
*
|
||||
* Surfaced by worker-fmtfloat probe during the #17 (fmt float dispatch
|
||||
* arm) pre-flight. Closed by adding an `fld_isfloat(st, ...)` /
|
||||
* `exprfloatkind(c, src)` arm ahead of the scalar fallback in both
|
||||
* stages — same kind-specific dispatch as the existing str / slice
|
||||
* branches and the structlit-field-flow MOVSD arm.
|
||||
*
|
||||
* Coverage is bit-level: each row punts the tagged-union slot through
|
||||
* `*u8` and reads payload bits as u64, so a tag-only or low-32-bits-only
|
||||
* store fails the row instead of silently approximating. The expected
|
||||
* payload for f64 1.0 is 0x3FF0000000000000 == 4607182418800017408.
|
||||
*
|
||||
* Rows:
|
||||
* literal_1_0 — `1.0` typed by surface form. Pre-fix passed
|
||||
* by coincidence (MOVQ AX path happened to
|
||||
* hold the right bits); pin it explicitly so
|
||||
* a future cgexpr refactor that changes the
|
||||
* constant-load shape can't silently regress.
|
||||
* cast_1_f64 — `1: f64`. Cast-peel keeps the cast (dest is
|
||||
* a concrete variant, not the union), so the
|
||||
* CVTSI2SD's X0 result must flow through
|
||||
* MOVSD.
|
||||
* call_makeone — fn returning f64. X0-ABI return.
|
||||
* unary_neg_f64 — `-(1: f64)`. N_UN(MINUS, N_CAST) over f64;
|
||||
* cgun keeps the value in X0.
|
||||
* ident_f64 — concrete f64 local. cgexpr emits a MOVSD
|
||||
* load to X0; AX is untouched.
|
||||
* field_f64 — load from a struct field of type f64.
|
||||
* Exercises the field-read shape through
|
||||
* cgexpr; same X0-only ABI.
|
||||
*
|
||||
* One extra row pins the negative case: an i64 rhs of the same union
|
||||
* must still emit MOVQ AX (the float arm is gated on the source's
|
||||
* float-class, not on the dst variant set).
|
||||
*
|
||||
* Note: this is a regression cover for the same path that blocks #17
|
||||
* (fmt float dispatch arm). No in-tree caller exercised the path
|
||||
* pre-fix, hence the latent silence; #17 will be the first consumer.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
/* Bit-pin via `*u8` cast → payload at slot+8, tag at slot+0. Returns
|
||||
* 0 if payload bits and tag both match expected
|
||||
* 1 if tag wrong
|
||||
* 2 if payload bits wrong (the f64 miscompile signature)
|
||||
* Driver checks `got == 0`. */
|
||||
#define BITCHECK_F64(union_decl, init_stmt, want_bits, want_tag) \
|
||||
union_decl \
|
||||
"fn main() i32 = {\n" \
|
||||
" " init_stmt \
|
||||
" let pp: *(i64 | f64) = &a;\n" \
|
||||
" let pu: *u8 = pp: *u8;\n" \
|
||||
" let tagp: *i64 = pu: *i64;\n" \
|
||||
" let payp: *u64 = (pu + 8u64): *u64;\n" \
|
||||
" if (*tagp != " want_tag "i64) { return 1; };\n" \
|
||||
" if (*payp != " want_bits "u64) { return 2; };\n" \
|
||||
" return 0;\n" \
|
||||
"};\n"
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Literal 1.0 — already-correct lock-in. TK_FLOAT lowering
|
||||
* loaded the f64 bits into AX so MOVQ wrote the right value;
|
||||
* after the fix it goes through the principled MOVSD path. */
|
||||
{ "literal_1_0",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = 1.0;\n",
|
||||
"4607182418800017408", "1"),
|
||||
0 },
|
||||
/* Runtime cast: `1: f64`. Pre-fix wrote integer 1 into payload. */
|
||||
{ "cast_1_f64",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = 1: f64;\n",
|
||||
"4607182418800017408", "1"),
|
||||
0 },
|
||||
/* Fn returning f64 — X0-ABI return. cgexpr on N_CALL leaves the
|
||||
* value in X0, AX holds the return-value index / 0. */
|
||||
{ "call_makeone",
|
||||
BITCHECK_F64(
|
||||
"fn makeone() f64 = { return 1: f64; };\n",
|
||||
"let a: (i64 | f64) = makeone();\n",
|
||||
"4607182418800017408", "1"),
|
||||
0 },
|
||||
/* Unary minus over an f64 cast — value stays in X0 through cgun. */
|
||||
{ "unary_neg_f64",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = -(1: f64);\n",
|
||||
/* -1.0 == 0xBFF0000000000000 == 13830554455654793216 */
|
||||
"13830554455654793216", "1"),
|
||||
0 },
|
||||
/* #40: N_UN(MINUS, N_FLOATLIT) where the inner literal is untyped.
|
||||
* cunop returns the operand type for TK_MINUS, so the expression's
|
||||
* type is ty_untyped_float; the pre-fix fld_isfloat predicate
|
||||
* accepted only TY_F32/TY_F64 and the variant-widen float arm
|
||||
* silently fell through to MOVQ AX, dropping the X0 result onto
|
||||
* a trashed AX. -2.5 == 0xC004000000000000. */
|
||||
{ "unary_neg_floatlit_direct",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = -2.5;\n",
|
||||
"13836183955189006336", "1"),
|
||||
0 },
|
||||
/* Same expression shape with explicit parens around the literal.
|
||||
* Parser builds N_UN(MINUS, N_FLOATLIT) for both forms; pinning
|
||||
* both rows guards against a future parse change altering that. */
|
||||
{ "unary_neg_floatlit_paren",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = -(2.5);\n",
|
||||
"13836183955189006336", "1"),
|
||||
0 },
|
||||
/* Binop of two untyped-float literals — N_BIN type is
|
||||
* ty_untyped_float (untyped+untyped → untyped, see check.c cbinop).
|
||||
* Same predicate gate as the unary case; pins that the extended
|
||||
* fld_isfloat covers the binop arm too. 2.5 + 1.0 == 3.5 ==
|
||||
* 0x400C000000000000. */
|
||||
{ "binop_floatlit_sum",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = (2.5 + 1.0);\n",
|
||||
"4615063718147915776", "1"),
|
||||
0 },
|
||||
/* Concrete f64 ident — cgexpr emits MOVSD load to X0; AX is
|
||||
* never touched. */
|
||||
{ "ident_f64",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let f: f64 = 1: f64;\n"
|
||||
" let a: (i64 | f64) = f;\n",
|
||||
"4607182418800017408", "1"),
|
||||
0 },
|
||||
/* Struct-field of type f64 — exercises the field-read shape
|
||||
* through cgexpr. Same X0-only ABI as plain idents. */
|
||||
{ "field_f64",
|
||||
BITCHECK_F64(
|
||||
"type holder = struct { v: f64, pad: i32 };\n",
|
||||
"let h: holder;\n"
|
||||
" h.v = 1: f64;\n"
|
||||
" let a: (i64 | f64) = h.v;\n",
|
||||
"4607182418800017408", "1"),
|
||||
0 },
|
||||
/* Negative control: i64 rhs into the same union must still hit
|
||||
* the integer arm (MOVQ AX). Pre-fix this also worked; the float
|
||||
* arm must not shadow it. Tag = 0 (i64 is variant 0). */
|
||||
{ "i64_rhs_still_integer",
|
||||
BITCHECK_F64(
|
||||
"",
|
||||
"let a: (i64 | f64) = 7i64;\n",
|
||||
"7", "0"),
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/twf64_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/twf64_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/twf64_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tagged_widen_f64: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"tagged_widen_f64[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tagged_widen_f64: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_widen_f64: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* 729_empty_alloc_infer — check: an untyped empty `alloc([], n)` must
|
||||
* loudly fail to infer its slice element type instead of silently
|
||||
* defaulting to []u8 (task #3 / B', subsumes #5).
|
||||
*
|
||||
* Pre-fix: cmd/wcc/check.c's alloc-slice branch pinned the element type
|
||||
* to u8 with no context. `let b = alloc([], n)!` (no annotation) became
|
||||
* []u8, and the value-form lowerings `return alloc([], n)!` /
|
||||
* `f(alloc([], n))` SILENTLY MISCOMPILED (malloc(8) ignoring n; a 16B
|
||||
* *u8|nomem where a 24B slice was expected) — that was bug #5.
|
||||
*
|
||||
* Post-fix: ww aligns DOWN to harec, which refuses to guess —
|
||||
* "Cannot infer array type from context" (ref/harec/src/check.c:1801).
|
||||
* The ONLY context that supplies the element type is the let annotation
|
||||
* (the #45 retype), so an annotated alloc of ANY element type still
|
||||
* compiles; every other empty alloc errors at check time.
|
||||
*
|
||||
* Cstage-driver negative test, same shape as 712_redecl. The wwstage
|
||||
* twin (selfhost/cmd/wcc/check.ww, exprtype alloc-slice branch +
|
||||
* checkletassign context-flag) rejects symmetrically; it is validated by
|
||||
* the 990-997 byte-id gates rebuilding the *_ww tools from check.ww. The
|
||||
* positive @test (annotated []u8 / []i32 alloc) rides attest_pass.ww
|
||||
* (910/997, dual-stage).
|
||||
*
|
||||
* row | kind | what it pins
|
||||
* ---------------------+-------------+------------------------------
|
||||
* neg_bare_let | build fails | no-annotation u8 default gone
|
||||
* neg_return | build fails | #5 value-form return
|
||||
* neg_call_arg | build fails | #5 value-form call-arg
|
||||
* neg_assign | build fails | re-bind has no annotation hint
|
||||
* pos_annotated_u8 | exit=16 | let []u8 still infers
|
||||
* pos_annotated_wide | exit=5 | let []i32 (#45 retype) infers
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* kind == 0: negative — build must fail (any nonzero exit).
|
||||
* kind == 1: positive — build must succeed AND binary exits with `want`.
|
||||
*/
|
||||
struct row { const char *label; int kind; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* neg: bare let, no annotation — was a silent []u8 default. */
|
||||
{ "neg_bare_let", 0,
|
||||
"fn main() i32 = {\n"
|
||||
" let b = alloc([], 8u64)!;\n"
|
||||
" return b.len: i32;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* neg: value-form return — #5 silent miscompile. */
|
||||
{ "neg_return", 0,
|
||||
"fn mk() []u8 = { return alloc([], 8u64)!; };\n"
|
||||
"fn main() i32 = { let b: []u8 = mk(); return b.len: i32; };\n",
|
||||
0 },
|
||||
|
||||
/* neg: value-form call-arg — #5 silent miscompile. */
|
||||
{ "neg_call_arg", 0,
|
||||
"fn g(x: []u8) i32 = { return x.len: i32; };\n"
|
||||
"fn main() i32 = { return g(alloc([], 8u64)!); };\n",
|
||||
0 },
|
||||
|
||||
/* neg: assignment target supplies NO context (only the let
|
||||
* annotation does; #45). A re-bind `x = alloc([], n)` must error
|
||||
* too — top-down hint threading to assign is A', out of scope. */
|
||||
{ "neg_assign", 0,
|
||||
"fn main() i32 = {\n"
|
||||
" let x: []u8 = alloc([], 4u64)!;\n"
|
||||
" x = alloc([], 8u64)!;\n"
|
||||
" return x.len: i32;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* pos: let annotation supplies the element type (u8). alloc([], n)
|
||||
* is Hare's len=0 / cap=n empty slice, so write into the cap-backed
|
||||
* memory and read back — also proves the u8 element stride. */
|
||||
{ "pos_annotated_u8", 1,
|
||||
"import rt;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: []u8 = alloc([], 8u64)!;\n"
|
||||
" b[0] = 7u8;\n"
|
||||
" b[3] = 9u8;\n"
|
||||
" return (b[0]: i32) + (b[3]: i32);\n"
|
||||
"};\n",
|
||||
16 },
|
||||
|
||||
/* pos: let annotation with a wide element — the #45 retype. The i32
|
||||
* stride (4B) must drive indexing, not the u8 default. */
|
||||
{ "pos_annotated_wide", 1,
|
||||
"import rt;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let w: []i32 = alloc([], 4u64)!;\n"
|
||||
" w[2] = 5i32;\n"
|
||||
" return w[2];\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char cmd[2048];
|
||||
|
||||
if (r->kind == 0) {
|
||||
/* Negative — build must fail. Source + outbin live inside a
|
||||
* per-row tmpdir so the compiler's <ostem>.sepwork (mkdir'd
|
||||
* before the build fails) lands there and is rm -rf'd, not
|
||||
* leaked next to a bare-/tmp source. */
|
||||
char tmpdir[128], src[160], outbin[160], rmcmd[192];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcealloc_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wcealloc_%d_%d.ww",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wcealloc_%d_%d",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s build -o %s %s >/dev/null 2>&1", driver, outbin, src);
|
||||
int rc = runwait(cmd);
|
||||
if (rc == 0)
|
||||
fprintf(stderr,
|
||||
"ealloc[%s]: build unexpectedly succeeded\n",
|
||||
r->label);
|
||||
runwait(rmcmd);
|
||||
return rc == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Positive — build (with rt linked via `ww run`) then check exit.
|
||||
* `ww run` self-cleans its scratch (/tmp/ww_run_<pid>.sepwork), so the
|
||||
* bare-/tmp source leaks nothing; left unwrapped. */
|
||||
char src[128], combined[160];
|
||||
snprintf(src, sizeof src, "/tmp/wcealloc_%d_%d.ww", getpid(), i);
|
||||
snprintf(combined, sizeof combined, "/tmp/wcealloc_%d_%d.combined.ww",
|
||||
getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", driver, src);
|
||||
int got = runwait(cmd);
|
||||
unlink(src);
|
||||
unlink(combined);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr, "ealloc[%s]: exit=%d want=%d\n",
|
||||
r->label, got, r->want);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int fail = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (run_row(cdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "ealloc: %d/%d row(s) failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("ealloc: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 742_parse_error — both stages of w6c must exit non-zero on a parse
|
||||
* error. Pins rule 10 symmetry on the failure path.
|
||||
*
|
||||
* Predecessor symptom: wwstage `w6c_ww` ran cgen on the broken AST and
|
||||
* exited 0 (stderr noise only). Drivers downstream of w6c (`ww build`,
|
||||
* make rules) saw no signal and proceeded.
|
||||
*
|
||||
* cstage `w6c` checks `l.errs || p.errs` before cgen and returns 1.
|
||||
* This test pins the same exit-code contract on both binaries against a
|
||||
* known-bad fixture.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *content)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(content, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* check_fail — the tool must exit non-zero AND emit a parse diagnostic.
|
||||
* Both stages report "expected identifier" first on this fixture; a crash
|
||||
* exits non-zero with no diagnostic, so the substring distinguishes a
|
||||
* clean reject from a segfault (#20). */
|
||||
static int
|
||||
check_fail(const char *tool, const char *src)
|
||||
{
|
||||
char cmd[4096], errf[64];
|
||||
snprintf(errf, sizeof errf, "/tmp/parse_err_e_%d.txt", getpid());
|
||||
snprintf(cmd, sizeof cmd, "%s %s >/dev/null 2>%s", tool, src, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, "expected identifier");
|
||||
unlink(errf);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "742 FAIL: %s exited 0 on parse-error fixture\n",
|
||||
tool);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "742 FAIL: %s nonzero exit but missing parse "
|
||||
"diagnostic (a crash, not a clean reject)\n", tool);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2048];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char src[64], outs[64];
|
||||
snprintf(src, sizeof src, "/tmp/parse_err_%d.ww", getpid());
|
||||
snprintf(outs, sizeof outs, "/tmp/parse_err_%d.s", getpid());
|
||||
if (write_file(src, "fn fn fn ;\n") != 0) {
|
||||
fprintf(stderr, "742: cannot write fixture\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char w6c[2240], w6c_ww[2240], w6c_ww_bin[2240];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c -o %s", bin, outs);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww -o %s", bin, outs);
|
||||
snprintf(w6c_ww_bin, sizeof w6c_ww_bin, "%s/w6c_ww", bin);
|
||||
|
||||
int total = 0, fail = 0;
|
||||
|
||||
total++;
|
||||
if (check_fail(w6c, src) != 0) fail++;
|
||||
|
||||
if (access(w6c_ww_bin, X_OK) == 0) {
|
||||
total++;
|
||||
if (check_fail(w6c_ww, src) != 0) fail++;
|
||||
}
|
||||
|
||||
unlink(src);
|
||||
unlink(outs);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "742_parse_error: %d/%d failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("742_parse_error: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
* 748_size_strategy_convergence — sentinel for #15/#26c (subsumes #36).
|
||||
*
|
||||
* Pins wwstage's first-use+fail-loud frame-sizing convergence with
|
||||
* cstage. Pre-#15 wwstage ran a `scanlocals` pre-pass that walked the
|
||||
* body to pre-size the SUBQ slot total; cstage allocated slots at
|
||||
* emit time and patched the prologue after the body finished. The
|
||||
* pre-pass under-counted match-arm `case let` bindings in `(str|rune)
|
||||
* ...` callees (#36 original surface): variadic param tnode was the
|
||||
* inner type, not the synthesised slice wrap, so the @match_spill
|
||||
* lookup at scan time fell back to the default (16) instead of the
|
||||
* real slot (24). Emit-time cgmatch then resolved through the slice-
|
||||
* wrapped local and chose 24, growing c.frame past the SUBQ reservation;
|
||||
* `case let r: rune =>` writes landed below SP.
|
||||
*
|
||||
* Post-fix both stages defer the prologue until after the body emits,
|
||||
* and every @-prefix scratch slot (@tagscr / @retscr / @sretscr /
|
||||
* @tagbase / @sretarg / @vararg_*) is sized at first use. A later
|
||||
* caller asking for a larger slot than the first allocation pinned
|
||||
* fatals (rule 7 — pinned offset can't grow in place; #26 already
|
||||
* proved the model for cstage's @tagscr cache). The variadic gather
|
||||
* also routes the element stride through the raw type size (matching
|
||||
* cstage's velem->size) so the callee read at `arg[i]` lines up with
|
||||
* the caller's store.
|
||||
*
|
||||
* Rows assert per-stage runtime and cstage-vs-wwstage byte-id on the
|
||||
* shapes that #36 + #15 are pinned against.
|
||||
*
|
||||
* row | what it pins
|
||||
* -------------------------------+--------------------------------
|
||||
* tag_variadic_runearm | #36 original surface — rune-arm
|
||||
* | of `(str|rune)...` callee. Pre-
|
||||
* | fix wwstage SUBQ $80, cstage $96.
|
||||
* | Post-fix byte-id on the callee.
|
||||
* trim_iter_match_prev | #36 sibling — `trim: rune...` +
|
||||
* | strings.iter outer + nested
|
||||
* | strings.prev match. Pre-fix both
|
||||
* | stages framed $192 but match-arm
|
||||
* | scratch offsets diverged inside
|
||||
* | the same frame. Post-fix loop_for
|
||||
* | byte-id; runtime returns 0.
|
||||
* variadic_gather_rune_stride | Caller-side gather stride for
|
||||
* | `(rune...)` must use raw u32
|
||||
* | size (4), not slotsize (8 —
|
||||
* | stack-padded). Pre-fix wwstage
|
||||
* | MOVQ @ 8B stride vs cstage MOVL
|
||||
* | @ 4B; callee's `arg[i]` read at
|
||||
* | 4B stride saw garbage at odd
|
||||
* | indices.
|
||||
* leaf_baseline | Leaf fn match: no nested blocks,
|
||||
* | no scratch slot cache interaction.
|
||||
* | Confirms the deferred-prologue
|
||||
* | refactor didn't regress the
|
||||
* | simple frame size.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. #36 original surface: rune-arm of `(str|rune)...` callee.
|
||||
* want("hello", 'X', "world") → 5 + 1 + 5 = 11. */
|
||||
{ "tag_variadic_runearm",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"fn want(args: (str | rune)...) i64 = {\n"
|
||||
" let total: i64 = 0;\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < args.len) {\n"
|
||||
" match (args[i]) {\n"
|
||||
" case let s: str => total += s.len: i64;\n"
|
||||
" case let r: rune => total += 1;\n"
|
||||
" };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return total;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let n: i64 = want(\"hello\", 'X', \"world\");\n"
|
||||
" return n: i32;\n"
|
||||
"};\n",
|
||||
11 },
|
||||
|
||||
/* 2. #36 sibling: variadic + iter + nested match prev in non-leaf.
|
||||
* Mirrors .ai/probe_trim_36extra.ww. loop_for("aabcc", 'a', 'b')
|
||||
* trims both leading 'a's and the leading 'b' → "cc" (len 2). */
|
||||
{ "trim_iter_match_prev",
|
||||
"package main;\n"
|
||||
"import strings;\n"
|
||||
"import encoding.utf8;\n"
|
||||
"fn loop_for(input: str, trim: rune...) str = {\n"
|
||||
" let it: strings.iterator = strings.iter(input);\n"
|
||||
" for (true) {\n"
|
||||
" match (strings.next(&it)) {\n"
|
||||
" case let r: rune => {\n"
|
||||
" let j: i32 = 0;\n"
|
||||
" let found: bool = false;\n"
|
||||
" for (j < trim.len) {\n"
|
||||
" if (r == trim[j]) { found = true; j = trim.len; }\n"
|
||||
" else { j += 1; };\n"
|
||||
" };\n"
|
||||
" if (!found) {\n"
|
||||
" match (strings.prev(&it)) {\n"
|
||||
" case let r2: rune => void;\n"
|
||||
" case utf8.done => void;\n"
|
||||
" };\n"
|
||||
" break;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" case utf8.done => break;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" return strings.iterstr(&it);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r: str = loop_for(\"aabcc\", 'a', 'b');\n"
|
||||
" if (r.len != 2) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* 3. Caller-side variadic gather stride. The callee reads `arg[j]`
|
||||
* at the raw element size (4 for rune); the gather must match.
|
||||
* Pre-fix wwstage stored at slotsize (8B stride) and the callee
|
||||
* read garbage for j=1 — the test would have returned `'b'` (98)
|
||||
* or 0 depending on the high half of the stack word. Post-fix the
|
||||
* caller gathers at 4B stride and `arg[1]` reads 'b' correctly. */
|
||||
{ "variadic_gather_rune_stride",
|
||||
"package main;\n"
|
||||
"fn pick(idx: i32, args: rune...) i32 = {\n"
|
||||
" if (idx >= args.len) { return 0; };\n"
|
||||
" return args[idx]: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r0: i32 = pick(0, 'a', 'b', 'c');\n"
|
||||
" let r1: i32 = pick(1, 'a', 'b', 'c');\n"
|
||||
" let r2: i32 = pick(2, 'a', 'b', 'c');\n"
|
||||
" if (r0 != 97) { return 11; };\n"
|
||||
" if (r1 != 98) { return 12; };\n"
|
||||
" if (r2 != 99) { return 13; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* 4. Leaf-only baseline. No nested blocks, no @-prefix scratch
|
||||
* interaction — a fn that match-binds a `(rune|void)` from a
|
||||
* concrete scrutinee should frame exactly the bind slot and
|
||||
* nothing else. Confirms the deferred-prologue refactor didn't
|
||||
* regress simple frame sizes. */
|
||||
{ "leaf_baseline",
|
||||
"package main;\n"
|
||||
"type tagged = (rune | void);\n"
|
||||
"fn pickrune(x: tagged) i32 = {\n"
|
||||
" match (x) {\n"
|
||||
" case let r: rune => return r: i32;\n"
|
||||
" case void => return 0;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: tagged = 'A';\n"
|
||||
" let r: i32 = pickrune(v);\n"
|
||||
" if (r != 65) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
build_with(const char *driver, const char *outbin, const char *src_path)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src_path);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
exec_bin(const char *bin)
|
||||
{
|
||||
return runwait(bin);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const struct row *r = &rows[i];
|
||||
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/sz_conv_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/sz_conv_%d_%d.ww",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/sz_conv_%d_%d",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { fail++; total++; runwait(rmcmd); continue; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
/* Runtime parity (cstage). */
|
||||
total++;
|
||||
if (build_with(cdrv, outbin, src) != 0) {
|
||||
fprintf(stderr,
|
||||
"size_strategy_convergence[cs][%s]: build failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else {
|
||||
int got = exec_bin(outbin);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr,
|
||||
"size_strategy_convergence[cs][%s]: rc=%d want=%d\n",
|
||||
r->label, got, r->want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Runtime parity (wwstage). */
|
||||
if (have_ww) {
|
||||
total++;
|
||||
if (build_with(wdrv, outbin, src) != 0) {
|
||||
fprintf(stderr,
|
||||
"size_strategy_convergence[ws][%s]: build failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else {
|
||||
int got = exec_bin(outbin);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr,
|
||||
"size_strategy_convergence[ws][%s]: rc=%d want=%d\n",
|
||||
r->label, got, r->want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"size_strategy_convergence: %d/%d rows failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("size_strategy_convergence: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* 751_vararg_seq_percall — sentinel for task #8. Post-#15 wwstage's
|
||||
* cgcall never bumped c.varargseq, so every variadic callsite in a fn
|
||||
* read seq=0 and minted the same `@vararg_d_0` / `@vararg_sl_0` slot.
|
||||
* Two variadic callsites with different arities in one fn hit #15's
|
||||
* `localadd: @-prefix slot grew within fn` fail-loud — the second
|
||||
* gather asked for a different element-buffer size than the pinned
|
||||
* slot. Cstage was unaffected: its `mklabel("vararg_d/sl")` bumps
|
||||
* labelseq naturally per call.
|
||||
*
|
||||
* Fix: cgcall reads `c.varargseq` and bumps it at each gather emit;
|
||||
* mirrors cstage's mklabel freshness.
|
||||
*
|
||||
* row | what it pins
|
||||
* -----------------------------+---------------------------------
|
||||
* mixed_arity_two_calls | original wedge — pick(1 arg) +
|
||||
* | pick(3 args) in main. Pre-fix
|
||||
* | wwstage fatals at the second
|
||||
* | gather; cstage rc=0. Post-fix
|
||||
* | both rc=0.
|
||||
* same_arity_two_calls | non-regression — pre and post
|
||||
* | both stages rc=0; same arity
|
||||
* | reuses the same slot size so
|
||||
* | the size-grow check never fired.
|
||||
* three_arity_drift | 3 callsites with arities 1/2/3
|
||||
* | in one fn. Post-fix both stages
|
||||
* | rc=0 and three distinct seq slots
|
||||
* | get minted (`@vararg_d_0/1/2`).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1. Two-call arity drift. Pre-fix wwstage fatals on the second
|
||||
* gather (1 rune vs 3 runes -> different element-buffer size). */
|
||||
{ "mixed_arity_two_calls",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"fn pick(args: (str | rune)...) i32 = { return args.len; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (pick(\"a\") != 1) { os.exit(11); };\n"
|
||||
" if (pick(\"a\", \"b\", \"c\") != 3) { os.exit(12); };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* 2. Non-regression: same-arity calls. */
|
||||
{ "same_arity_two_calls",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"fn pick(args: (str | rune)...) i32 = { return args.len; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (pick(\"a\") != 1) { os.exit(11); };\n"
|
||||
" if (pick(\"b\") != 1) { os.exit(12); };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* 3. Three callsites with arities 1/2/3 in one fn. Pre-fix
|
||||
* wwstage fatals at the second gather. Post-fix three distinct
|
||||
* @vararg_d_0/1/2 slots are minted. */
|
||||
{ "three_arity_drift",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"fn pick(args: (str | rune)...) i32 = { return args.len; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (pick(\"a\") != 1) { os.exit(11); };\n"
|
||||
" if (pick(\"a\", \"b\") != 2) { os.exit(12); };\n"
|
||||
" if (pick(\"a\", \"b\", \"c\") != 3) { os.exit(13); };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
build_with(const char *driver, const char *src_path, const char *outbin)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src_path);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
exec_bin(const char *bin)
|
||||
{
|
||||
return runwait(bin);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const struct row *r = &rows[i];
|
||||
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/vararg_seq_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/vararg_seq_%d_%d.ww",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/vararg_seq_%d_%d",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); fail++; total++; continue; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
total++;
|
||||
if (build_with(cdrv, src, outbin) != 0) {
|
||||
fprintf(stderr,
|
||||
"vararg_seq_percall[cs][%s]: build failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else {
|
||||
int got = exec_bin(outbin);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr,
|
||||
"vararg_seq_percall[cs][%s]: rc=%d want=%d\n",
|
||||
r->label, got, r->want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
unlink(outbin);
|
||||
|
||||
if (have_ww) {
|
||||
total++;
|
||||
if (build_with(wdrv, src, outbin) != 0) {
|
||||
fprintf(stderr,
|
||||
"vararg_seq_percall[ws][%s]: build failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else {
|
||||
int got = exec_bin(outbin);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr,
|
||||
"vararg_seq_percall[ws][%s]: rc=%d want=%d\n",
|
||||
r->label, got, r->want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"vararg_seq_percall: %d/%d rows failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("vararg_seq_percall: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
/*
|
||||
* 763_typeeq_fn_ast — locks wwstage check.ww typeeqast's TY_FN equality
|
||||
* arm at the AST layer. Pre-fix master left N_TFN under the conservative
|
||||
* "anything else fails" fall-through; case-patterns spelled with a raw
|
||||
* `*fn(...)` head (the io vtable use case) tripped errbadcase on every
|
||||
* variant compare, so a perfectly well-typed `match` would not compile
|
||||
* under wwstage. cstage type.c:239's `type_eq` walks the SAME shape on
|
||||
* resolved Type — that asymmetry is filed as project #178 (typeeqast
|
||||
* lives one layer below cstage's type_eq).
|
||||
*
|
||||
* Coverage: 7 rows, table-driven. Each row's source compiles cleanly
|
||||
* post-fix on BOTH stages, and the runtime selects the tag-0 arm
|
||||
* returning 7. Rows distinguish along the same axes harec's
|
||||
* type_equal STORAGE_FUNCTION arm checks (ref/harec/src/types.c:589-
|
||||
* 615): result type, arity, param type, variadism. Param NAMES do NOT
|
||||
* participate per harec + drew (row 7).
|
||||
*
|
||||
* a) Identical: fn(x: i32) void ≡ fn(x: i32) void
|
||||
* b) Diff return: fn(x: i32) void ≢ fn(x: i32) i32
|
||||
* c) Diff arity: fn(x: i32) void ≢ fn(x: i32, y: i64) void
|
||||
* d) Diff param type: fn(x: i32) void ≢ fn(x: i64) void
|
||||
* e) Variadic ≢ fixed: fn(xs: i32...) void ≢ fn(x: i32) void
|
||||
* f) Param-name only: fn(a: i32) void ≡ fn(b: i32) void
|
||||
* g) Io-vtable shape: fn(s: *i32, n: i32) i32 ≢ fn(s: *i32) i32
|
||||
*
|
||||
* Each row's tagged uses two variants whose tags differ (variant 0 is
|
||||
* the first fn-ptr shape; variant 1 is either the alternative fn-ptr
|
||||
* shape — rows b/c/d/e/g — or an `i32` payload — rows a/f). The
|
||||
* `let v: t;` bare declaration zero-inits the slot (see selfhost/
|
||||
* CLAUDE.md "Bare `let x: T;` ... now zero-inits") so the tag is 0 and
|
||||
* arm A fires returning 7. The wwstage checker's casevariantin /
|
||||
* casecovers walks (selfhost/cmd/wcc/check.ww:3082, :3110) only accept
|
||||
* a case-pattern that typeeqast'es one of the scrutinee's variants —
|
||||
* which is exactly the path the fold-b N_TFN arm now satisfies.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means the typeeqast
|
||||
* N_TFN arm regressed back toward the master fall-through.
|
||||
*
|
||||
* NOT GATED: cstage-vs-wwstage .s byte-identity is intentionally NOT
|
||||
* checked per-row. Rows b/c/d/e/g currently diverge at the case-arm
|
||||
* tag CMPQ because wwstage's cgmatch gates the case-pattern flatvariant
|
||||
* lookup on `pat.kind == nkind.N_TNAME` (selfhost/cmd/wcc/cgenexpr.ww:
|
||||
* 1512) — N_TPTR(N_TFN) case patterns short-circuit to tag 0. That is
|
||||
* project #179 (cgmatch flatvariantidx N_TNAME-only gate), a SIBLING
|
||||
* cgen-side bug filed alongside this fold per the brief's "do not
|
||||
* sweep" instruction, and orthogonal to the AST-layer typeeqast
|
||||
* equality this fold ratifies. Runtime is fortuitously OK because
|
||||
* the bare `let v: t;` zero-inits tag 0, masking the wrong-tag-load
|
||||
* the .s divergence would otherwise produce. The pre/post-fix asymmetry
|
||||
* THIS PROBE locks is the wwstage CHECKER outcome: pre-fix master
|
||||
* red-errors the program (4× errbadcase + match-not-handled); post-
|
||||
* fix it compiles to a runnable binary. The runtime exit-code arm
|
||||
* (= 7) double-checks that arm A's body actually runs.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* a) Identical fn-ptr shape; v: t zero-inits to tag 0 (the
|
||||
* *fn(x: i32) void variant); arm A fires. */
|
||||
{ "identical",
|
||||
"type t = (*fn(x: i32) void | i32);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(x: i32) void => return 7;\n"
|
||||
" case i32 => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* b) Same arity + param, different return (void vs i32). Both
|
||||
* variants are *fn(x: i32) _; post-fix typeeqast distinguishes
|
||||
* them by ret type. */
|
||||
{ "diff_return",
|
||||
"type t = (*fn(x: i32) void | *fn(x: i32) i32);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(x: i32) void => return 7;\n"
|
||||
" case *fn(x: i32) i32 => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* c) Different arity (1 vs 2 params). */
|
||||
{ "diff_arity",
|
||||
"type t = (*fn(x: i32) void | *fn(x: i32, y: i64) void);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(x: i32) void => return 7;\n"
|
||||
" case *fn(x: i32, y: i64) void => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* d) Different param type (i32 vs i64). */
|
||||
{ "diff_param_type",
|
||||
"type t = (*fn(x: i32) void | *fn(x: i64) void);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(x: i32) void => return 7;\n"
|
||||
" case *fn(x: i64) void => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* e) Variadic flag differs: Hare-style `xs: T...` carries
|
||||
* .op == TK_ELLIPSIS on the N_PARAM node — typeeqast must
|
||||
* compare flags before recursing. */
|
||||
{ "variadic_vs_fixed",
|
||||
"type t = (*fn(xs: i32...) void | *fn(x: i32) void);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(xs: i32...) void => return 7;\n"
|
||||
" case *fn(x: i32) void => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* f) Drew row 7: param names DO NOT participate. The variant
|
||||
* has param name `b`; the case pattern has `a`. Post-fix
|
||||
* typeeqast walks param .lhs only, ignoring .str. Mirrors
|
||||
* harec ref/harec/src/types.c:589-615 which never reads
|
||||
* param_a->name in type_equal. */
|
||||
{ "param_name_only",
|
||||
"type t = (*fn(b: i32) void | i32);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(a: i32) void => return 7;\n"
|
||||
" case i32 => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* g) Bonus: the io-vtable shape that motivated the fold —
|
||||
* a tagged of two fn-pointer signatures whose param shapes
|
||||
* differ (mirrors lib/io/io.ww:29-31 read vs close). The
|
||||
* pre-fix wwstage rejected the program because the case
|
||||
* pattern's *fn(...) didn't typeeqast any variant. */
|
||||
{ "io_vtable_shape",
|
||||
"type t = (*fn(s: *i32, n: i32) i32 | *fn(s: *i32) i32);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: t;\n"
|
||||
" match (v) {\n"
|
||||
" case *fn(s: *i32, n: i32) i32 => return 7;\n"
|
||||
" case *fn(s: *i32) i32 => return 3;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wctyfn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wctyfn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wctyfn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
/* timeout 180 per repo convention (cf. 761, 762); test/run does
|
||||
* not bound individual binaries. */
|
||||
snprintf(cmd, sizeof cmd, "timeout 180 %s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "typeeq_fn_ast: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"typeeq_fn_ast[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"typeeq_fn_ast: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("typeeq_fn_ast: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
* 768_io_types_run — project #94 fold-1 step (d) (drew Fold D) sentinel.
|
||||
* Pins lib/io/types.ww (ref/hare/io/types.ha) — `mode` / `whence`
|
||||
* enums, `error = !(errors.unsupported | underread | nomem)` union,
|
||||
* and the `reader` / `writer` / `closer` fn-type aliases. The
|
||||
* fold-eFinal FLIP targets the aliases at `stream` (= *vtable in
|
||||
* lib/io/stream.ww); the rows track. Hare splits the io module the
|
||||
* same way: stream.ha + types.ha share the `io` module.
|
||||
*
|
||||
* Each row is a self-contained `import io;` program: cstage `ww build`
|
||||
* compiles it, we run the binary and assert the exit. Rows that hit a
|
||||
* known wwstage checker gap (see SIBLINGS below) are cstage-only via
|
||||
* stage_mask — the bulk run both stages so the dir-enum concat through
|
||||
* types.ww + io.ww is exercised by ww_ww too.
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------------+--------------------------------------
|
||||
* enum_mode_value | io.mode (enum u8) member access +
|
||||
* | underlying numeric promotion. RDWR=3
|
||||
* | matches Hare's READ|WRITE bitfield
|
||||
* | (1 SSoT; see types.ww header note on
|
||||
* | the value spelling).
|
||||
* enum_whence_value | io.whence (enum i32) member access.
|
||||
* | END=2 matches ref/hare/io/types.ha:40.
|
||||
* reader_alias_param | `io.reader` usable as a fn-VALUE param
|
||||
* | type — the immediate use case the
|
||||
* | upcoming vtable port needs. dummy_read
|
||||
* | passed by-name, called through the
|
||||
* | alias, `is size` gate confirms the
|
||||
* | (size | io.eof | io.error) return
|
||||
* | shape parses + dispatches.
|
||||
* writer_alias_param | `io.writer` same shape; (size |
|
||||
* | io.error) return.
|
||||
* closer_alias_param | `io.closer` (void | io.error). Most
|
||||
* | minimal of the three — pins the alias
|
||||
* | resolves without depending on the
|
||||
* | tagged-return discriminant path.
|
||||
*
|
||||
* SIBLINGS (out of scope for #94 fold-1(d), filed inline):
|
||||
*
|
||||
* - #189: wwstage checker bails "let: not assignable" on
|
||||
* `let r: io.reader = some_fn_name;`
|
||||
* i.e. binding a fn-name to a fn-VALUE-typed let. cstage accepts.
|
||||
* Param + struct-field assignment work in both stages, so the io
|
||||
* vtable port is not blocked. PROBE ROUTES AROUND by using the
|
||||
* alias only at the param slot.
|
||||
*
|
||||
* - #190: wwstage match-arm on cross-module variant tag bails
|
||||
* "case: not a variant of scrutinee (io.eof)" / "(io.error)"
|
||||
* on `match (r(s,buf)) { case io.eof => ...; case let e: io.error
|
||||
* => ...; }`. Likely the same family as #178 (typeeqast layer
|
||||
* asymmetry). cstage accepts. PROBE ROUTES AROUND by using `is`
|
||||
* instead of `match` for the variant gate. The Hare-faithful
|
||||
* match form lands once that family is closed.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means either types.ww
|
||||
* stopped parsing/checking, an enum-value underlying narrowed, or the
|
||||
* fn-type alias resolution regressed.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want_exit;
|
||||
int stage_mask;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "enum_mode_value",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let m: io.mode = io.mode.RDWR;\n"
|
||||
" return m: i32;\n"
|
||||
"};\n",
|
||||
3,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "enum_whence_value",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let w: io.whence = io.whence.END;\n"
|
||||
" return w: i32;\n"
|
||||
"};\n",
|
||||
2,
|
||||
STAGE_CS | STAGE_WW },
|
||||
/* reader/writer/closer alias rows. Fold-e1 re-targeted these aliases
|
||||
* from *stream → vstream (= *vtable); the test fixtures track. The
|
||||
* callee receivers are typed io.stream and the call sites build a
|
||||
* stub vtable to take its address from. The vtable values are inert
|
||||
* — only the dispatch type-shape is exercised. */
|
||||
{ "reader_alias_param",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn dummy_read(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
|
||||
" return 0: size;\n"
|
||||
"};\n"
|
||||
"fn use_reader(r: io.reader, s: io.stream, buf: []u8) i32 = {\n"
|
||||
" let v = r(s, buf);\n"
|
||||
" if (v is size) { return 11; };\n"
|
||||
" return -1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let buf: [4]u8;\n"
|
||||
" let bs: []u8 = buf[0:4];\n"
|
||||
" return use_reader(dummy_read, &vt, bs);\n"
|
||||
"};\n",
|
||||
11,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "writer_alias_param",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn dummy_write(s: io.stream, buf: []u8) (size | io.error) = {\n"
|
||||
" return buf.len: size;\n"
|
||||
"};\n"
|
||||
"fn use_writer(w: io.writer, s: io.stream, buf: []u8) i32 = {\n"
|
||||
" let v = w(s, buf);\n"
|
||||
" if (v is size) { return 22; };\n"
|
||||
" return -1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" let buf: [7]u8;\n"
|
||||
" let bs: []u8 = buf[0:7];\n"
|
||||
" return use_writer(dummy_write, &vt, bs);\n"
|
||||
"};\n",
|
||||
22,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "closer_alias_param",
|
||||
"package main;\n"
|
||||
"import io;\n"
|
||||
"fn dummy_close(s: io.stream) (void | io.error) = {\n"
|
||||
" return void;\n"
|
||||
"};\n"
|
||||
"fn use_closer(c: io.closer, s: io.stream) i32 = {\n"
|
||||
" let _v = c(s);\n"
|
||||
" return 33;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let vt: io.vtable;\n"
|
||||
" return use_closer(dummy_close, &vt);\n"
|
||||
"};\n",
|
||||
33,
|
||||
STAGE_CS | STAGE_WW },
|
||||
};
|
||||
|
||||
static int
|
||||
build_with(const char *driver, const char *src_path, const char *outbin,
|
||||
const char *cwd)
|
||||
{
|
||||
char cmd[2048];
|
||||
/* explicit -o keeps both the binary and <stem>.sepwork inside the
|
||||
* per-row tmpdir so rm -rf tmpdir reclaims the sep scratch */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s build -I %s/lib -o %s %s 2>/dev/null",
|
||||
driver, cwd, outbin, src_path);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const struct row *r = &rows[i];
|
||||
|
||||
char src[128], tmpdir[64], outbin[160], rmcmd[256];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/iotyp_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/iotyp_%d_%d.ww",
|
||||
tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); fail++; total++; continue; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
if (r->stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
if (build_with(cdrv, src, outbin, cwd) != 0) {
|
||||
fprintf(stderr,
|
||||
"io_types_run[cs][%s]: build failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else {
|
||||
int got = runwait(outbin);
|
||||
if (got != r->want_exit) {
|
||||
fprintf(stderr,
|
||||
"io_types_run[cs][%s]: rc=%d want=%d\n",
|
||||
r->label, got, r->want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_ww && (r->stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
if (build_with(wdrv, src, outbin, cwd) != 0) {
|
||||
fprintf(stderr,
|
||||
"io_types_run[ww][%s]: build failed\n",
|
||||
r->label);
|
||||
fail++;
|
||||
} else {
|
||||
int got = runwait(outbin);
|
||||
if (got != r->want_exit) {
|
||||
fprintf(stderr,
|
||||
"io_types_run[ww][%s]: rc=%d want=%d\n",
|
||||
r->label, got, r->want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"io_types_run: %d/%d rows failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("io_types_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
/*
|
||||
* 780_fmt_mods_run — io fold-2 (#5) commit-2 sentinel. Pins the
|
||||
* modifier-formatting machinery (rawleni64 / rawlenstr / rawlenf64 /
|
||||
* rawlen / formatraw / formatone) in lib/fmt/fmt.ww: formatfield takes
|
||||
* `*mods` and routes through formatone so the {:mods} placeholder
|
||||
* honours width / alignment / pad / sign / base / prec. #5 graduated
|
||||
* fprintf's sink io.stream -> io.handle; these rows drive it over the
|
||||
* FILE arm (`fd: io.file` widens to io.handle).
|
||||
*
|
||||
* Each row imports fmt + os + io, opens a per-process /tmp output file,
|
||||
* calls fprintf with a {:mods} format string + one arg, and asserts the
|
||||
* exact byte content read back.
|
||||
*
|
||||
* Cstage-only per row. #209 (the wwstage checker bail on fmt's
|
||||
* spread-union match-arms) is now CLOSED; the residual blocker is the
|
||||
* pre-existing cgen cluster #226 (io.read error-remap nominal-identity)
|
||||
* + #227 (spread-union widen/return-ABI). Identical carve-out to
|
||||
* 777_fmt_handle_run (cited there). Byte-id graduates when #226 (+#227)
|
||||
* land.
|
||||
*
|
||||
* row | format | arg | expect | exit
|
||||
* -----------------+------------+--------------+-----------+-----
|
||||
* width | "{:5}" | 42i64 | " 42" | 50
|
||||
* precision | "{:.3}" | "hello" | "hel" | 51
|
||||
* base_hex | "{:x}" | 255i64 | "ff" | 52
|
||||
* sign_plus | "{:+}" | 7i64 | "+7" | 53
|
||||
* zero_pad | "{:_05}" | 42i64 | "00042" | 54
|
||||
*
|
||||
* Modifier surface exercised (subset of scanmods at fmt.ww):
|
||||
*
|
||||
* width digits 1..9 — RIGHT-align pad to N chars
|
||||
* prec '.' + digits — str truncation / int min-digits
|
||||
* base 'x' — strconv.base.HEX_LOWER
|
||||
* neg '+' — emit '+' on non-negative
|
||||
* pad '_<rune>' — pad rune override (default space when ':')
|
||||
*
|
||||
* Drew NaN/Inf signoff (carry from task #58): f64 arm not exercised
|
||||
* here (rows pin i64+str+bool fastpaths); follow-on fold can add f64
|
||||
* once Drew's strconv.f64tos shortest-G stays static.
|
||||
*
|
||||
* IMPORT-ORDER WORKAROUND (sibling task #208, pre-existing): every row
|
||||
* places `import os;` FIRST for the same reason 776/777 document
|
||||
* (wwstage checker ordering-sensitivity on os.tryread/trywrite/
|
||||
* tryopen's bare `return r;` over a tagged return type).
|
||||
*
|
||||
* DEFERRALS / RELATED (NOT addressed by these rows):
|
||||
*
|
||||
* - #226 (io.read error-remap nominal-identity) + #227 (spread-union
|
||||
* widen/return-ABI): block STAGE_WW here, same as 777_fmt_handle_run.
|
||||
* #209 (the checker bail) is closed; these cgen folds are the
|
||||
* residual. Cstage-only until they land.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
|
||||
* combined.ww (grep confirmed), so the #5 fmt graduation does NOT
|
||||
* require a Makefile regen (#110); only test paths see the machinery.
|
||||
* 990-997 byte-id gates stay green by virtue of fmt being test-only on
|
||||
* the bootstrap surface.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means formatone /
|
||||
* formatraw / rawlen miscomputed bytes, formatfield regressed its
|
||||
* *mods route, scanmods/modsinit drifted, or the strconv.u64tos base
|
||||
* path miscompiled under fprintf over the io.handle file arm.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want_exit;
|
||||
int stage_mask;
|
||||
int byte_id;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "width",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtm_780_a\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintf(fd: io.file, \"{:5}\", 42i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 5i64) { return 93; };\n"
|
||||
" if (nw != 5: size) { return 94; };\n"
|
||||
" if (buf[0] != 32u8 || buf[1] != 32u8 || buf[2] != 32u8 || buf[3] != 52u8 || buf[4] != 50u8) { return 95; };\n"
|
||||
" return 50;\n"
|
||||
"};\n",
|
||||
50,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "precision",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtm_780_b\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintf(fd: io.file, \"{:.3}\", \"hello\");\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 3i64) { return 93; };\n"
|
||||
" if (nw != 3: size) { return 94; };\n"
|
||||
" if (buf[0] != 104u8 || buf[1] != 101u8 || buf[2] != 108u8) { return 95; };\n"
|
||||
" return 51;\n"
|
||||
"};\n",
|
||||
51,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "base_hex",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtm_780_c\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintf(fd: io.file, \"{:x}\", 255i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 2i64) { return 93; };\n"
|
||||
" if (nw != 2: size) { return 94; };\n"
|
||||
" if (buf[0] != 102u8 || buf[1] != 102u8) { return 95; };\n"
|
||||
" return 52;\n"
|
||||
"};\n",
|
||||
52,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "sign_plus",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtm_780_d\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintf(fd: io.file, \"{:+}\", 7i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 2i64) { return 93; };\n"
|
||||
" if (nw != 2: size) { return 94; };\n"
|
||||
" if (buf[0] != 43u8 || buf[1] != 55u8) { return 95; };\n"
|
||||
" return 53;\n"
|
||||
"};\n",
|
||||
53,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "zero_pad",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtm_780_e\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintf(fd: io.file, \"{:_05}\", 42i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 5i64) { return 93; };\n"
|
||||
" if (nw != 5: size) { return 94; };\n"
|
||||
" if (buf[0] != 48u8 || buf[1] != 48u8 || buf[2] != 48u8 || buf[3] != 52u8 || buf[4] != 50u8) { return 95; };\n"
|
||||
" return 54;\n"
|
||||
"};\n",
|
||||
54,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the
|
||||
* source (filed task #15), so each row's build leaves
|
||||
* <src>.{combined.ww,s,o} + bare exe alongside. Mirror of 777's. */
|
||||
static void
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[640];
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
||||
rmdir(tmpdir);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
|
||||
const char *src)
|
||||
{
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null",
|
||||
tmpdir, driver, cwd, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[512], base[64], outbin[768];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/fhm_%d_d_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main780");
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
||||
mkdir(tmpdir, 0755);
|
||||
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
|
||||
int rc;
|
||||
int br = build_via_driver(driver, tmpdir, cwd, src);
|
||||
if (br == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
rc = runwait(outbin);
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
cleanup_tmp(tmpdir, base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
int got = run_row(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"fmt_mods_run[cs][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
int got = run_row(wdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"fmt_mods_run[ww][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "fmt_mods_run: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "fmt_mods_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("fmt_mods_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
/*
|
||||
* 781_fmt_compositions_run — io fold-2 (#5) commit-2 sentinel. Pins the
|
||||
* composition wrappers in lib/fmt/fmt.ww — fprintln / fprintfln /
|
||||
* bsprintf / asprintf — plus the memio accessor (memio.string in
|
||||
* lib/memio/memio.ww) they read back through. #5 graduated the
|
||||
* fprint-family sink io.stream -> io.handle; the file-arm rows drive
|
||||
* fprintln / fprintfln over `fd: io.file`, the stream-arm rows over a
|
||||
* memio.fixed io.stream — both arms of io.handle.
|
||||
*
|
||||
* memio.string is the direct enabler: bsprintf reads its written prefix
|
||||
* via memio.string; asprintf reads its grown buffer via the same
|
||||
* accessor.
|
||||
*
|
||||
* row | what it pins
|
||||
* --------------------+--------------------------------------
|
||||
* fprintln_file_basic | fprintln(fd: io.file, 1, "hi", true)
|
||||
* | over the file arm — space-separator plus
|
||||
* | trailing newline byte content.
|
||||
* fprintfln_file_fmt | fprintfln(fd: io.file, "x={}", 7) over the
|
||||
* | file arm — placeholder substitution +
|
||||
* | trailing newline.
|
||||
* fprintln_basic | fmt.fprintln dispatched over a memio.fixed
|
||||
* | io.stream (stream arm) + read back via
|
||||
* | memio.string. Pins the composition symbol.
|
||||
* fprintfln_basic | fmt.fprintfln over memio.fixed + memio.string,
|
||||
* | same stream-arm rationale.
|
||||
* bsprintf_basic | bsprintf into a caller-supplied [16]u8
|
||||
* | buffer through memio.fixed; memio.string
|
||||
* | returns the prefix view. Asserts both the
|
||||
* | caller-buffer bytes AND the returned str view.
|
||||
* asprintf_basic | asprintf into a heap-grown str through
|
||||
* | memio.dynamic; memio.string + shrink-copy +
|
||||
* | io.close. Asserts the owned str bytes, then
|
||||
* | frees via os.free.
|
||||
*
|
||||
* Cstage-only per row (no STAGE_WW, no byte_id). #209 (the wwstage
|
||||
* checker bail `case: not a variant of scrutinee` + `match: variant not
|
||||
* handled (formattable)` on fmt's spread-union match-arms) is now CLOSED;
|
||||
* wwstage compiles fmt. The residual blocker is the pre-existing cgen
|
||||
* cluster #226 (io.read error-remap nominal-identity) + #227 (spread-union
|
||||
* widen/return-ABI). Identical carve-out to 777_fmt_handle_run +
|
||||
* 780_fmt_mods_run. Byte-id graduates when #226 (+#227) land.
|
||||
*
|
||||
* IMPORT-ORDER WORKAROUND (sibling task #208, pre-existing): every row
|
||||
* places `import os;` FIRST for the same reason 776/777/780 document
|
||||
* (wwstage checker ordering-sensitivity on os.tryread / trywrite /
|
||||
* tryopen's bare `return r;` over a tagged return type).
|
||||
*
|
||||
* DEFERRALS / RELATED (NOT addressed by these rows):
|
||||
*
|
||||
* - #226 (io.read error-remap nominal-identity) + #227 (spread-union
|
||||
* widen/return-ABI): block STAGE_WW here, same as 777/780. #209 (the
|
||||
* checker bail) is closed; these cgen folds are the residual.
|
||||
* Cstage-only until they land.
|
||||
* - #173 (TRY-on-tagged-return both-stages broken): bsprintf widens
|
||||
* nomem into io.error inline rather than `memio.fixed(buf)?` for
|
||||
* the same reason memio.ww does.
|
||||
*
|
||||
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
|
||||
* combined.ww (grep verified). memio IS embedded in w6c + wwdump
|
||||
* combined.ww but is unchanged by #5, so no regen rides along here.
|
||||
* 990/994/995 byte-id gates stay green.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means fprintln /
|
||||
* fprintfln dropped the newline byte, bsprintf returned a stale
|
||||
* memio.string view (intrusive cast broke), asprintf failed to
|
||||
* shrink-copy / close cleanly, or the underlying fprint / fprintf
|
||||
* primitives regressed over the io.handle sink.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want_exit;
|
||||
int stage_mask;
|
||||
int byte_id;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "fprintln_file_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtc_781_a\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintln(fd: io.file, 1i64, \"hi\", true);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 10i64) { return 93; };\n"
|
||||
" if (nw != 10: size) { return 94; };\n"
|
||||
" if (buf[0] != 49u8) { return 95; };\n"
|
||||
" if (buf[1] != 32u8) { return 96; };\n"
|
||||
" if (buf[2] != 104u8 || buf[3] != 105u8) { return 97; };\n"
|
||||
" if (buf[4] != 32u8) { return 98; };\n"
|
||||
" if (buf[9] != 10u8) { return 99; };\n"
|
||||
" return 60;\n"
|
||||
"};\n",
|
||||
60,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "fprintfln_file_fmt",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let path: str = \"/tmp/wwfmtc_781_b\";\n"
|
||||
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
|
||||
" if (fd < 0) { return 90; };\n"
|
||||
" let wr = fmt.fprintfln(fd: io.file, \"x={}\", 7i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" os.close(fd);\n"
|
||||
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
|
||||
" if (rd < 0) { return 92; };\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
|
||||
" os.close(rd);\n"
|
||||
" if (n != 4i64) { return 93; };\n"
|
||||
" if (nw != 4: size) { return 94; };\n"
|
||||
" if (buf[0] != 120u8 || buf[1] != 61u8 || buf[2] != 55u8 || buf[3] != 10u8) { return 95; };\n"
|
||||
" return 61;\n"
|
||||
"};\n",
|
||||
61,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "fprintln_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"import memio;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let vs: io.stream = &st.vt;\n"
|
||||
" let wr = fmt.fprintln(vs, 1i64, \"hi\", true);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" if (nw != 10: size) { return 92; };\n"
|
||||
" let view: str = memio.string(&st);\n"
|
||||
" if (view.len != 10) { return 93; };\n"
|
||||
" if (view[0] != 49u8) { return 94; };\n"
|
||||
" if (view[1] != 32u8) { return 95; };\n"
|
||||
" if (view[2] != 104u8 || view[3] != 105u8) { return 96; };\n"
|
||||
" if (view[9] != 10u8) { return 97; };\n"
|
||||
" return 64;\n"
|
||||
"};\n",
|
||||
64,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "fprintfln_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"import memio;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
|
||||
" let vs: io.stream = &st.vt;\n"
|
||||
" let wr = fmt.fprintfln(vs, \"x={}\", 7i64);\n"
|
||||
" let nw: size = 0;\n"
|
||||
" match (wr) {\n"
|
||||
" case let n: size => { nw = n; };\n"
|
||||
" case io.error => { return 91; };\n"
|
||||
" };\n"
|
||||
" if (nw != 4: size) { return 92; };\n"
|
||||
" let view: str = memio.string(&st);\n"
|
||||
" if (view.len != 4) { return 93; };\n"
|
||||
" if (view[0] != 120u8 || view[1] != 61u8) { return 94; };\n"
|
||||
" if (view[2] != 55u8 || view[3] != 10u8) { return 95; };\n"
|
||||
" return 65;\n"
|
||||
"};\n",
|
||||
65,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "bsprintf_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [16]u8;\n"
|
||||
" let r = fmt.bsprintf(buf[0:16], \"v={}\", 42i64);\n"
|
||||
" let view: str = \"\";\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { view = s; };\n"
|
||||
" case io.error => { return 90; };\n"
|
||||
" };\n"
|
||||
" if (view.len != 4) { return 91; };\n"
|
||||
" if (view[0] != 118u8 || view[1] != 61u8) { return 92; };\n"
|
||||
" if (view[2] != 52u8 || view[3] != 50u8) { return 93; };\n"
|
||||
" if (buf[0] != 118u8 || buf[3] != 50u8) { return 94; };\n"
|
||||
" return 62;\n"
|
||||
"};\n",
|
||||
62,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
{ "asprintf_basic",
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let view: str = fmt.asprintf(\"hello={}\", 9i64);\n"
|
||||
" if (view.len != 7) { return 90; };\n"
|
||||
" if (view[0] != 104u8 || view[1] != 101u8 || view[2] != 108u8) { return 91; };\n"
|
||||
" if (view[3] != 108u8 || view[4] != 111u8) { return 92; };\n"
|
||||
" if (view[5] != 61u8 || view[6] != 57u8) { return 93; };\n"
|
||||
" os.free(view.ptr: *void, view.len: u64);\n"
|
||||
" return 63;\n"
|
||||
"};\n",
|
||||
63,
|
||||
STAGE_CS | STAGE_WW, 1 },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the
|
||||
* source (task #15), so each row's build leaves <src>.{combined.ww,
|
||||
* s,o} + bare exe alongside. Mirror of 777/780's. */
|
||||
static void
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[640];
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
||||
rmdir(tmpdir);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
|
||||
const char *src)
|
||||
{
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null",
|
||||
tmpdir, driver, cwd, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[512], base[64], outbin[768];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/fhc_%d_d_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main781");
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
||||
mkdir(tmpdir, 0755);
|
||||
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
|
||||
int rc;
|
||||
int br = build_via_driver(driver, tmpdir, cwd, src);
|
||||
if (br == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
rc = runwait(outbin);
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
cleanup_tmp(tmpdir, base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
int got = run_row(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"fmt_compositions_run[cs][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
int got = run_row(wdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"fmt_compositions_run[ww][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "fmt_compositions_run: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "fmt_compositions_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("fmt_compositions_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* 782_strict_package — #24a strict-package gate, BOTH stages (w6c + w6c_ww).
|
||||
*
|
||||
* Every primary section must open with a `package` clause; a missing clause
|
||||
* on the first real decl is a hard error ("missing package clause"). An
|
||||
* imported (`//ww:module <path>`) or sep primary-reset region carries its
|
||||
* identity out-of-band and is exempt; an in-section `package` clause then
|
||||
* ASSERTS — its leaf must equal the path's last component ("package <x>
|
||||
* does not match import path <p>"). A bare `//ww:module-reset` opens a fresh
|
||||
* primary section that must itself re-declare a clause.
|
||||
*
|
||||
* Rule-10: every row runs on cstage `w6c` AND wwstage `w6c_ww`; both must
|
||||
* agree (accept/reject identically; diagnostic text differs by stage —
|
||||
* cstage `errorf` carries a position, wwstage `errmsg` is terser — so the
|
||||
* check is on a shared substring, not the whole line). Reject rows feed RAW
|
||||
* source: the `package` clause IS the unit under test, so there is no
|
||||
* auto-prepend here (unlike the migrated codegen fixtures).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) return 0;
|
||||
char line[4096];
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f))
|
||||
if (strstr(line, needle)) { found = 1; break; }
|
||||
fclose(f);
|
||||
return found;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int accept; /* 1 = must compile clean; 0 = must reject */
|
||||
const char *diag; /* reject: required stderr substring (NULL on accept) */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 1: a `package main;` executable section. */
|
||||
{ "pkg_main_accept",
|
||||
"package main;\nexport fn main() i32 = { return 0; };\n",
|
||||
1, NULL },
|
||||
/* 2: no clause → hard error on the first decl. */
|
||||
{ "no_clause_reject",
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0, "missing package clause" },
|
||||
/* 3: imported region — the clause leaf matches the path leaf. */
|
||||
{ "module_leaf_match_accept",
|
||||
"//ww:module foo\npackage foo;\nexport fn x() i32 = { return 0; };\n",
|
||||
1, NULL },
|
||||
/* 4: imported region — the clause leaf mismatches the path. */
|
||||
{ "module_leaf_mismatch_reject",
|
||||
"//ww:module foo\npackage bar;\nexport fn x() i32 = { return 0; };\n",
|
||||
0, "does not match import path" },
|
||||
/* 5: a comment-only file declares nothing, so no clause is required. */
|
||||
{ "comment_only_accept",
|
||||
"// just a comment, no decls\n",
|
||||
1, NULL },
|
||||
/* 6: a bare `//ww:module-reset` opens a fresh primary section; the
|
||||
* clause-less second section is rejected (the #84/#24a boundary). */
|
||||
{ "reset_second_section_reject",
|
||||
"package main;\nexport fn a() i32 = { return 0; };\n"
|
||||
"//ww:module-reset\nexport fn b() i32 = { return 0; };\n",
|
||||
0, "missing package clause" },
|
||||
{ NULL, NULL, 0, NULL },
|
||||
};
|
||||
|
||||
/* Compile r->src with `stage` (a w6c-family binary); verify accept/reject
|
||||
* and, on reject, the diagnostic substring. Returns 0 on success. */
|
||||
static int
|
||||
check_stage(const char *stage, const char *tag, const struct row *r,
|
||||
const char *tmpdir)
|
||||
{
|
||||
char src[256], sout[256], errf[256], cmd[2048];
|
||||
snprintf(src, sizeof src, "%s/sp_%s.ww", tmpdir, r->label);
|
||||
snprintf(sout, sizeof sout, "%s/sp_%s.s", tmpdir, r->label);
|
||||
snprintf(errf, sizeof errf, "%s/sp_%s.err", tmpdir, r->label);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { fprintf(stderr, "782 FAIL: write %s\n", src); return 1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>%s", stage, sout, src, errf);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
if (r->accept) {
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "782 FAIL: [%s][%s] expected ACCEPT, "
|
||||
"rejected (rc=%d)\n", r->label, tag, rc);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "782 FAIL: [%s][%s] expected REJECT, "
|
||||
"accepted\n", r->label, tag);
|
||||
return 1;
|
||||
}
|
||||
if (!file_contains(errf, r->diag)) {
|
||||
fprintf(stderr, "782 FAIL: [%s][%s] rejected but no `%s` "
|
||||
"on stderr\n", r->label, tag, r->diag);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cs[1100], ww[1100], tmpdir[64], rmcmd[160];
|
||||
snprintf(cs, sizeof cs, "%s/w6c", bin);
|
||||
snprintf(ww, sizeof ww, "%s/w6c_ww", bin);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/sp782_%d", getpid());
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
/* wwstage twin is gated on its presence (rule 14 / 993 pattern). */
|
||||
int have_ww = (access(ww, X_OK) == 0);
|
||||
|
||||
int fail = 0;
|
||||
for (int i = 0; rows[i].label; i++) {
|
||||
fail += check_stage(cs, "cs", &rows[i], tmpdir);
|
||||
if (have_ww)
|
||||
fail += check_stage(ww, "ww", &rows[i], tmpdir);
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
if (fail) {
|
||||
fprintf(stderr, "782: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("strict_package: 6 rows (accept/reject + leaf-assert + bare-reset) "
|
||||
"agree on cstage w6c%s (#24a)\n", have_ww ? " and wwstage w6c_ww" : "");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* 788_type_value_shadow_run -- SLIMMED to the one irreducible asymmetric row.
|
||||
*
|
||||
* The #225 type/value-namespace corpus migrated to fold-2 homes (#5-C6): the
|
||||
* value rows (param_and_cast, cast_local) -> test/lang/type_value_shadow_test.ww
|
||||
* (@test + T2 byte-id).
|
||||
*
|
||||
* value_not_type_neg CANNOT move to a runww //ww:error carrier: both stages
|
||||
* REJECT a value name in type position with no same-named type, but with
|
||||
* DIVERGENT diagnostics -- cstage emits a clean "unknown type 'q'" while wwstage
|
||||
* trips an internal "asserttyped: cast" guard (filed task #29: a checker-quality
|
||||
* cs!=ww, wwstage to align DOWN to cstage's clean diagnostic per rule 10). No
|
||||
* honest shared substring exists, so the #20-non-vacuity ERROR carrier (which
|
||||
* demands the SAME substring in both stages) is inexpressible. A build-reject
|
||||
* also emits no .s, so there is no byte-id surface (ken). It stays a slim C pin
|
||||
* asserting rc!=0 on BOTH stages.
|
||||
*
|
||||
* Non-vacuity self-check (the pin CAN go RED): the SAME name `q` is also built
|
||||
* as a real TYPE in `valid_src`, which BOTH stages must ACCEPT (rc==0). If the
|
||||
* harness reported reject for everything, that control would fail; it proves the
|
||||
* pin discriminates accept-vs-reject, not "all builds error". Mutation-checked:
|
||||
* giving `value_not_type_neg` a real `type q = i32;` flips both stages to accept
|
||||
* -> the reject leg goes RED (verified).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* value_not_type_neg: `q` is a VALUE (SK_VAR) in type/cast position with no
|
||||
* same-named type in any scope -- both stages reject (cstage "unknown type
|
||||
* 'q'", wwstage asserttyped:cast, #29). */
|
||||
static const char *reject_src =
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: i32 = 3;\n"
|
||||
" return q: q;\n"
|
||||
"};\n";
|
||||
|
||||
/* Vacuity control: the SAME name `q` bound as a real TYPE -- both stages accept.
|
||||
* Proves the pin distinguishes accept from reject. */
|
||||
static const char *valid_src =
|
||||
"package main;\n"
|
||||
"type q = i32;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return 3: q;\n"
|
||||
"};\n";
|
||||
|
||||
/* build_only -- write `src`, build via `driver` (no run). Returns the build
|
||||
* exit code (0 = accepted, nonzero = rejected). */
|
||||
static int
|
||||
build_only(const char *driver, const char *src, int seq)
|
||||
{
|
||||
char tmpdir[96], srcp[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tvs_%d_%d", getpid(), seq);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(srcp, sizeof srcp, "%s/c.ww", tmpdir);
|
||||
snprintf(outbin, sizeof outbin, "%s/c", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(srcp, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "timeout 20 %s build -o %s %s >/dev/null 2>&1",
|
||||
driver, outbin, srcp);
|
||||
int brc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return brc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[2120], wdrv[2120];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int fail = 0, seq = 0;
|
||||
|
||||
/* reject leg: both stages must REJECT (rc!=0). */
|
||||
if (build_only(cdrv, reject_src, seq++) == 0) {
|
||||
fprintf(stderr, "type_value_shadow[cs]: value_not_type_neg ACCEPTED (want reject)\n");
|
||||
fail++;
|
||||
}
|
||||
if (wwpresent && build_only(wdrv, reject_src, seq++) == 0) {
|
||||
fprintf(stderr, "type_value_shadow[ww]: value_not_type_neg ACCEPTED (want reject)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* vacuity control: both stages must ACCEPT (rc==0). */
|
||||
if (build_only(cdrv, valid_src, seq++) != 0) {
|
||||
fprintf(stderr, "type_value_shadow[cs]: vacuity control REJECTED (pin may be vacuous)\n");
|
||||
fail++;
|
||||
}
|
||||
if (wwpresent && build_only(wdrv, valid_src, seq++) != 0) {
|
||||
fprintf(stderr, "type_value_shadow[ww]: vacuity control REJECTED (pin may be vacuous)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "type_value_shadow: %d checks failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("type_value_shadow: value_not_type_neg pin ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
/*
|
||||
* 832_tuple_elem_overlong — an OVERLONG array literal in a TUPLE ELEMENT
|
||||
* position is INVALID ww; BOTH stages must LOUDLY REJECT at check time
|
||||
* (#20, the #12 + #106 follow-up; rob spec .ai/rob-20-spec.md). #12/#106
|
||||
* wired checkarrlitfits for a DIRECT array lhs (+ alias) at the decl /
|
||||
* return / call-arg positions, but the tuple-element position was never
|
||||
* wired — `let t: ([2]int, i32) = ([1,2,3], 5)` over-fills the [2]int slot
|
||||
* with 3 initialisers.
|
||||
*
|
||||
* WWSTAGE-ONLY fix — cstage already rejects (tuple element-wise
|
||||
* type_assignable counts elements: `[3]int` vs `[2]int`). Pre-fix wwstage
|
||||
* divergence (the mutation-sanity target):
|
||||
* - tuple_arr_over `let t:([2]int,i32)=([1,2,3],5)` : ww silently ACCEPTED
|
||||
* - tuple_nested_arr `let t:([2][3]int,i32)=([...x3],5)` : the outer [2]
|
||||
* slot over-filled by 3 sub-arrays (checkarrlitfits
|
||||
* nested-array recursion under the tuple walk)
|
||||
*
|
||||
* The diagnostic TEXT may differ between stages ("over-fill" vs "not
|
||||
* assignable") — byte-id-blind (stderr is not asm). Both REJECT and emit no
|
||||
* asm; selfhost has no overlong tuple-elements, so 990-997 byte-id is
|
||||
* untouched. Do NOT chase message parity.
|
||||
*
|
||||
* #24 (DISP-B broad reject, rob spec .ai/rob-24-spec.md): a tuple whose
|
||||
* ELEMENT is a composite (array / struct / nested-tuple >8B) cannot ride the
|
||||
* 8B cursor slot (#60 layout) — it silently DROPS on construction and SEGVs
|
||||
* on the t.N[i] read. Both stages now REJECT such a type at N_TTUPLE
|
||||
* resolution (kind ∈ {TY_ARRAY, TY_STRUCT, TY_TUPLE} after TY_NAMED chase),
|
||||
* converting two silent miscompiles into one loud checker error. This FLIPS
|
||||
* the former tuple_arr_exact / tuple_nested_exact positive controls to the
|
||||
* neg table (their types are now outlawed) and adds slice/str/tagged-element
|
||||
* positive controls proving DISP-B does NOT over-reject the inline-header
|
||||
* kinds. Full inline support deferred to task #60 / DISP-A.
|
||||
*
|
||||
* neg row | shape | gate
|
||||
* -------------------+----------------------------------------------+--------
|
||||
* tuple_arr_over | let t:([2]int,i32)=([1,2,3],5) | b. FAIL
|
||||
* tuple_nested_arr | let t:([2][3]int,i32)=([[..],[..],[..]],5) | b. FAIL
|
||||
* tuple_in_tuple | let t:([2]int,([2]int,i32))=([..],([1,2,3],.))| #26 FAIL
|
||||
* tuple_return_over | fn()([2]int,i32){return([1,2,3],5)} | #25 FAIL
|
||||
* tuple_return_nested| fn()([2]int,([2]int,i32)){return(..,([..3],.))| #25 FAIL
|
||||
* tuple_arr_exact | let t:([2]int,i32)=([1,2],5) | #24 FAIL
|
||||
* tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)) | #24 FAIL
|
||||
* tuple_struct_elem | type P=struct{x:int}; let t:(P,i32)=(P{x=1},5)| #24 FAIL
|
||||
*
|
||||
* pos row | shape | want
|
||||
* -------------------+----------------------------------------------+------
|
||||
* tuple_scalar | let t:(i32,i32)=(1,2); t.1 | 2
|
||||
* tuple_slice_elem | let t:([]u8,i32)=(a,5); t.1 | 5
|
||||
* tuple_str_elem | let t:(str,i32)=("hi",7); t.1 | 7
|
||||
* tuple_tagged_elem | let t:((void|size),i32)=(3,9); t.1 | 9
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* a scalar-only tuple — all elements ride the 8B slot. */
|
||||
{ "tuple_scalar",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: (i32, i32) = (1, 2);\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* #24 positive control — a SLICE element is DISP-B-allowed (its 24B
|
||||
* header rides the cursor). Must NOT be over-rejected. Readout is the
|
||||
* scalar t.1 (=5). */
|
||||
{ "tuple_slice_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
|
||||
"\tlet t: ([]u8, i32) = (a, 5);\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* #24 positive control — a STR element is DISP-B-allowed (24B header).
|
||||
* Readout is the scalar t.1 (=7). */
|
||||
{ "tuple_str_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: (str, i32) = (\"hi\", 7);\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
/* #24 positive control — a TAGGED-UNION element is DISP-B-allowed (its
|
||||
* tag+payload box rides the slot). Readout is the scalar t.1 (=9). */
|
||||
{ "tuple_tagged_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: ((void | size), i32) = (3, 9);\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
};
|
||||
|
||||
/* An overlong array literal in a tuple element — both stages must FAIL the
|
||||
* build (loud checker diagnostic, not silent accept). */
|
||||
static const char *neg[] = {
|
||||
/* tuple_arr_over (the #20 repro) — [2]int slot gets 3 inits. */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: ([2]int, i32) = ([1, 2, 3], 5);\n"
|
||||
"\treturn t.0[1]: i32;\n"
|
||||
"};\n",
|
||||
/* tuple_nested_arr — outer [2] slot over-filled by 3 sub-arrays
|
||||
* (checkarrlitfits recursion under the tuple walk). */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: ([2][3]int, i32) = "
|
||||
"([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 5);\n"
|
||||
"\treturn t.0[0][0]: i32;\n"
|
||||
"};\n",
|
||||
/* tuple_in_tuple (#26) — nested tuple element; inner [2]int over-
|
||||
* filled by 3. The walk must RECURSE the nested tuple (let pos). */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: ([2]int, ([2]int, i32)) = ([1, 2], ([3, 4, 5], 6));\n"
|
||||
"\treturn t.0[0]: i32;\n"
|
||||
"};\n",
|
||||
/* tuple_return_over (#25) — overlong [2]int in a tuple RETURN. */
|
||||
"package main;\n"
|
||||
"fn f() ([2]int, i32) = {\n"
|
||||
"\treturn ([1, 2, 3], 5);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t = f();\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
/* tuple_return_nested (#25 path × #26 recursion) — nested tuple in a
|
||||
* RETURN, inner [2]int over-filled. */
|
||||
"package main;\n"
|
||||
"fn f() ([2]int, ([2]int, i32)) = {\n"
|
||||
"\treturn ([1, 2], ([3, 4, 5], 6));\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t = f();\n"
|
||||
"\treturn t.0[0]: i32;\n"
|
||||
"};\n",
|
||||
/* tuple_arr_exact (#24) — was a GREEN positive control; the DISP-B
|
||||
* broad reject now OUTLAWS an ARRAY tuple element (silent-drop on
|
||||
* construction + segv on t.0[i] read). MIGRATED to the neg table. */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: ([2]int, i32) = ([1, 2], 5);\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
/* tuple_nested_exact (#24) — was a GREEN positive control; a NESTED
|
||||
* TUPLE element is now outlawed by DISP-B. MIGRATED to the neg table. */
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n"
|
||||
"\treturn t.0;\n"
|
||||
"};\n",
|
||||
/* tuple_struct_elem (#24) — a STRUCT tuple element is now a checker
|
||||
* loud (was a cgen "unsupported field-read shape" loud). */
|
||||
"package main;\n"
|
||||
"type P = struct { x: int };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: (P, i32) = (P { x = 1 }, 5);\n"
|
||||
"\treturn t.1;\n"
|
||||
"};\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/teo_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/teo_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/teo_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* build_should_fail — an overlong tuple-element array must error on
|
||||
* `driver`; returns 0 when the build correctly FAILS, non-zero when it
|
||||
* wrongly succeeded. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/teon_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/teon_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/teon_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int nn = (int)(sizeof neg / sizeof neg[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tuple_elem_overlong: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"tuple_elem_overlong[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path, neg[i],
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr,
|
||||
"tuple_elem_overlong[%s][neg%d]: built ok, "
|
||||
"expected a loud error\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tuple_elem_overlong: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tuple_elem_overlong: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
/*
|
||||
* 834_tagged_arr_variant — constructing an ARRAY-typed payload into a
|
||||
* tagged-union variant is unwired in cgen and must be LOUDLY REJECTED at
|
||||
* check time on BOTH stages (#5 / #60, rob spec .ai/rob-24sib-spec.md).
|
||||
*
|
||||
* cstage zero-filled the array payload at box materialization (a silent
|
||||
* MOVQ $0 drop — `let v:V=[7,8,9]` then `match` read 0); wwstage only
|
||||
* loud at the dead match arm (errbadcase), SILENTLY accepting the
|
||||
* construct. The fix is a construct-site checker reject keyed on: target
|
||||
* chases TY_TAGGED AND the variant the source selects chases TY_ARRAY.
|
||||
* cstage tagged_array_variant (check.c) + wwstage taggedarrayvariantctor
|
||||
* (check.ww), at the let-init and return assignability paths.
|
||||
*
|
||||
* The tagged TYPE-decl WITH an array variant stays legal — only the
|
||||
* array-variant CONSTRUCTION is refused. The narrow_in_wide positive
|
||||
* (944's `(void|size|[5]size)`, boxing the scalar `size`) proves the
|
||||
* type-decl + scalar ctor remain accepted. struct / slice / scalar / str
|
||||
* variants all WORK (sib2/sib3) and are positive controls. Both stages
|
||||
* REJECT the neg rows and emit no asm; selfhost never boxes an array, so
|
||||
* 990-997 byte-id is untouched. Diagnostic TEXT is byte-id-blind; do not
|
||||
* chase message parity. Full array-block-store fix deferred to task #6.
|
||||
*
|
||||
* neg row | shape | gate
|
||||
* ----------------+---------------------------------------------+------
|
||||
* arrlit_ctor | V=([3]int|int); let v:V=[7,8,9] | FAIL
|
||||
* arrvar_ctor | let a:[3]int=..; let v:V=a (variable src) | FAIL
|
||||
* arr_return | fn()V { return [7,8,9]; } | FAIL
|
||||
*
|
||||
* pos row | shape | want
|
||||
* ----------------+---------------------------------------------+------
|
||||
* struct_variant | V=(P|int); v=P{x=1,y=2}; match -> p.y | 2
|
||||
* slice_variant | V=([]int|int); v=s; match -> s[2] | 9
|
||||
* scalar_variant | V=(int|bool); v=5; match -> n | 5
|
||||
* str_variant | V=(str|int); v="hi"; match -> s.len | 2
|
||||
* narrow_in_wide | big=(void|size|[5]size); m=7:size; match | 7
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* struct variant box + match (sib2 — works + byte-id). */
|
||||
{ "struct_variant",
|
||||
"package main;\n"
|
||||
"type P = struct { x: int, y: int };\n"
|
||||
"type V = (P | int);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: V = P { x = 1, y = 2 };\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let p: P => return p.y: i32;\n"
|
||||
"\tcase int => return 0;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* slice variant box + match (sib3 — works + byte-id). */
|
||||
{ "slice_variant",
|
||||
"package main;\n"
|
||||
"type V = ([]int | int);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [3]int = [7, 8, 9];\n"
|
||||
"\tlet a: []int = hb;\n"
|
||||
"\tlet v: V = a;\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let s: []int => return s[2]: i32;\n"
|
||||
"\tcase int => return 0;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
|
||||
/* scalar variant box + match. */
|
||||
{ "scalar_variant",
|
||||
"package main;\n"
|
||||
"type V = (int | bool);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: V = 5;\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let n: int => return n: i32;\n"
|
||||
"\tcase bool => return 0;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* str variant box + match (24B header payload). */
|
||||
{ "str_variant",
|
||||
"package main;\n"
|
||||
"type V = (str | int);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: V = \"hi\";\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let s: str => return s.len: i32;\n"
|
||||
"\tcase int => return 0;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* narrow_in_wide — 944's green shape: a tagged union that DECLARES an
|
||||
* array variant ([5]size) but boxes only the narrow scalar `size`.
|
||||
* Proves the type-decl + scalar ctor stay legal under the reject. */
|
||||
{ "narrow_in_wide",
|
||||
"package main;\n"
|
||||
"type big = (void | size | [5]size);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet m: big = 7: size;\n"
|
||||
"\tmatch (m) {\n"
|
||||
"\tcase let s: size => return s: i32;\n"
|
||||
"\tcase => return 9;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
/* Constructing an array payload into a tagged variant — both stages must
|
||||
* FAIL the build (loud construct-site checker reject). */
|
||||
static const char *neg[] = {
|
||||
/* arrlit_ctor — array LITERAL boxed into ([3]int|int). */
|
||||
"package main;\n"
|
||||
"type V = ([3]int | int);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: V = [7, 8, 9];\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
/* arrvar_ctor — array VARIABLE boxed (not literal-specific). */
|
||||
"package main;\n"
|
||||
"type V = ([3]int | int);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [3]int = [7, 8, 9];\n"
|
||||
"\tlet v: V = a;\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
/* arr_return — array boxed into a tagged return type. */
|
||||
"package main;\n"
|
||||
"type V = ([3]int | int);\n"
|
||||
"fn f() V = {\n"
|
||||
"\treturn [7, 8, 9];\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tav_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/tav_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tav_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* build_should_fail — the array-variant construct must error on `driver`;
|
||||
* returns 0 when the build correctly FAILS, non-zero when it wrongly
|
||||
* succeeded. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tavn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/tavn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tavn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int nn = (int)(sizeof neg / sizeof neg[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tagged_arr_variant: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"tagged_arr_variant[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path, neg[i],
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr,
|
||||
"tagged_arr_variant[%s][neg%d]: built ok, "
|
||||
"expected a loud reject\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tagged_arr_variant: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_arr_variant: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* 835_nested_union_int_box — two reject-align fixes in the nested-union /
|
||||
* widen-subset family. Both close SILENT wwstage miscompiles by aligning
|
||||
* ww DOWN to cstage's existing loud.
|
||||
*
|
||||
* #23 (drew spec .ai/drew-23-spec.md) — a bare untyped-int return/assign
|
||||
* into a tagged union with NO DIRECT variant able to hold an int (the int
|
||||
* is reachable only via a NESTED union, or no slot exists at all). ww's
|
||||
* isuntypedint tagged sub-loop fell through to a permissive `*confident=
|
||||
* false; return true;` so the caller never flagged it — ww silently built
|
||||
* a tag=0/payload box with no inner-tag wrapper (malformed). The fix:
|
||||
* confident-reject when no direct variant is numeric-or-enum (mirror
|
||||
* cstage type.c:343 `return 0`), routing through errnotassign. An ENUM
|
||||
* variant is kept ACCEPTED (new N_TENUM check, mirrors cstage type_isnum
|
||||
* (enum)=true) — A3 is the trap: dropping it would flip an enum-variant
|
||||
* union to reject while cstage accepts. wwstage-ONLY; check.c unchanged.
|
||||
*
|
||||
* S1 / #35 (ken census .ai/miscompile-census.md) — a widen-SUBSET cast in
|
||||
* a tagged construct: `return true: inner` where inner=(int|bool) boxed
|
||||
* into outer=(int|bool|str). The cast target is a TAGGED union that is NOT
|
||||
* dst, and the inner-variant tag is never remapped to dst's index, so ww
|
||||
* cgen's scalar arm emitted tag=0 — running the int arm on a bool value
|
||||
* (census S1 returned 70, expected 11). cstage loud-stops at cgen.c:2721-
|
||||
* 2723 (#35). wwstage gains the twin in cgwidentaggedstorebp (cgenutil.ww).
|
||||
* A 2ND-VARIANT value (bool) is mandatory: `5:inner` is benign (int is
|
||||
* variant 0 in both, tag 0 coincidentally right). Faithful inner->outer
|
||||
* tag remap = the #23/#40 widen-subset feature, deferred post-CSP.
|
||||
*
|
||||
* neg row | shape | gate
|
||||
* -------------+------------------------------------------------+------
|
||||
* R1_nested | inner=(int|bool); outer=(inner|str); ret 5 | FAIL
|
||||
* R2_noslot | u=(str|bool); ret 5 | FAIL
|
||||
* R3_struct | A=struct{v:int}; u=(A|str); ret 5 | FAIL
|
||||
* S1_subcast | return true: inner (inner=(int|bool)->outer) | FAIL
|
||||
*
|
||||
* pos row | shape | want
|
||||
* -------------+------------------------------------------------+------
|
||||
* A1_direct | u=(int|str); ret 5; match int -> n | 5
|
||||
* A2_aliased | myint=int; u=(myint|str); ret 5 | 0
|
||||
* A3_enum | col=enum{RED,GREEN}; u=(col|str); ret 5 [trap] | 0
|
||||
* A4_unionsrc | outer=(inner|str); mk(x:inner) outer = x | 0
|
||||
* A5_subset | sub=(int|uint); sup=(int|uint|str); ret x | 0
|
||||
*
|
||||
* Diagnostic TEXT is byte-id-blind (#23 routes through errnotassign "not
|
||||
* assignable"; S1 emits the #35 line); both stages REJECT and emit no asm.
|
||||
* selfhost has no such construct, so 990-997 byte-id is untouched.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* A1 — direct numeric variant; live runtime proof the accept path
|
||||
* still builds a correct box (match int -> return 5). */
|
||||
{ "A1_direct",
|
||||
"package main;\n"
|
||||
"type u = (int | str);\n"
|
||||
"fn mk() u = { return 5; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v = mk();\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let n: int => return n: i32;\n"
|
||||
"\tcase str => return 1;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* A2 — aliased numeric variant (resolvealias -> int). Compile-only:
|
||||
* main returns 0; the accept is proven by the build succeeding. */
|
||||
{ "A2_aliased",
|
||||
"package main;\n"
|
||||
"type myint = int;\n"
|
||||
"type u = (myint | str);\n"
|
||||
"fn mk() u = { return 5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
|
||||
/* A3 — enum variant (the trap): the N_TENUM accept keeps this legal,
|
||||
* mirroring cstage type_isnum(enum). Compile-only. */
|
||||
{ "A3_enum",
|
||||
"package main;\n"
|
||||
"type col = enum { RED, GREEN };\n"
|
||||
"type u = (col | str);\n"
|
||||
"fn mk() u = { return 5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
|
||||
/* A4 — src is a UNION (inner), not an untyped int -> never enters the
|
||||
* int arm; tagged->tagged direct-variant accept. Compile-only. */
|
||||
{ "A4_unionsrc",
|
||||
"package main;\n"
|
||||
"type inner = (int | bool);\n"
|
||||
"type outer = (inner | str);\n"
|
||||
"fn mk(x: inner) outer = { return x; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
|
||||
/* A5 — proper-subset union widen, src union. Compile-only. */
|
||||
{ "A5_subset",
|
||||
"package main;\n"
|
||||
"type sub = (int | uint);\n"
|
||||
"type sup = (int | uint | str);\n"
|
||||
"fn w(x: sub) sup = { return x; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
/* Each neg must FAIL the build on both stages (loud reject). */
|
||||
static const char *neg[] = {
|
||||
/* R1 — #23 canonical: int reachable only via a NESTED union. */
|
||||
"package main;\n"
|
||||
"type inner = (int | bool);\n"
|
||||
"type outer = (inner | str);\n"
|
||||
"fn mk() outer = { return 5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
/* R2 — no slot for an int at all (same fallthrough). */
|
||||
"package main;\n"
|
||||
"type u = (str | bool);\n"
|
||||
"fn mk() u = { return 5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
/* R3 — int fits no variant (struct | str). */
|
||||
"package main;\n"
|
||||
"type A = struct { v: int };\n"
|
||||
"type u = (A | str);\n"
|
||||
"fn mk() u = { return 5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
/* S1 / #35 — widen-SUBSET cast in a RETURN, 2nd-variant (bool) value
|
||||
* (census sx4). ww cgen's cgreturn formerly skipped the widener for an
|
||||
* N_CAST-tagged source (needswiden=false) -> plain cgexpr -> tag=0
|
||||
* SILENT (ran the int arm -> 70, want 11). Now the genuine-widening
|
||||
* (ru!=fnret) needswiden arm routes it through cgwidentaggedstore ->
|
||||
* the #35 widen-subset loud-stop. Both stages reject. */
|
||||
"package main;\n"
|
||||
"type inner = (int | bool);\n"
|
||||
"type outer = (int | bool | str);\n"
|
||||
"fn mk() outer = { return true: inner; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v = mk();\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let n: int => return 70;\n"
|
||||
"\tcase let b: bool => return 11;\n"
|
||||
"\tcase let s: str => return 22;\n"
|
||||
"\t};\n"
|
||||
"\treturn 99;\n"
|
||||
"};\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/nui_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/nui_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/nui_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* build_should_fail — the construct must error on `driver`; returns 0 when
|
||||
* the build correctly FAILS, non-zero when it wrongly succeeded. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/nuin_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/nuin_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/nuin_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int nn = (int)(sizeof neg / sizeof neg[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "nested_union_int_box: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"nested_union_int_box[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path, neg[i],
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr,
|
||||
"nested_union_int_box[%s][neg%d]: built ok, "
|
||||
"expected a loud reject\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"nested_union_int_box: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("nested_union_int_box: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
* 836_tagged_assignable_cluster — checker isassignable tagged-dst over-accept
|
||||
* cluster (drew2 spec .ai/drew2-family-spec.md). Arms that silently
|
||||
* over-accepted a NON-variant source into a tagged union where cstage
|
||||
* confident-rejects. SILENT-in-ww / cstage-LOUD (category A), aligned DOWN to
|
||||
* cstage's `return 0` per arm.
|
||||
*
|
||||
* LANDED here: A4 + A5 (the two arms build-proven byte-id-NEUTRAL on the full
|
||||
* 990-997 bootstrap gate).
|
||||
* A4 untyped-float into a tagged dst with no DIRECT float variant. The arm
|
||||
* had no tagged sub-loop (the #23 fix only covered untyped-int) so it
|
||||
* fell to the permissive catch-all. Now: accept iff a direct f32/f64
|
||||
* variant exists (cstage type_isfloat, type.c:376), else confident
|
||||
* reject.
|
||||
* A5 nil into a tagged dst with no nullable (ptr/slice/chan/fn) variant.
|
||||
* The tagged loop only checked ptr/slice and on miss fell through to a
|
||||
* permissive accept. Now: chan/fn added (cstage type.c:382-385) and a
|
||||
* miss confident-rejects. (The NON-tagged nil fallthrough is left for a
|
||||
* separate sibling.)
|
||||
*
|
||||
* DEFERRED (escalated): A6 (concrete->tagged innerconf) and A7 (tagged->tagged
|
||||
* subset loop = S2). Both OVER-REJECT real bootstrap forwards that cstage
|
||||
* accepts — A7 on `(i64 | oserror)` -> `(i64 | os.oserror)` (typeeqast does
|
||||
* not equate the unqualified vs module-qualified spelling of the same nominal
|
||||
* type; cstage type_eq does — the #199b cross-module nominal gap), A6 on a
|
||||
* concrete return into `(fast_parsed_float | invalid)` and `opaque_ -> error`
|
||||
* (ww's isassignable confidence model diverges from cstage type_assignable).
|
||||
* Landing them needs the typeeqast nominal fix first; deferred per rule 7
|
||||
* (no loosening to rescue, which would re-open S2). The valid-form POS rows
|
||||
* below keep working with A6/A7 in their PRE-task permissive state.
|
||||
*
|
||||
* neg row | shape | gate
|
||||
* -------------+------------------------------------------------+------
|
||||
* A4_float | (int|str) <- 3.5 | FAIL
|
||||
* A5_nil | (int|str) <- nil | FAIL
|
||||
*
|
||||
* pos row | shape | want
|
||||
* -------------+------------------------------------------------+------
|
||||
* A4_f64ok | (f64|str) <- 3.5; match f64 -> 35 | 35
|
||||
* A5_ptrok | (*int|void) <- nil (nullable) | 0
|
||||
* A6_directstr | (inner|str) <- str (direct variant) | 0
|
||||
* A6_intbool | (int|bool) <- 5 (numeric variant) | 0
|
||||
* A7_subset | sup(int|uint|str) <- sub(int|uint) | 0
|
||||
*
|
||||
* Diagnostic TEXT is byte-id-blind (both stages REJECT, no asm). 990-997
|
||||
* byte-id untouched (A4/A5 are NEUTRAL: cstage runs the same predicate on a
|
||||
* green bootstrap, which contains no rejected shape).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* A4 — float into a dst WITH an f64 variant: live runtime proof the
|
||||
* accept path still builds a correct box (match f64 -> 35). */
|
||||
{ "A4_f64ok",
|
||||
"package main;\n"
|
||||
"type u = (f64 | str);\n"
|
||||
"fn mk() u = { return 3.5; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v = mk();\n"
|
||||
"\tmatch (v) {\n"
|
||||
"\tcase let f: f64 => return 35;\n"
|
||||
"\tcase str => return 1;\n"
|
||||
"\t};\n"
|
||||
"\treturn 9;\n"
|
||||
"};\n",
|
||||
35 },
|
||||
|
||||
/* A5 — nil into a nullable (*int | void): the ptr variant accepts.
|
||||
* Compile-only (main returns 0). */
|
||||
{ "A5_ptrok",
|
||||
"package main;\n"
|
||||
"type u = (*int | void);\n"
|
||||
"fn mk() u = { return nil; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
|
||||
/* A6 valid — direct str variant (the permissive arm still accepts a
|
||||
* VALID variant; only the SILENT over-accept is what A6 would close).
|
||||
* Compile-only. */
|
||||
{ "A6_directstr",
|
||||
"package main;\n"
|
||||
"type inner = (int | bool);\n"
|
||||
"type u = (inner | str);\n"
|
||||
"fn mk(s: str) u = { return s; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
|
||||
/* A6 valid — numeric variant (untyped-int arm, unaffected).
|
||||
* Compile-only. */
|
||||
{ "A6_intbool",
|
||||
"package main;\n"
|
||||
"type u = (int | bool);\n"
|
||||
"fn mk() u = { return 5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
|
||||
/* A7 valid — proper-subset union widen (every src variant a direct dst
|
||||
* variant). Accepts on both today. Compile-only. */
|
||||
{ "A7_subset",
|
||||
"package main;\n"
|
||||
"type sub = (int | uint);\n"
|
||||
"type sup = (int | uint | str);\n"
|
||||
"fn w(x: sub) sup = { return x; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
/* Each neg must FAIL the build on both stages (loud confident reject). */
|
||||
static const char *neg[] = {
|
||||
/* A4 — untyped float into a dst with NO float variant. */
|
||||
"package main;\n"
|
||||
"type u = (int | str);\n"
|
||||
"fn mk() u = { return 3.5; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
/* A5 — nil into a dst with NO nullable variant. */
|
||||
"package main;\n"
|
||||
"type u = (int | str);\n"
|
||||
"fn mk() u = { return nil; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tac_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/tac_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tac_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* build_should_fail — the construct must error on `driver`; returns 0 when
|
||||
* the build correctly FAILS, non-zero when it wrongly succeeded. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tacn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/tacn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tacn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int nn = (int)(sizeof neg / sizeof neg[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tagged_assignable_cluster: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"tagged_assignable_cluster[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path, neg[i],
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr,
|
||||
"tagged_assignable_cluster[%s][neg%d]: built ok, "
|
||||
"expected a loud reject\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tagged_assignable_cluster: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_assignable_cluster: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* 837_idcast_tagged_return — S3 / #35: a SAME-TYPE tagged CAST in a RETURN
|
||||
* (`fn f() u = { ...; return v: u; }` where v: u — an identity cast).
|
||||
* ken2 spec .ai/ken2-family-spec.md (S3 section). Category A, CORRECTNESS
|
||||
* (widen-route), proven byte-id with cstage.
|
||||
*
|
||||
* ww's cgreturn keyed the tagged passthrough on rhs.kind; an N_CAST was
|
||||
* uncovered, so the same-type cast fell to the scalar-shuffle fast-path and
|
||||
* synthesised tag 0 — SILENT on any non-variant-0 value (a bool boxed as the
|
||||
* int variant: ran the int arm -> 70, want 11). cstage routes the N_CAST
|
||||
* through the scratch-widener (the N_CAST defeats its srcreg passthrough),
|
||||
* which peels the identity cast internally (cg_widen_tagged_store /
|
||||
* cg_tagged_castpeel). The fix adds the same-type N_CAST guard to cgreturn's
|
||||
* needswiden block so ww takes the widen route too — NOT a top-level peel,
|
||||
* which would reroute a call/dot source to passthrough and break byte-id.
|
||||
*
|
||||
* row | shape | want
|
||||
* -------------+----------------------------------------+------
|
||||
* S3_idcast | return x: u (x: u, 2nd-variant bool) | 11
|
||||
* S3_nocast | return x (same value, no cast) | 11
|
||||
* S3_intcast | return x: u (x: u, variant-0 int=7) | 7
|
||||
*
|
||||
* The 2nd-variant (bool) value is mandatory: a variant-0 (int) value boxes to
|
||||
* tag 0 coincidentally right (the S1 trap). S3_nocast pins the no-cast route
|
||||
* unchanged; S3_intcast pins the cast route on a variant-0 value.
|
||||
*
|
||||
* BYTE-ID: NEUTRAL. The newly-covered shape (same-type tagged CAST in return)
|
||||
* is non-bootstrap — had the bootstrap contained it the byte-id gate would
|
||||
* already be RED (pre-fix ww emitted tag-0 scalar-shuffle, cstage the
|
||||
* scratch-widen). 990-997 untouched.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* S3 — same-type tagged CAST in return, 2nd-variant (bool) value.
|
||||
* Pre-fix ww ran the int arm (70); now the widen route boxes tag 1. */
|
||||
{ "S3_idcast",
|
||||
"package main;\n"
|
||||
"type u = (int | bool | str);\n"
|
||||
"fn f() u = { let x: u = true; return x: u; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tmatch (f()) {\n"
|
||||
"\tcase let n: int => return 70;\n"
|
||||
"\tcase let b: bool => return 11;\n"
|
||||
"\tcase let s: str => return 22;\n"
|
||||
"\t};\n"
|
||||
"\treturn 99;\n"
|
||||
"};\n",
|
||||
11 },
|
||||
|
||||
/* S3 — no-cast control: identical value, plain return. Pins the
|
||||
* forwardtagged/passthrough route unchanged. */
|
||||
{ "S3_nocast",
|
||||
"package main;\n"
|
||||
"type u = (int | bool | str);\n"
|
||||
"fn f() u = { let x: u = true; return x; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tmatch (f()) {\n"
|
||||
"\tcase let n: int => return 70;\n"
|
||||
"\tcase let b: bool => return 11;\n"
|
||||
"\tcase let s: str => return 22;\n"
|
||||
"\t};\n"
|
||||
"\treturn 99;\n"
|
||||
"};\n",
|
||||
11 },
|
||||
|
||||
/* S3 — cast route on a variant-0 (int) value: the widen route must
|
||||
* box tag 0 and carry the payload (7), not just coincidentally land
|
||||
* on tag 0. */
|
||||
{ "S3_intcast",
|
||||
"package main;\n"
|
||||
"type u = (int | bool | str);\n"
|
||||
"fn f() u = { let x: u = 7; return x: u; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tmatch (f()) {\n"
|
||||
"\tcase let n: int => return n: i32;\n"
|
||||
"\tcase let b: bool => return 11;\n"
|
||||
"\tcase let s: str => return 22;\n"
|
||||
"\t};\n"
|
||||
"\treturn 99;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/idc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/idc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/idc_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "idcast_tagged_return: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"idcast_tagged_return[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"idcast_tagged_return: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("idcast_tagged_return: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* 838_global_tagged_castinit — E8: a module-level tagged global initialised
|
||||
* by a same-type/subset CAST (`let g: u = true: u;`). ken2 spec
|
||||
* .ai/ken2-family-spec.md (E8 section). Category A, SILENT-in-ww / cstage-
|
||||
* LOUD, aligned DOWN to cstage's loud.
|
||||
*
|
||||
* ww's emittaggeddata selected the variant via taggedvariantindex, whose
|
||||
* str/slice SHAPE fallback (cgenutil.ww) returns the first scalar variant
|
||||
* (tag 0) when no variant typeeq's the source. For a CAST init the checker
|
||||
* stamps the peeled literal's type as the union itself, so no variant matches
|
||||
* and the fallback synthesised tag 0 in the DATA word — SILENT (a bool/int
|
||||
* cast ran the int arm, payload mis-tagged). cstage's emit_tagged_data calls
|
||||
* cg_tag_for_variant (NO shape fallback, cgen.c:15472) which returns -1 ->
|
||||
* the caller loud-stops ("unsupported variant init"). The fix routes ww's
|
||||
* emittaggeddata through flatvariantidx — the EXACT cg_tag_for_variant twin —
|
||||
* so a cast-init that resolves to no variant louds, while every shape cstage
|
||||
* accepts (bare literal, str-literal cast) still resolves via pass 1.
|
||||
*
|
||||
* neg row | shape | gate
|
||||
* -------------+--------------------------------+------
|
||||
* E8_boolcast | let g: u = true: u; | FAIL
|
||||
* E8_intcast | let g: u = 7: u; | FAIL
|
||||
*
|
||||
* pos row | shape | want
|
||||
* -------------+--------------------------------+------
|
||||
* E8_barebool | let g: u = true; match bool | 11
|
||||
* E8_bareint | let g: u = 7; match int -> n | 7
|
||||
* E8_barestr | let g: u = "hi"; match str | 22
|
||||
* E8_strcast | let g: u = "hi": u; (str keeps | 22
|
||||
* | str type -> str variant) |
|
||||
*
|
||||
* The bare-literal forms are clean on both stages (byte-id with cstage); the
|
||||
* str-literal CAST keeps its concrete str type and resolves to the str
|
||||
* variant, so it must NOT be over-rejected (a naive "N_CAST onto tagged ->
|
||||
* loud" predicate would wrongly reject it). u = (int | bool | str): int is
|
||||
* variant 0, so bare-int landing on tag 0 is correct (and pins the payload).
|
||||
*
|
||||
* BYTE-ID: NEUTRAL. A new ww loud emits no asm; cstage already louds; the
|
||||
* bootstrap cannot contain a shape cstage refuses. The accept-path tags are
|
||||
* unchanged (flatvariantidx returns the same index as the old pass 1). 990-
|
||||
* 997 untouched.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const char *PROLOGUE =
|
||||
"package main;\n"
|
||||
"type u = (int | bool | str);\n";
|
||||
|
||||
static const char *EPILOGUE =
|
||||
"export fn main() i32 = {\n"
|
||||
"\tmatch (g) {\n"
|
||||
"\tcase let n: int => return n: i32;\n"
|
||||
"\tcase let b: bool => return 11;\n"
|
||||
"\tcase let s: str => return 22;\n"
|
||||
"\t};\n"
|
||||
"\treturn 99;\n"
|
||||
"};\n";
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "E8_barebool", "let g: u = true;\n", 11 },
|
||||
{ "E8_bareint", "let g: u = 7;\n", 7 },
|
||||
{ "E8_barestr", "let g: u = \"hi\";\n", 22 },
|
||||
{ "E8_strcast", "let g: u = \"hi\": u;\n", 22 },
|
||||
};
|
||||
|
||||
static const char *neg[] = {
|
||||
"let g: u = true: u;\n",
|
||||
"let g: u = 7: u;\n",
|
||||
};
|
||||
|
||||
static void
|
||||
write_src(const char *path, const char *mid)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return;
|
||||
fputs(PROLOGUE, f);
|
||||
fputs(mid, f);
|
||||
fputs(EPILOGUE, f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gtc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/gtc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/gtc_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
write_src(src, r->src);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *mid, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gtcn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/gtcn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/gtcn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
write_src(s, mid);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
int rc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int nn = (int)(sizeof neg / sizeof neg[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "global_tagged_castinit: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"global_tagged_castinit[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path, neg[i],
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr,
|
||||
"global_tagged_castinit[%s][neg%d]: built ok, "
|
||||
"expected a loud reject\n",
|
||||
drivers[d].name, i);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"global_tagged_castinit: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("global_tagged_castinit: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
/*
|
||||
* 840_zeroinit_run — task #16. A bare `let x: T;` (no initializer) must
|
||||
* zero-fill its slot (Go zero-value, rob's ruling). Before the fix, cgen
|
||||
* emitted the zero-fill ONLY for 8B-primitive slots and >8B composites; a
|
||||
* SUB-8 aggregate (`let c: [3]u8;` = 3 bytes, a 3-byte struct, …) matched
|
||||
* neither arm and fell through to NOTHING, so the slot read whatever the
|
||||
* stack held.
|
||||
*
|
||||
* THE BUG (cstage == wwstage, BOTH wrong — shared gap, NOT rule-10):
|
||||
* ken's bytes verdict (.ai/ken-bytes16-verdict.md) traced lib/bytes'
|
||||
* green-but-broken 967 to ltrim_cases' `let c: [3]u8;` reading a prior
|
||||
* deep-frame sibling's leftover bytes. The masking was a stack-zero
|
||||
* accident: a bare let on a FRESH frame happens to read 0, so the bug
|
||||
* only fires when a deep-framed fn ran first. byte-id was BLIND (#263):
|
||||
* both stages emitted the identical no-store sequence.
|
||||
*
|
||||
* REPRO SHAPE (ken's bytes-independent minimal repro, generalised):
|
||||
* dirty() writes a big local ([64]u8 = 222) to soil the stack region;
|
||||
* probe() then declares the bare let as its FIRST local — reusing
|
||||
* dirty()'s slot — and reads it. A correct zero-fill returns 0; the
|
||||
* pre-fix garbage returned (222 * width) & 0xff (154 for [3]u8).
|
||||
*
|
||||
* THE FIX (#16, both stages): widen the no-rhs zero-fill gate from
|
||||
* `sz > 8` to `sz > 0` (cgen.c N_LET) and add the symmetric `zsz > 0`
|
||||
* run arm (cgenstmt.ww cglet), so 1..7-byte slots zero through the same
|
||||
* MOVL/MOVB tail. sz == 8 keeps its immediate MOVQ $0; sz == 0 (`[0]T`)
|
||||
* needs no stores.
|
||||
*
|
||||
* EACH ROW CARRIES BOTH DIMENSIONS (802 model):
|
||||
* (a) cstage `ww build` + run, asserting exit 0 — pins that the bare let
|
||||
* reads zero even after a dirtied frame (runtime correctness the
|
||||
* byte-id gate cannot see).
|
||||
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A nonzero exit means a bare-let slot
|
||||
* regressed to reading stack garbage; a byte-id FAIL means the stages
|
||||
* diverged on the zero-fill emission.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want_exit; };
|
||||
|
||||
/* Every row shares the dirty()→probe() spine: dirty soils the frame,
|
||||
* probe declares the bare let FIRST and reads it. want_exit is 0 (all
|
||||
* reads zero). The discriminating rows are the SUB-8 aggregates ([3]u8,
|
||||
* [5]u8, [7]u8, the 3-byte struct) — the exact gap #16 closed; the
|
||||
* scalar / [20]u8 / str / slice rows are controls (already zeroed pre-#16
|
||||
* via the sz==8 and sz>8 arms) pinning no-regression. */
|
||||
static const struct row rows[] = {
|
||||
/* the exact ken repro: [3]u8 sub-8 array. Pre-fix returned 154. */
|
||||
{ "arr3_u8",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = {\n"
|
||||
" let c: [3]u8;\n"
|
||||
" return (c[0]: i32) + (c[1]: i32) + (c[2]: i32);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* sub-8, width 5 — exercises the MOVL+MOVB tail split (4 + 1). */
|
||||
{ "arr5_u8",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = {\n"
|
||||
" let c: [5]u8; let acc: i32 = 0; let j: i32 = 0;\n"
|
||||
" for (j < 5) { acc += (c[j]: i32); j += 1; };\n"
|
||||
" return acc;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* sub-8, width 7 — the widest sub-8 extent (MOVL + MOVW + MOVB is
|
||||
* unreachable under the run's 4/1 tail, so this is MOVL + 3×MOVB). */
|
||||
{ "arr7_u8",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = {\n"
|
||||
" let c: [7]u8; let acc: i32 = 0; let j: i32 = 0;\n"
|
||||
" for (j < 7) { acc += (c[j]: i32); j += 1; };\n"
|
||||
" return acc;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* sub-8 STRUCT (3 bytes) — the non-array half of the gap. Read each
|
||||
* field; a garbage slot would sum nonzero. */
|
||||
{ "struct3_u8",
|
||||
"package main;\n"
|
||||
"type S = struct { a: u8, b: u8, c: u8 };\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = {\n"
|
||||
" let s: S;\n"
|
||||
" return (s.a: i32) + (s.b: i32) + (s.c: i32);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* CONTROL: scalar i32 (sz==8) — zeroed pre-#16 by the MOVQ $0 arm. */
|
||||
{ "scalar_i32",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = { let x: i32; return x; };\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* CONTROL: [20]u8 (>8) — zeroed pre-#16 by the #84 sz>8 run. */
|
||||
{ "arr20_u8",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = {\n"
|
||||
" let c: [20]u8; let acc: i32 = 0; let j: i32 = 0;\n"
|
||||
" for (j < 20) { acc += (c[j]: i32); j += 1; };\n"
|
||||
" return acc;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* CONTROL: bare str header (24B) — rob's declmod `let empty: str;`
|
||||
* shape. .len must read 0 (zeroed pre-#16 by the sz>8 run). */
|
||||
{ "str_hdr",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = { let empty: str; return empty.len; };\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
/* CONTROL: bare slice header (24B) — .len must read 0. */
|
||||
{ "slice_hdr",
|
||||
"package main;\n"
|
||||
"fn dirty() void = {\n"
|
||||
" let big: [64]u8; let i: i32 = 0;\n"
|
||||
" for (i < 64) { big[i] = 222u8; i += 1; };\n"
|
||||
"};\n"
|
||||
"fn probe() i32 = { let xs: []i32; return xs.len; };\n"
|
||||
"export fn main() i32 = { dirty(); return probe(); };\n", 0 },
|
||||
{ NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
static int
|
||||
slurp_eq(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb");
|
||||
FILE *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||
int rc = 0;
|
||||
for (;;) {
|
||||
int ca = fgetc(fa);
|
||||
int cb = fgetc(fb);
|
||||
if (ca != cb) { rc = -1; break; }
|
||||
if (ca == EOF) break;
|
||||
}
|
||||
fclose(fa); fclose(fb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char w6c[1100], w6c_ww[1100];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
if (access(w6c_ww, X_OK) != 0) {
|
||||
fprintf(stderr, "zeroinit: w6c_ww missing — cannot run the "
|
||||
"cs==ww byte-id gate\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char tmpdir[64];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwzi_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160];
|
||||
snprintf(src, sizeof src, "%s/wwzi_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wwzi_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(cs_s, sizeof cs_s, "%s/wwzi_%d_%d_cs.s", tmpdir, getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "%s/wwzi_%d_%d_ww.s", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; runwait(rmcmd); continue; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
/* (a) cstage build + run. */
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", bin, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||||
rows[i].label);
|
||||
fail++; runwait(rmcmd); continue;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row[%s]: cstage exit %d, want %d "
|
||||
"(bare let read stack garbage?)\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* (b) cs==ww byte-id gate. */
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
|
||||
fail++; runwait(rmcmd); continue;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c_ww, ws_s, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww failed\n", rows[i].label);
|
||||
fail++; runwait(rmcmd); continue;
|
||||
}
|
||||
if (slurp_eq(cs_s, ws_s) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
|
||||
"(rule-10 byte-id violation)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
runwait(rmcmd);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d zeroinit tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("zeroinit: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
* 841_dup_main_reject — cstage and wwstage LOUD-REJECT a program that
|
||||
* declares more than one top-level `main` (F-D; rob ruling 2026-06-14).
|
||||
*
|
||||
* `main` lowers to ONE bare entry symbol. A second top-level decl named
|
||||
* `main` (any kind, any package) collides with the entry at link time:
|
||||
* the `let main` DATA symbol and the entry `fn main` TEXT symbol clash.
|
||||
* Pre-fix BOTH stages built the program rc=0 and SEGFAULTED at runtime
|
||||
* (the w6l silent-dup-symbol latent, task #31); align both UP to a loud
|
||||
* compile-time reject. Correct multi-main mangling (entry stays bare,
|
||||
* every other `main` module-qualifies) is the deferred root-unit entry
|
||||
* detection (task #32) — until then the collision is rejected, never
|
||||
* silently miscompiled (rule 7).
|
||||
*
|
||||
* The reject is program-GLOBAL, name-only, CROSS-MODULE: it counts
|
||||
* top-level decls named "main" across the fully-bundled program. The
|
||||
* existing duplicate-decl rejects key on (name, MODULE), so they read a
|
||||
* cross-package `foo.main` and the bare entry `main` as DISTINCT and
|
||||
* miss this clash. The trigger is COUNT("main") > 1, NOT package
|
||||
* identity — ww has no package-main convention (examples/cmatrix, lisp,
|
||||
* mandelbrot are working entries with `fn main` in a non-main package).
|
||||
*
|
||||
* Each reject row is a GENUINE cross-module case: a real `import foo;`
|
||||
* binds package foo, so decl_mod stamps foo's `main` with module "foo"
|
||||
* and the entry `main` with module NULL — DISTINCT keys the existing
|
||||
* (name, module) reject reads apart and lets through. Without the
|
||||
* import both `main`s collapse to module NULL and the OLD `duplicate fn
|
||||
* main` reject already fires, so the import is what exercises THIS
|
||||
* check (verified: pre-fix base 7a6b67f builds every row rc=0).
|
||||
*
|
||||
* row | shape | expect
|
||||
* ----------------+-----------------------------------------+--------
|
||||
* let_main | import foo; foo `let main` + entry main | REJECT
|
||||
* fn_main | import foo; foo `fn main` + entry main | REJECT
|
||||
* def_main | import foo; foo `def main` + entry main | REJECT
|
||||
* type_main | import foo; foo `type main`+ entry main | REJECT
|
||||
* single_nonmain | one `fn main` in a NON-main package | BUILD,
|
||||
* | (cmatrix-style) — the corpus-safety | run 7
|
||||
* | guard: a lone main must STILL compile. |
|
||||
*
|
||||
* DISCRIMINATION (verified against a clean 7a6b67f base): pre-fix the
|
||||
* let_main/def_main rows built rc=0 then exited 139 (SIGSEGV) and
|
||||
* fn_main/type_main built rc=0 and ran the WRONG entry (two bare `TEXT
|
||||
* main` symbols, the w6l silent-dup latent task #31); post-fix every
|
||||
* row loud-rejects at compile (build rc!=0, no binary), both stages.
|
||||
* The single_nonmain row proves the reject did not eat the working
|
||||
* single-`main` corpus.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* build_should_fail — a dup-`main` program must error on `driver`
|
||||
* (loud reject); returns 0 when the build FAILS *and* emits `diag`.
|
||||
* This test drives the full `ww` driver, so a crash inside w6c also
|
||||
* surfaces as the driver's "w6c failed" exit 1 — the diagnostic-substring
|
||||
* check is the ONLY way to tell a crash from a clean reject here (#20).
|
||||
* Intermediates land in tmpdir, never the source tree (rule 14). */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], errf[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dupmain_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/dupmain_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
|
||||
snprintf(errf, sizeof errf, "%s/err.txt", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr,
|
||||
"dup_main[%s][%s]: built ok, expected a loud reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "dup_main[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* run_build — build a program that MUST compile, run it, return its
|
||||
* exit code (the corpus-safety guard). */
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dupmain_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/dupmain_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/dupmain_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "dup_main[%s][%s]: build failed (corpus "
|
||||
"regression — a lone main must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(outbin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; const char *diag; };
|
||||
|
||||
/* Each program declares a second top-level `main` (in package foo)
|
||||
* alongside the entry `fn main` (in package main), bound by a REAL
|
||||
* `import foo;` — so decl_mod stamps the two `main`s with DISTINCT
|
||||
* modules ("foo" vs NULL) and the existing (name, module) reject reads
|
||||
* them apart and misses the clash; only THIS program-global name-only
|
||||
* reject fires. The driver bundles both packages into one unit; foo
|
||||
* also exports `anchor` so the import is used and foo resolves. */
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "let_main",
|
||||
"package foo;\n"
|
||||
"export let main: i32 = 99;\n"
|
||||
"export fn anchor() i32 = { return main; };\n"
|
||||
"package main;\n"
|
||||
"import foo;\n"
|
||||
"fn main() i32 = { return foo.anchor(); };\n",
|
||||
"duplicate entry main" },
|
||||
|
||||
{ "fn_main",
|
||||
"package foo;\n"
|
||||
"export fn main() i32 = { return 1; };\n"
|
||||
"export fn anchor() i32 = { return 2; };\n"
|
||||
"package main;\n"
|
||||
"import foo;\n"
|
||||
"fn main() i32 = { return foo.anchor(); };\n",
|
||||
"duplicate entry main" },
|
||||
|
||||
{ "def_main",
|
||||
"package foo;\n"
|
||||
"export def main: i32 = 5;\n"
|
||||
"export fn anchor() i32 = { return main; };\n"
|
||||
"package main;\n"
|
||||
"import foo;\n"
|
||||
"fn main() i32 = { return foo.anchor(); };\n",
|
||||
"duplicate entry main" },
|
||||
|
||||
{ "type_main",
|
||||
"package foo;\n"
|
||||
"export type main = i32;\n"
|
||||
"export fn anchor() i32 = { return 3; };\n"
|
||||
"package main;\n"
|
||||
"import foo;\n"
|
||||
"fn main() i32 = { return foo.anchor(); };\n",
|
||||
"duplicate entry main" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
/* The corpus-safety guard: a SINGLE `fn main` in a NON-main package
|
||||
* (cmatrix/lisp/mandelbrot style) MUST still compile and run. */
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "single_nonmain",
|
||||
"package cmatrix;\n"
|
||||
"fn main() i32 = { return 7; };\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "dup_main: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
reject_rows[i].diag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"dup_main[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "dup_main: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dup_main: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* 845_tuple_trailing_comma_reject (catB-92) — cstage and wwstage
|
||||
* LOUD-REJECT a single-element trailing-comma tuple `(a,)`.
|
||||
*
|
||||
* A trailing comma is legal ONLY after >=2 tuple elements; `(a,)` is a
|
||||
* parse error, `(a, b)` and `(a, b,)` are 2-tuples. cstage parse.c:552-
|
||||
* 558 parses each element BEFORE the RPAREN-break, so `(a,)` errors at
|
||||
* the next parseexpr (it sees `)`). wwstage's lib/ww/parse/expr.ww tuple
|
||||
* loop checked the RPAREN-break at the TOP, so `(a,)` parsed as a
|
||||
* 1-element N_TUPLE and reached cgen — a silent wrong-accept. The fix
|
||||
* mirrors cstage's loop order (break sits AFTER accept(COMMA)).
|
||||
*
|
||||
* The reject row is `let t = (5,);` — an INFERRED-type 1-tuple, the
|
||||
* context the old wwstage built rc=0 and codegen'd (a 2-tuple-DECLARED
|
||||
* `(5,)` already errored on arity downstream, masking the parse bug;
|
||||
* the inferred form is the clean discriminator). Pre-fix wwstage built
|
||||
* it rc=0; cstage rejected it. Post-fix both reject at parse.
|
||||
*
|
||||
* row | shape | expect
|
||||
* -----------+-----------------+--------
|
||||
* one_comma | let t = (5,); | REJECT (parse)
|
||||
* two_elem | (3, 4) | BUILD, run 7
|
||||
* two_comma | (3, 4,) | BUILD, run 7 (trailing OK after >=2)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* outbin — derive the driver's default binary name from a *.ww source
|
||||
* (strip dir + the .ww suffix), placed in tmpdir. */
|
||||
static void
|
||||
outbin(char *dst, size_t n, const char *tmpdir, const char *src)
|
||||
{
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(dst, n, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(dst, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* build_should_fail — returns 0 when the build FAILS *and* the stage's
|
||||
* own parse diagnostic appears; a crash wraps as a nonzero "w6c failed"
|
||||
* with no diagnostic, so the substring distinguishes it (#20). The two
|
||||
* stages' parse messages diverge with no common core (cstage "unexpected
|
||||
* token in expression"/"expected ), got ;" vs wwstage "parse: expected
|
||||
* expression"/"parse: expected ')' in tuple"), so `diag` is passed
|
||||
* per-stage by the caller (a real fidelity gap, filed under #20). */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tuptc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/tuptc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/tuptc_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
/* explicit -o INSIDE tmpdir so the binary AND its <stem>.sepwork land
|
||||
* under tmpdir and die with rm -rf; a reject leaks the mkdir'd scratch
|
||||
* too, so clean on every exit path. */
|
||||
outbin(bin, sizeof bin, tmpdir, s);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "tuptc[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "tuptc[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* run_build — build a program that MUST compile, run it, return exit. */
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tuptc_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/tuptc_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/tuptc_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "tuptc[%s][%s]: build failed (a valid tuple "
|
||||
"must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow {
|
||||
const char *label;
|
||||
const char *src;
|
||||
const char *cdiag; /* cstage diagnostic body substring */
|
||||
const char *wdiag; /* wwstage diagnostic body substring (#20 gap) */
|
||||
};
|
||||
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "one_comma",
|
||||
"package main;\n"
|
||||
"fn main() i32 = { let t = (5,); return 0; };\n",
|
||||
"unexpected token in expression", "expected expression" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "two_elem",
|
||||
"package main;\n"
|
||||
"fn pair() (i32, i32) = { return (3, 4); };\n"
|
||||
"fn main() i32 = { let a, b = pair(); return a + b; };\n", 7 },
|
||||
|
||||
{ "two_comma",
|
||||
"package main;\n"
|
||||
"fn pair() (i32, i32) = { return (3, 4,); };\n"
|
||||
"fn main() i32 = { let a, b = pair(); return a + b; };\n", 7 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tuptc: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
drivers[d].gated ? reject_rows[i].wdiag
|
||||
: reject_rows[i].cdiag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "tuptc[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "tuptc: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tuptc: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* 846_voidless_return_reject (catB-24) — cstage and wwstage LOUD-REJECT
|
||||
* a value-less `return;` in a function whose return type is not void.
|
||||
*
|
||||
* `fn f() i32 = { return; }` would RET a garbage value register — a
|
||||
* common programmer slip. cstage rejects at cmd/wcc/check.c:2428-2439
|
||||
* (a value-less return has type void, then type_assignable(c->ret,
|
||||
* void) fails for a non-void scalar). wwstage's checkretassign
|
||||
* short-circuited on the no-value case ("skip flagging for now"), a
|
||||
* silent accept. The fix builds a void source type and runs the same
|
||||
* isassignable verdict: void→void and void→(T|void) accept, void→i32
|
||||
* is a confident reject.
|
||||
*
|
||||
* row | shape | expect
|
||||
* -----------+-----------------------------+--------
|
||||
* nonvoid | fn f() i32 = { return; }; | REJECT
|
||||
* void_ok | fn f() void = { return; }; | BUILD, run 5
|
||||
* value_ok | fn f() i32 = { return 5; }; | BUILD, run 5
|
||||
* tagged_ok | fn f() (i32|void) ... return;| BUILD, run 9
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* A reject row passes only when the build FAILS *and* the stage's clean
|
||||
* diagnostic appears; a crash (segfault) wraps as a nonzero "w6c failed"
|
||||
* with no diagnostic, so the substring check distinguishes it (#20).
|
||||
* cstage "return void not assignable to i32" and wwstage "return: not
|
||||
* assignable (void → i32)" share the "not assignable" core. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/vret_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/vret_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/vret_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/vret_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "vret[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "vret[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/vret_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/vret_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/vret_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "vret[%s][%s]: build failed (a valid return "
|
||||
"must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; const char *diag; };
|
||||
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "nonvoid",
|
||||
"package main;\n"
|
||||
"fn f() i32 = { return; };\n"
|
||||
"fn main() i32 = { return f(); };\n",
|
||||
"not assignable" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "void_ok",
|
||||
"package main;\n"
|
||||
"fn f() void = { return; };\n"
|
||||
"fn main() i32 = { f(); return 5; };\n", 5 },
|
||||
|
||||
{ "value_ok",
|
||||
"package main;\n"
|
||||
"fn f() i32 = { return 5; };\n"
|
||||
"fn main() i32 = { return f(); };\n", 5 },
|
||||
|
||||
{ "tagged_ok",
|
||||
"package main;\n"
|
||||
"fn f(x: i32) (i32 | void) = { if (x > 0) { return x; }; return; };\n"
|
||||
"fn main() i32 = { match (f(0)) { case let v: i32 => return v; "
|
||||
"case => return 9; }; };\n", 9 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "vret: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
reject_rows[i].diag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "vret[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "vret: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("vret: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* 847_const_reassign_reject (catB-22) — cstage and wwstage LOUD-REJECT
|
||||
* a reassignment of a `const`-declared binding.
|
||||
*
|
||||
* `const x = 5; x = 6;` mutates an immutable binding — a programmer
|
||||
* error. The sym carries an is_const flag (lib/ww/sym.ww), but wwstage
|
||||
* never SET it at the let-install nor CONSUMED it at assignment, so the
|
||||
* reassignment slipped through silently. The fix mirrors cstage's two
|
||||
* sites: set is_const when n.op == TK_CONST at the local let-install
|
||||
* (cmd/wcc/check.c:2408) and reject an N_ASSIGN whose lhs ident resolves
|
||||
* to an is_const sym (cmd/wcc/check.c:1889-1896). cstage already
|
||||
* rejected; this aligns wwstage UP.
|
||||
*
|
||||
* Both rows annotate `: i32` so the inferred-int default (#34 int/i32
|
||||
* cs/ww divergence) does not contaminate the const signal.
|
||||
*
|
||||
* row | shape | expect
|
||||
* -----------+--------------------------------+--------
|
||||
* const_set | const x: i32 = 5; x = 6; | REJECT
|
||||
* let_set | let y: i32 = 5; y = 6; | BUILD, run 6
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
outbin(char *dst, size_t n, const char *tmpdir, const char *src)
|
||||
{
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(dst, n, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(dst, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* A reject row passes only when the build FAILS *and* the stage's clean
|
||||
* diagnostic appears; a crash (segfault) wraps as a nonzero "w6c failed"
|
||||
* with no diagnostic, so the substring check distinguishes it (#20).
|
||||
* cstage "cannot assign to const `x`" and wwstage "cannot assign to const
|
||||
* binding" share the "cannot assign to const" core. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/creas_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/creas_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/creas_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
outbin(bin, sizeof bin, tmpdir, s);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "creas[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "creas[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], bin[128], cmd[1024], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/creas_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/creas_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/creas_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "creas[%s][%s]: build failed (a mutable let "
|
||||
"must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; const char *diag; };
|
||||
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "const_set",
|
||||
"package main;\n"
|
||||
"fn main() i32 = { const x: i32 = 5; x = 6; return x; };\n",
|
||||
"cannot assign to const" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "let_set",
|
||||
"package main;\n"
|
||||
"fn main() i32 = { let y: i32 = 5; y = 6; return y; };\n", 6 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "creas: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
reject_rows[i].diag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "creas[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "creas: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("creas: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* 849_dupfield_reject (catB-7/14) — cstage and wwstage LOUD-REJECT a
|
||||
* struct declaration that carries two fields with the same name.
|
||||
*
|
||||
* cstage already rejected at cmd/wcc/check.c:943-950 (the regular-named-
|
||||
* field arm of resolve_type's N_TSTRUCT). wwstage silently ACCEPTED — it
|
||||
* never compared field names. The fix adds validatestructfields(), a pure
|
||||
* read-only O(n^2) named-field dup walk, fired once per struct decl from
|
||||
* resolvewalk's eager type-decl dispatch (selfhost/cmd/wcc/check.ww), the
|
||||
* rule-10 twin of cstage's once-per-resolve_type site. ww has no struct
|
||||
* embedding (parser gap catB-89), so cstage's embed-collision arm
|
||||
* (check.c:961-985) is intentionally NOT ported — named fields only.
|
||||
*
|
||||
* Byte-id: pure diagnostic, no n.type_ / offset / checker-state mutation;
|
||||
* the valid corpus has no duplicate fields, so codegen of every valid
|
||||
* program is unchanged → cstage == wwstage byte-id holds.
|
||||
*
|
||||
* row | shape | expect
|
||||
* -------------+------------------------------------+--------
|
||||
* adj_dup | struct { a: i32, a: i32 } | REJECT
|
||||
* nonadj_dup | struct { a: i32, b: i32, a: i32 } | REJECT
|
||||
* dup_difftype | struct { a: i32, a: bool } | REJECT
|
||||
* distinct | struct { x: i32, y: i32 }, use x+y | BUILD, run 7
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
outbin(char *dst, size_t n, const char *tmpdir, const char *src)
|
||||
{
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(dst, n, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(dst, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* A reject row passes only when the build FAILS *and* the stage's clean
|
||||
* diagnostic appears; a crash (segfault) wraps as a nonzero "w6c failed"
|
||||
* with no diagnostic, so the substring check distinguishes it (#20). */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dupf_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/dupf_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/dupf_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
outbin(bin, sizeof bin, tmpdir, s);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "dupf[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "dupf[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dupf_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/dupf_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/dupf_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "dupf[%s][%s]: build failed (distinct fields "
|
||||
"must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; const char *diag; };
|
||||
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "adj_dup",
|
||||
"package main;\n"
|
||||
"type d = struct { a: i32, a: i32 };\n"
|
||||
"fn main() i32 = { let p: *d = nil; return 0; };\n",
|
||||
"duplicate field" },
|
||||
{ "nonadj_dup",
|
||||
"package main;\n"
|
||||
"type d = struct { a: i32, b: i32, a: i32 };\n"
|
||||
"fn main() i32 = { let p: *d = nil; return 0; };\n",
|
||||
"duplicate field" },
|
||||
{ "dup_difftype",
|
||||
"package main;\n"
|
||||
"type d = struct { a: i32, a: bool };\n"
|
||||
"fn main() i32 = { let p: *d = nil; return 0; };\n",
|
||||
"duplicate field" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "distinct",
|
||||
"package main;\n"
|
||||
"type pt = struct { x: i32, y: i32 };\n"
|
||||
"fn main() i32 = { let p: pt = pt{x=3, y=4}; return p.x + p.y; };\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "dupf: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
reject_rows[i].diag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "dupf[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "dupf: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dupf: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* 850_enum_reject (catB-2) — cstage and wwstage LOUD-REJECT the three
|
||||
* invalid enum-declaration shapes: a non-integer storage type, a
|
||||
* duplicate member name, and an unfoldable (non-constant) member value.
|
||||
*
|
||||
* cstage already rejected at cmd/wcc/check.c:1000-1042 (the N_TENUM arm
|
||||
* of resolve_type — storage type_isint gate, the per-member dup walk,
|
||||
* and eval_enum_value's constant fold). wwstage silently ACCEPTED — it
|
||||
* stamped member types but never validated storage/dup/foldability. The
|
||||
* fix adds validateenummembers(), a pure read-only diagnostic fired once
|
||||
* per enum decl from resolvewalk's eager type-decl dispatch (selfhost/
|
||||
* cmd/wcc/check.ww), the rule-10 twin of cstage's once-per-resolve_type
|
||||
* site, sibling to validatestructfields (catB-7/14).
|
||||
*
|
||||
* Byte-id: pure diagnostic, no n.type_ / member-value / checker-state
|
||||
* mutation; the valid corpus has integer storage, distinct members, and
|
||||
* foldable values, so codegen of every valid program is unchanged →
|
||||
* cstage == wwstage byte-id holds.
|
||||
*
|
||||
* row | shape | expect
|
||||
* -------------+--------------------------------------+--------
|
||||
* nonint_stor | enum f64 { A } | REJECT
|
||||
* dup_member | enum { A, B, A } | REJECT
|
||||
* fwd_ref | enum { A = B, B } (non-constant) | REJECT
|
||||
* distinct | enum { A, B=5, C=B+1 }, use A+C | BUILD, run 6
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* A reject row passes only when the build FAILS *and* the stage's clean
|
||||
* diagnostic appears; a crash (segfault) wraps as a nonzero "w6c failed"
|
||||
* with no diagnostic, so the substring check distinguishes it (#20). */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/enr_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/enr_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/enr_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/enr_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "enr[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "enr[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/enr_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/enr_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/enr_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "enr[%s][%s]: build failed (valid enum must "
|
||||
"compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; const char *diag; };
|
||||
|
||||
/* diag substrings are the shared message BODY (no file:line prefix, which
|
||||
* differs cstage vs wwstage). fwd_ref's stages diverge in wording (cstage
|
||||
* "enum value: unknown identifier 'B'" vs wwstage "enum value must be a
|
||||
* constant integer expression"); "enum value" is the common core (#20). */
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "nonint_stor",
|
||||
"package main;\n"
|
||||
"type e = enum f64 { A };\n"
|
||||
"fn main() i32 = { return e.A as i32; };\n",
|
||||
"enum storage type must be integer" },
|
||||
{ "dup_member",
|
||||
"package main;\n"
|
||||
"type e = enum { A, B, A };\n"
|
||||
"fn main() i32 = { return e.A as i32; };\n",
|
||||
"duplicate enum member" },
|
||||
{ "fwd_ref",
|
||||
"package main;\n"
|
||||
"type e = enum { A = B, B };\n"
|
||||
"fn main() i32 = { return e.A as i32; };\n",
|
||||
"enum value" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "distinct",
|
||||
"package main;\n"
|
||||
"type e = enum { A, B = 5, C = B + 1 };\n"
|
||||
"fn main() i32 = { return e.A as i32 + e.C as i32; };\n",
|
||||
6 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "enr: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
reject_rows[i].diag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "enr[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "enr: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("enr: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* 851_index_int_reject (catB-17) — cstage and wwstage LOUD-REJECT an
|
||||
* N_INDEX whose index operand is not an integer (float, str, pointer,
|
||||
* bool). cstage already rejected at cmd/wcc/check.c:1491-1495 (the
|
||||
* N_INDEX arm: `if (idx != ty_err && !type_isint(idx)) err(...)`).
|
||||
* wwstage silently ACCEPTED — indexresult() computed the index type and
|
||||
* dropped it (`let _idx = exprtype(...)`), so `xs[f64]` / `xs[str]` /
|
||||
* `xs[*T]` / `xs[bool]` compiled. The fix derives the index tinfo and
|
||||
* gates syntax.typeisint (selfhost/cmd/wcc/check.ww indexresult), the
|
||||
* rule-10 twin of cstage's once-per-N_INDEX site, sibling to
|
||||
* validateenummembers (catB-2) / validatestructfields (catB-7/14).
|
||||
*
|
||||
* The ALIAS-INT accept row is load-bearing: typeisint is the tinfo
|
||||
* chaser (follows TY_NAMED.under / TY_ENUM.sub), so `type ix = i32`
|
||||
* indexes clean. The AST-keyed isinttypeast would falsely reject it —
|
||||
* a vacuous plain-int-only test would not catch that.
|
||||
*
|
||||
* Byte-id: pure diagnostic, no n.type_ / checker-state mutation beyond
|
||||
* c.errs; the valid corpus indexes only by integer types, so codegen of
|
||||
* every valid program is unchanged → cstage == wwstage byte-id holds.
|
||||
*
|
||||
* reject row | index operand type | expect
|
||||
* -------------+----------------------+--------
|
||||
* by_float | f64 | REJECT
|
||||
* by_str | str | REJECT
|
||||
* by_ptr | *i32 | REJECT
|
||||
* by_bool | bool | REJECT
|
||||
*
|
||||
* accept row | index operand type | expect
|
||||
* -------------+----------------------+--------
|
||||
* by_int | int | BUILD, run 20
|
||||
* by_uint | uint | BUILD, run 30
|
||||
* by_enum | named enum value | BUILD, run 20
|
||||
* by_alias_int | type ix = i32 | BUILD, run 30 (load-bearing)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
outbin(char *dst, size_t n, const char *tmpdir, const char *src)
|
||||
{
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(dst, n, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(dst, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* A reject row passes only when the build FAILS *and* the stage's clean
|
||||
* diagnostic appears; a crash (segfault) wraps as a nonzero "w6c failed"
|
||||
* with no diagnostic, so the substring check distinguishes it (#20). */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
const char *diag, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/idxr_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/idxr_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/idxr_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
outbin(bin, sizeof bin, tmpdir, s);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "idxr[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "idxr[%s][%s]: nonzero exit but missing "
|
||||
"diagnostic \"%s\" (a crash, not a clean reject)\n",
|
||||
driver, label, diag);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/idxr_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/idxr_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/idxr_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "idxr[%s][%s]: build failed (integer index "
|
||||
"must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; const char *diag; };
|
||||
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "by_float",
|
||||
"package main;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let f: f64 = 1.0;\n"
|
||||
" return a[f];\n"
|
||||
"};\n",
|
||||
"index must be integer" },
|
||||
{ "by_str",
|
||||
"package main;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let s: str = \"x\";\n"
|
||||
" return a[s];\n"
|
||||
"};\n",
|
||||
"index must be integer" },
|
||||
{ "by_ptr",
|
||||
"package main;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let p: *i32 = nil;\n"
|
||||
" return a[p];\n"
|
||||
"};\n",
|
||||
"index must be integer" },
|
||||
{ "by_bool",
|
||||
"package main;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let b: bool = true;\n"
|
||||
" return a[b];\n"
|
||||
"};\n",
|
||||
"index must be integer" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "by_int",
|
||||
"package main;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let i: int = 1;\n"
|
||||
" return a[i];\n"
|
||||
"};\n",
|
||||
20 },
|
||||
{ "by_uint",
|
||||
"package main;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let i: uint = 2;\n"
|
||||
" return a[i];\n"
|
||||
"};\n",
|
||||
30 },
|
||||
{ "by_enum",
|
||||
"package main;\n"
|
||||
"type col = enum { A, B, C };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" return a[col.B];\n"
|
||||
"};\n",
|
||||
20 },
|
||||
{ "by_alias_int",
|
||||
"package main;\n"
|
||||
"type ix = i32;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [10i32, 20i32, 30i32];\n"
|
||||
" let i: ix = 2;\n"
|
||||
" return a[i];\n"
|
||||
"};\n",
|
||||
30 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "idxr: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
reject_rows[i].diag, d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "idxr[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "idxr: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("idxr: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* 852_variadic_body_reject (C3) — cstage and wwstage LOUD-REJECT a
|
||||
* BODIED function whose param list ends in a bare C-style `...`, while
|
||||
* still ACCEPTING the bodiless extern/@symbol prototype (the real FFI
|
||||
* path) and the bodied Hare-style `T...` typed variadic.
|
||||
*
|
||||
* DIVERGENCE (rule 7/8): harec places NO bodiless restriction on C
|
||||
* variadism — a bodied C-variadic fn is legal in Hare because that is
|
||||
* where vastart/vaarg/vaend live (ref/harec/src/check.c:3656). ww ships
|
||||
* no va* builtins (task #16), so a bodied bare-`...` body could never
|
||||
* read its varargs — the construct is inexpressible. Gating it to
|
||||
* bodiless decls is therefore a sound ww-pragmatic restriction, NOT
|
||||
* "what Hare does"; when #16's builtins land this gate reopens.
|
||||
*
|
||||
* Pre-fix: cstage silently ACCEPTED a bodied bare-`...` fn (cmd/wcc/
|
||||
* check.c N_FNDECL pass-2 only guarded body==NULL); wwstage SEGFAULTED
|
||||
* (resolvefnbody walked a `...` param with no type). The fix adds, at
|
||||
* the N_FNDECL body-check site of each stage, a guard rejecting
|
||||
* (body != NULL && C-style variadic). The bare C-style param is the one
|
||||
* with str=="..."; Hare-style `T...` carries a name + op==TK_ELLIPSIS,
|
||||
* so it is unaffected. Bodiless decls keep flowing the FFI path.
|
||||
*
|
||||
* row | shape | expect
|
||||
* -------------+----------------------------------------+--------
|
||||
* bodied_cvar | fn f(x: i64, ...) void = {...} | REJECT
|
||||
* bodied_bare | fn f(...) void = {...} | REJECT
|
||||
* extern_cvar | @symbol(..) fn ext(x: i64, ...) i64; | BUILD, run 7
|
||||
* hare_var | fn sum(xs: i64...) i64 = {...} | BUILD, run 7
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
outbin(char *dst, size_t n, const char *tmpdir, const char *src)
|
||||
{
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(dst, n, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(dst, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* The reject must be the gate's clean diagnostic, not any nonzero exit:
|
||||
* the wwstage gate's absence reverts to a w6c SEGFAULT, which the driver
|
||||
* also wraps as a nonzero "w6c failed". Matching this shared diagnostic
|
||||
* substring distinguishes a loud reject from a crash, so the wwstage
|
||||
* reject rows are non-vacuous.
|
||||
*/
|
||||
static const char rejmsg[] =
|
||||
"C-style variadic '...' requires a bodiless declaration";
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *label, const char *src,
|
||||
int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], errf[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/vbr_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/vbr_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/vbr_%d_e_%d.txt", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
outbin(bin, sizeof bin, tmpdir, s);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, bin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, rejmsg);
|
||||
runwait(rmcmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "vbr[%s][%s]: built ok, expected a reject\n",
|
||||
driver, label);
|
||||
return -1;
|
||||
}
|
||||
if (!hasmsg) {
|
||||
fprintf(stderr, "vbr[%s][%s]: nonzero exit but missing gate "
|
||||
"diagnostic (a crash, not a clean reject)\n", driver, label);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const char *label, const char *src, int i)
|
||||
{
|
||||
char s[128], tmpdir[64], cmd[1024], bin[128], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/vbr_ok_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/vbr_ok_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(bin, sizeof bin, "%s/vbr_ok_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, bin, s);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "vbr[%s][%s]: build failed (valid variadic "
|
||||
"must compile)\n", driver, label);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
int got = runwait(bin);
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
struct rejrow { const char *label; const char *src; };
|
||||
|
||||
static const struct rejrow reject_rows[] = {
|
||||
{ "bodied_cvar",
|
||||
"package main;\n"
|
||||
"fn f(x: i64, ...) void = { return; };\n"
|
||||
"fn main() i32 = { return 0; };\n" },
|
||||
{ "bodied_bare",
|
||||
"package main;\n"
|
||||
"fn f(...) void = { return; };\n"
|
||||
"fn main() i32 = { return 0; };\n" },
|
||||
};
|
||||
|
||||
struct okrow { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct okrow ok_rows[] = {
|
||||
{ "extern_cvar",
|
||||
"package main;\n"
|
||||
"@symbol(\"extfix\") fn extfix(x: i64, ...) i64;\n"
|
||||
"fn main() i32 = { return 7; };\n",
|
||||
7 },
|
||||
{ "hare_var",
|
||||
"package main;\n"
|
||||
"fn sum(xs: i64...) i64 = { return 0; };\n"
|
||||
"fn main() i32 = { return 7; };\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; } drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]);
|
||||
int nok = (int)(sizeof ok_rows / sizeof ok_rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "vbr: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nrej; i++) {
|
||||
total++;
|
||||
if (build_should_fail(drivers[d].path,
|
||||
reject_rows[i].label, reject_rows[i].src,
|
||||
d * 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
for (int i = 0; i < nok; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].path, ok_rows[i].label,
|
||||
ok_rows[i].src, d * 100 + i);
|
||||
if (got != ok_rows[i].want) {
|
||||
fprintf(stderr, "vbr[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, ok_rows[i].label,
|
||||
got, ok_rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "vbr: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("vbr: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 900_stdlib — verify each stdlib module parses, type-checks, and
|
||||
* codegens. We don't link or run them; they exist for downstream
|
||||
* users to import. As real applications come online they will start
|
||||
* exercising the bodies through the e2e harness.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *modules[] = {
|
||||
"lib/types/types.ww",
|
||||
"lib/ascii/ascii.ww",
|
||||
"lib/io/io.ww",
|
||||
"lib/strconv/strconv.ww",
|
||||
"lib/sort/sort.ww",
|
||||
"lib/encoding/utf8/utf8.ww",
|
||||
"lib/encoding/base32/base32.ww",
|
||||
"lib/hash/fnv/fnv.ww",
|
||||
"lib/hash/adler32/adler32.ww",
|
||||
"lib/hash/crc16/crc16.ww",
|
||||
"lib/hash/crc32/crc32.ww",
|
||||
"lib/hash/crc64/crc64.ww",
|
||||
"lib/hash/siphash/siphash.ww",
|
||||
"lib/math/random/random.ww",
|
||||
"lib/time/time.ww",
|
||||
"lib/c/libc/libc.ww",
|
||||
/* lib/bufio/bufio.ww, lib/bytes/bytes.ww, lib/errors/errors.ww,
|
||||
* lib/fmt/fmt.ww, lib/os/os.ww, and lib/strings/strings.ww moved
|
||||
* off this list: each has cross-module type refs that only resolve
|
||||
* once the driver concatenates `use`d modules. bufio / fmt
|
||||
* graduated to io.stream-based sinks (io.stream = *io.vtable,
|
||||
* io.eof, io.error); lib/os carries time.instant in filestat
|
||||
* post-Commit B; lib/errors.errno references os.errno / os.E* /
|
||||
* os.strerror (ww folds Hare's sys role into os); lib/strings.iterator
|
||||
* + lib/strings.next reference utf8.decoder / utf8.done;
|
||||
* lib/bytes.tokenize references the assert builtin + types.I64_MAX/MIN per
|
||||
* ref/hare/bytes/tokenize.ha:23-24,42-43; lib/encoding/hex and
|
||||
* lib/encoding/base64 graduated to Hare's io-streaming surface
|
||||
* (hex references io.handle / fmt.fprint / memio.dynamic / strconv /
|
||||
* errors.invalid; base64 references io.handle / memio.dynamic /
|
||||
* bytes.zero / strings.frombytes / errors.invalid); lib/path.path
|
||||
* is import-dependent post-c2-stack realignment ([MAX]u8 via
|
||||
* os.PATH_MAX def-dim + match over imported path error types).
|
||||
* Coverage lives at
|
||||
* lib/bufio/bufiotest.ww + lib/bytes/bytestest.ww +
|
||||
* lib/errors/errnotest.ww + lib/fmt/fmttest.ww + lib/os/stattest.ww
|
||||
* + lib/strings/stringstest.ww + lib/encoding/hex/hextest.ww +
|
||||
* lib/encoding/base64/base64_test.ww (wired at 998_bufio_run.c,
|
||||
* 967_bytes_run.c, 902_errno_run.c, 970_fmt_run.c, 976_stat_run.c,
|
||||
* 966_strings_run.c, 979_hex_run.c, 984_base64_run.c) + path at
|
||||
* lib/path/pathtest.ww (989_path_run.c), plus the
|
||||
* bufio.scanline / fmt.println e2e rows in test/wcc/700_e2e.c. */
|
||||
"lib/net/net.ww",
|
||||
NULL
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; modules[i]; i++, n++) {
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, modules[i]);
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c %s > /dev/null 2>&1",
|
||||
bin, path);
|
||||
int rc = system(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "stdlib FAIL: %s (rc=%d)\n", modules[i], rc);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (fail) { fprintf(stderr, "%d/%d stdlib modules failed\n", fail, n); return 1; }
|
||||
printf("stdlib: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 902_errno_run — execute the lib/errors errno @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 982_getopt_run / 980_memio_run. errnotest.ww carries its
|
||||
* own `export fn main()` that drives the @test fns and signals which
|
||||
* case failed via the exit code, so this file is a thin wrapper — no
|
||||
* @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/errors/errnotest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "errno_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("errno_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 904_ascii_run — execute the lib/ascii @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 967_bytes_run / 968_utf8_run:
|
||||
* asciitest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/ascii/asciitest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "ascii_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("ascii_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 904_tok_run — execute the lib/ww/syntax tokname/kwlookup @test fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 904_ascii_run / 967_bytes_run: toktest.ww
|
||||
* carries its own `export fn main()` that drives the @test fns and
|
||||
* signals which case failed via the exit code. This pins tokname's
|
||||
* if-ladder -> switch fold (every tkind + the unknown-kind fallback),
|
||||
* which the 990_selfhost wwdump diff only covers for kinds that appear
|
||||
* in its corpus.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/ww/syntax/toktest.ww";
|
||||
char path[1024], inc[1024], cmd[3072];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(inc, sizeof inc, "%s/lib/ww", cwd);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test -I %s %s", bin, inc, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "tok_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("tok_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 905_nkname_run — execute the lib/ww nkname @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 904_tok_run / 904_ascii_run: asttest.ww
|
||||
* carries its own `export fn main()` that drives the @test fn and
|
||||
* signals which case failed via the exit code. This pins nkname's
|
||||
* if-ladder -> switch fold (every nkind + the unknown-kind fallback),
|
||||
* which the 990_selfhost wwdump diff only covers for kinds that appear
|
||||
* in its corpus.
|
||||
*
|
||||
* Unlike 904_tok_run, the fixture imports the `syntax` package, whose
|
||||
* printer references the token tkind/tokname, so the run needs
|
||||
* `-I lib/ww` to resolve the `import syntax` directory package.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/ww/syntax/asttest.ww";
|
||||
char path[1024], inc[1024], cmd[3072];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(inc, sizeof inc, "%s/lib/ww", cwd);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test -I %s %s", bin, inc, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "nkname_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("nkname_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 908_ftos_run — execute the lib/strconv/test/ftostest fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 909_stof_run / 922_decimal_run: ftostest.ww
|
||||
* carries its own `export fn main()` that drives the @test fns (Ryū
|
||||
* f64tos: fixed + scientific + nan/±inf/zero) and signals which case
|
||||
* failed via the exit code (signalled + 10).
|
||||
*
|
||||
* The fixture lives in lib/strconv/test/ (not lib/strconv/) so `import
|
||||
* strconv` resolves to the strconv DIRECTORY (full package: strconv.ww +
|
||||
* decimal.ww + stof_data.ww + stof.ww + ftos_data.ww + ftos.ww) rather
|
||||
* than shadowing on the strconv.ww FILE — same rationale as 922.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/strconv/test/ftostest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "ftos_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("ftos_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 909_stof_run — execute the lib/strconv/test/stoftest fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 922_decimal_run: stoftest.ww carries its own
|
||||
* `export fn main()` that drives the @test fns (stof64/stof32 DEC + hex
|
||||
* vectors, bit-exact) and signals which case failed via the exit code
|
||||
* (signalled + 10, so cases 1..4 → exit 11..14).
|
||||
*
|
||||
* The fixture lives in lib/strconv/test/ (not lib/strconv/) so
|
||||
* `import strconv` resolves to the lib/strconv DIRECTORY (full package:
|
||||
* strconv.ww + decimal.ww + stof_data.ww + stof.ww), not the strconv.ww
|
||||
* FILE — same rationale as 922_decimal_run.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/strconv/test/stoftest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "stof_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("stof_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 922_strconv_int_run — execute the lib/strconv/test/inttest fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 922_decimal_run / 909_stof_run: inttest.ww
|
||||
* carries its own `export fn main()` that drives the @test fns and
|
||||
* signals which case failed via the exit code (signalled + 10), so a
|
||||
* non-zero exit pinpoints the failing scenario.
|
||||
*
|
||||
* The fixture lives in lib/strconv/test/ (not lib/strconv/) so
|
||||
* `import strconv` resolves to the lib/strconv DIRECTORY rather than
|
||||
* shadowing on the strconv.ww FILE — same rationale as 922_decimal_run.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/strconv/test/inttest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "strconv_int_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("strconv_int_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* 940_str_forrange_loopvar_run — corpus coverage for the step-3 Fold 1
|
||||
* fold: ranging a `str` value-form (`for (let b .. s)`) and reading the
|
||||
* loop var `b` back must narrow to MOVZBQ (u8 zero-extend), matching
|
||||
* cstage. Pre-fold the wwstage cgen registered the loop var with a nil
|
||||
* tnode (str had no element node the way []u8 does), so the read-back
|
||||
* fell through localloadop to a wide MOVQ; cstage's checker stamps the
|
||||
* binding u8, so it narrowed. The fold synthesises the u8 element off
|
||||
* str.sub (Phase 2 F1) so the loop var carries a u8 tnode and the
|
||||
* existing narrow-load logic fires on its own.
|
||||
*
|
||||
* REAL GATE IS SHAPE BYTE-ID, NOT RUNTIME. The MOVQ-vs-MOVZBQ
|
||||
* divergence is runtime-benign: the slot is always written by a
|
||||
* MOVZBQ-into-AX then MOVQ-AX-into-slot, so the upper bytes are already
|
||||
* zero — a MOVQ read-back yields the same value a MOVZBQ would. A
|
||||
* runtime probe therefore passes on BOTH the buggy and the fixed code
|
||||
* and CANNOT distinguish them. The load-bearing gate is diffing the
|
||||
* cstage (w6c) and wwstage (w6c_ww) .s at the loop-var read:
|
||||
* pre-fold: cstage MOVZBQ vs wwstage MOVQ (1-line diff)
|
||||
* post-fold: byte-identical
|
||||
* This fixture exists for corpus presence — it confirms the path lowers
|
||||
* and runs correctly through both drivers; it does not by itself prove
|
||||
* the narrow.
|
||||
*
|
||||
* Loop-var read-back is exercised two ways the fold touches:
|
||||
* - Site A: pass `b` to a fn (`use1(b)` → cgident read into an arg).
|
||||
* - Site B: use `b` in an arithmetic expression (`sum += b: i32`).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Site A — read-back into a fn arg. "AB" is 0x41,0x42; the callee
|
||||
* checks each byte zero-extends to its exact value, so a botched
|
||||
* wide read (had the slot held garbage) would mismatch. */
|
||||
{ "sitea_arg_readback",
|
||||
"fn check(x: u8) i32 = {\n"
|
||||
" if (x: i32 < 65) { return 1; };\n"
|
||||
" if (x: i32 > 66) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: str = \"AB\";\n"
|
||||
" for (let b .. s) {\n"
|
||||
" if (check(b) != 0) { return 1; };\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Site B — read-back in an arithmetic expression. Sum the bytes of
|
||||
* "AB" (0x41 + 0x42 = 131) and confirm. */
|
||||
{ "siteb_expr_readback",
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: str = \"AB\";\n"
|
||||
" let sum: i32 = 0;\n"
|
||||
" for (let b .. s) {\n"
|
||||
" sum += b: i32;\n"
|
||||
" };\n"
|
||||
" if (sum != 131) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[96], src[160], outbin[160], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strforrange_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/strforrange_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/strforrange_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_forrange_loopvar_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_forrange_loopvar_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_forrange_loopvar_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_forrange_loopvar_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* 944_alias_cgen_b6_run — SLIM CARRIER (#5-C3 alias fold-2). The 23 K_RUN value
|
||||
* rows migrated to test/lang/alias_cgen_b6_test.ww (@test, cs==ww byte-id); the
|
||||
* symmetric K_BUILDERR rejects (fsarg0_loud_ctl, try_loud_38b) to
|
||||
* test/wcc/data/alias_{fsarg0_loud,try_loud_38b}/case.ww runww.
|
||||
*
|
||||
* What survives here is the one IRREDUCIBLE Group-B row: BOTH stages reject the
|
||||
* same input but with DIFFERENT diagnostics — a held cs!=ww divergence (a
|
||||
* rule-10 smell, tracked not silently merged). A single-substring runww
|
||||
* //ww:error would erase the distinction; this carrier asserts EACH stage's own
|
||||
* substring. cstage rejects with the #271/#165 SSE-eightbyte-transport reason;
|
||||
* wwstage rejects with the #272/#276/#277 aggregate-return scalar-default
|
||||
* reason (the float-bearing alias struct arg from a non-ident source).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
errlog_has(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[8192];
|
||||
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[got] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* cstage (w6c) rejects with cserr; wwstage (w6c_ww) rejects with wwerr. */
|
||||
static int
|
||||
run_row(const char *bin, const char *label, const char *src,
|
||||
const char *cserr, const char *wwerr, int i)
|
||||
{
|
||||
char dir[96], srcf[160], cse[160], wwe[160], ss[160], rm[200], cmd[2048];
|
||||
snprintf(dir, sizeof dir, "/tmp/ab6_%d_%d", getpid(), i);
|
||||
mkdir(dir, 0755);
|
||||
snprintf(srcf, sizeof srcf, "%s/c.ww", dir);
|
||||
snprintf(cse, sizeof cse, "%s/cs.err", dir);
|
||||
snprintf(wwe, sizeof wwe, "%s/ww.err", dir);
|
||||
snprintf(ss, sizeof ss, "%s/o.s", dir);
|
||||
snprintf(rm, sizeof rm, "rm -rf %s", dir);
|
||||
|
||||
FILE *f = fopen(srcf, "wb");
|
||||
if (!f) { runwait(rm); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
int ok = 1;
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>%s", bin, ss, srcf, cse);
|
||||
int crc = runwait(cmd);
|
||||
if (!(crc != 0 && errlog_has(cse, cserr))) {
|
||||
fprintf(stderr, "row[%s]: cstage expected reject \"%s\" (rc=%d)\n",
|
||||
label, cserr, crc);
|
||||
ok = 0;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>%s", bin, ss, srcf, wwe);
|
||||
int wrc = runwait(cmd);
|
||||
if (!(wrc != 0 && errlog_has(wwe, wwerr))) {
|
||||
fprintf(stderr, "row[%s]: wwstage expected reject \"%s\" (rc=%d)\n",
|
||||
label, wwerr, wrc);
|
||||
ok = 0;
|
||||
}
|
||||
runwait(rm);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; const char *cserr;
|
||||
const char *wwerr; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "fsarg2_bound",
|
||||
"package main;\n"
|
||||
"type fs0 = struct { x: f64 };\n"
|
||||
"type fs = fs0;\n"
|
||||
"fn mk() fs = {\n"
|
||||
" let s: fs; s.x = 2.5;\n"
|
||||
" return s;\n"
|
||||
"};\n"
|
||||
"fn g(p: fs) f64 = { return p.x; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (g(mk()) != 2.5) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
"#271/#165: float-bearing struct arg from a non-ident source",
|
||||
"#272/#276/#277: aggregate return reaches scalar default" },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int fail = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (run_row(bin, rows[i].label, rows[i].src, rows[i].cserr,
|
||||
rows[i].wwerr, i) != 0)
|
||||
fail++;
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "alias_cgen_b6 (slim): %d/%d rows failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("alias_cgen_b6 (slim): %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* 944_alias_emit_b7_run — SLIM CARRIER (#5-C3 alias fold-2). The 8 K_RUN value
|
||||
* rows migrated to test/lang/alias_emit_b7_test.ww (@test, cs==ww byte-id); the
|
||||
* symmetric K_BUILDERR rejects (slcstr_plain_ctl, slcslc_2lvl) to
|
||||
* test/wcc/data/alias_{slcstr_plain,slcslc}/case.ww runww //ww:error.
|
||||
*
|
||||
* What survives here are the 2 IRREDUCIBLE both-stage emit-reject rows neither
|
||||
* in-language surface can host (ken's emit_slice_data observation cells —
|
||||
* load-bearing codegen pins, MUST NOT move):
|
||||
* • slcstr_2lvl / slctag_2lvl: a slice-of-{str,tagged} literal GLOBAL is
|
||||
* admitted by BOTH checkers and then LOUD-REJECTED at emit_slice_data
|
||||
* ("slice-of-{str,slice,tagged} literal static-init unsupported", a rule-7
|
||||
* deferred #10 follow-up) in BOTH stages with the IDENTICAL message. @test
|
||||
* needs a build, runww //ww:error drives one path — a dual-stage emit
|
||||
* reject fits neither, so each stage's reject is asserted directly here.
|
||||
*
|
||||
* HISTORY (#28): Group A (slc_2lvl []my64b / slc_plain_ctl []i64, cstage-runs /
|
||||
* wwstage-checker-rejects) and Group B's old wwstage HALF (the ww checker
|
||||
* rejected EVERY slice-literal global at "let: not assignable") were both that
|
||||
* pre-#28 divergence. Once the module-scope slice-from-arrlit admission landed
|
||||
* (selfhost check.ww checkletassign), wwstage admits these globals at the
|
||||
* checker exactly as cstage does: Group A converged to cs==ww build+run and
|
||||
* migrated to test/lang/slice_global_arg_test.ww; Group B converged to the
|
||||
* shared emit_slice_data reject below.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
errlog_has(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[8192];
|
||||
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[got] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
#define M_CSRUN_WWERR 0 /* cstage build+run exit 0; wwstage w6c_ww rejects wwerr */
|
||||
#define M_BOTHERR 1 /* cstage w6c rejects cserr; wwstage w6c_ww rejects wwerr */
|
||||
|
||||
struct row { const char *label; const char *src; int mode;
|
||||
const char *cserr; const char *wwerr; };
|
||||
|
||||
static int
|
||||
run_row(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char dir[96], srcf[160], outbin[160], cse[160], wwe[160], ss[160],
|
||||
rm[200], cmd[2048];
|
||||
snprintf(dir, sizeof dir, "/tmp/ab7_%d_%d", getpid(), i);
|
||||
mkdir(dir, 0755);
|
||||
snprintf(srcf, sizeof srcf, "%s/c.ww", dir);
|
||||
snprintf(outbin, sizeof outbin, "%s/bin", dir);
|
||||
snprintf(cse, sizeof cse, "%s/cs.err", dir);
|
||||
snprintf(wwe, sizeof wwe, "%s/ww.err", dir);
|
||||
snprintf(ss, sizeof ss, "%s/o.s", dir);
|
||||
snprintf(rm, sizeof rm, "rm -rf %s", dir);
|
||||
|
||||
FILE *f = fopen(srcf, "wb");
|
||||
if (!f) { runwait(rm); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
int ok = 1;
|
||||
if (r->mode == M_CSRUN_WWERR) {
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 20 %s/ww build -o %s %s >/dev/null 2>&1",
|
||||
bin, outbin, srcf);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage build failed (want run)\n",
|
||||
r->label);
|
||||
ok = 0;
|
||||
} else if (runwait(outbin) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage run != 0\n", r->label);
|
||||
ok = 0;
|
||||
}
|
||||
} else {
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>%s", bin, ss, srcf, cse);
|
||||
int crc = runwait(cmd);
|
||||
if (!(crc != 0 && errlog_has(cse, r->cserr))) {
|
||||
fprintf(stderr, "row[%s]: cstage expected reject \"%s\" (rc=%d)\n",
|
||||
r->label, r->cserr, crc);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>%s", bin, ss, srcf, wwe);
|
||||
int wrc = runwait(cmd);
|
||||
if (!(wrc != 0 && errlog_has(wwe, r->wwerr))) {
|
||||
fprintf(stderr, "row[%s]: wwstage expected reject \"%s\" (rc=%d)\n",
|
||||
r->label, r->wwerr, wrc);
|
||||
ok = 0;
|
||||
}
|
||||
runwait(rm);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "slcstr_2lvl",
|
||||
"package main;\n"
|
||||
"type ms0 = str;\n"
|
||||
"type ms = ms0;\n"
|
||||
"let G: []ms = [\"aa\", \"bbb\"];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (len(G[0]) != 2) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", M_BOTHERR,
|
||||
"slice-of-{str,slice,tagged} literal static-init unsupported",
|
||||
"slice-of-{str,slice,tagged} literal static-init unsupported" },
|
||||
{ "slctag_2lvl",
|
||||
"package main;\n"
|
||||
"type u0 = (void | i64);\n"
|
||||
"type u1 = u0;\n"
|
||||
"let G: []u1 = [1i64, 2i64];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (len(G) != 2) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", M_BOTHERR,
|
||||
"slice-of-{str,slice,tagged} literal static-init unsupported",
|
||||
"slice-of-{str,slice,tagged} literal static-init unsupported" },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int fail = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (run_row(bin, &rows[i], i) != 0) fail++;
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "alias_emit_b7 (slim): %d/%d rows failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("alias_emit_b7 (slim): %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* 944_array_zeroinit_run — #84: uninit `[N]T` ARRAY locals were NEVER
|
||||
* zero-filled. The bare-let no-rhs zero-fill (cmd/w6c/cgen.c N_LET else
|
||||
* + selfhost/cmd/wcc/cgenstmt.ww cglet) was gated `sz>8 && !TY_ARRAY`,
|
||||
* so `let a: [3]int;` read whatever the stack held — BOTH stages,
|
||||
* both-wrong-IDENTICAL, byte-id-blind (#263-class): the cs==ww gate
|
||||
* cannot see a shared-garbage read. The fix drops the `!TY_ARRAY`
|
||||
* exclusion symmetrically (arrays zero-fill like every other
|
||||
* composite, Go-zero per user ruling); the zero-fill extent is the
|
||||
* array's chased ABI size (lu->size / chased tinfo.size, NOT the
|
||||
* slot-padded size), so a non-8-multiple array zeroes its exact byte
|
||||
* count.
|
||||
*
|
||||
* GATE-BLIND → the load-bearing net is a RUNTIME zero-read, NOT asm
|
||||
* presence: a clean (fresh) stack frame is already 0, masking the bug.
|
||||
* So every row DIRTIES the stack first (a filler fn writes 165 across
|
||||
* a 512B frame), then declares the uninit array in a sibling frame
|
||||
* that reuses that region, and asserts the read is 0. byte-id won't
|
||||
* catch a regression here — only run-and-check-value will.
|
||||
*
|
||||
* row | shape (after a 165-dirtied stack) | want
|
||||
* -----------------+------------------------------------------+-----
|
||||
* array_elem_0 | let a: [3]int; sum a[0..2] | 0
|
||||
* narrow_array_0 | let a: [4]u32; sum a[0..3] (esz 4) | 0
|
||||
* nonmult8_array_0 | let b: [20]u8; sum b[0..19] (ABI 20 != |
|
||||
* | slot 24 — extent-source guard) | 0
|
||||
* array_2d_0 | let m: [2][2]int; sum all 4 | 0
|
||||
* control_initd | let c: [3]int = [7,8,9]; sum (no-regress)| 24
|
||||
*
|
||||
* Each row also asserts cstage/wwstage .s byte-id (rule 10 — the fix
|
||||
* adds the SAME insns to both). NNN<950, self-contained (/tmp, no
|
||||
* imports) — rule-14's selfhost-sibling race does not apply (941/944
|
||||
* precedent). #84 array-only; the tagged/plain-*T reject-set is #113.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp_eq(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb");
|
||||
FILE *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||
int rc = 0;
|
||||
for (;;) {
|
||||
int ca = fgetc(fa), cb = fgetc(fb);
|
||||
if (ca != cb) { rc = -1; break; }
|
||||
if (ca == EOF) break;
|
||||
}
|
||||
fclose(fa); fclose(fb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
/* Every src dirties the stack via dirty() (a 512B [64]int filled with
|
||||
* 165) before the reader's uninit array lands on that region. */
|
||||
#define DIRTY \
|
||||
"fn dirty() int = {\n" \
|
||||
" let j: [64]int;\n" \
|
||||
" for (let i: int = 0; i < 64; i += 1) { j[i] = 165; };\n" \
|
||||
" return j[0];\n" \
|
||||
"};\n"
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "array_elem_0",
|
||||
"package main;\n" DIRTY
|
||||
"fn rd() i32 = { let a: [3]int; return (a[0]+a[1]+a[2]): i32; };\n"
|
||||
"export fn main() i32 = { dirty(); return rd(); };\n", 0 },
|
||||
{ "narrow_array_0",
|
||||
"package main;\n" DIRTY
|
||||
"fn rd() i32 = { let a: [4]u32;\n"
|
||||
" return (a[0]+a[1]+a[2]+a[3]): i32; };\n"
|
||||
"export fn main() i32 = { dirty(); return rd(); };\n", 0 },
|
||||
{ "nonmult8_array_0",
|
||||
"package main;\n" DIRTY
|
||||
"fn rd() i32 = { let b: [20]u8; let s: i32 = 0;\n"
|
||||
" for (let i: int = 0; i < 20; i += 1) { s += b[i]: i32; };\n"
|
||||
" return s; };\n"
|
||||
"export fn main() i32 = { dirty(); return rd(); };\n", 0 },
|
||||
{ "array_2d_0",
|
||||
"package main;\n" DIRTY
|
||||
"fn rd() i32 = { let m: [2][2]int;\n"
|
||||
" return (m[0][0]+m[0][1]+m[1][0]+m[1][1]): i32; };\n"
|
||||
"export fn main() i32 = { dirty(); return rd(); };\n", 0 },
|
||||
/* no-regress control: an INITIALIZED array keeps its values. */
|
||||
{ "control_initd",
|
||||
"package main;\n" DIRTY
|
||||
"fn rd() i32 = { let c: [3]int = [7, 8, 9];\n"
|
||||
" return (c[0]+c[1]+c[2]): i32; };\n"
|
||||
"export fn main() i32 = { dirty(); return rd(); };\n", 24 },
|
||||
};
|
||||
|
||||
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[96], src[160], outbin[160], errf[160], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/azi_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/azi_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/azi_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/err", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 20 %s build -o %s %s >/dev/null 2>%s",
|
||||
driver, outbin, src, errf);
|
||||
int brc = runwait(cmd);
|
||||
if (brc != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
|
||||
r->label, driver, got, r->want);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cs==ww .s byte-id (rule 10). */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[96], cs[96], ws[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/azi_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/azi_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/azi_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
int rc = slurp_eq(cs, ws);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[2120], wdrv[2120];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "array_zeroinit: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("array_zeroinit: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 948_selfimport — #16 check-(c): a package may not import itself.
|
||||
*
|
||||
* Both compiler stages (w6c, w6c_ww) must loud-reject `package foo;
|
||||
* import foo;` — nonzero exit AND a "self-import" diagnostic on stderr.
|
||||
* Compile-only (`-o /dev/null`): w6c/w6c_ww do not expand imports (the
|
||||
* driver does), so a lone fixture triggers check-(c) directly. This is
|
||||
* NOT a ww_ww DRIVER test — it never invokes the driver — so it is
|
||||
* Phase-1-safe and does not race the 990-997 byte-id gates (rule 14).
|
||||
*
|
||||
* check-(a) unused + (b)/(d) membership are DEFERRED to task #8 (ww's
|
||||
* filename-keyed file-inclusion imports lack import->file->symbol
|
||||
* provenance); only self-import is package-model-independent.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *
|
||||
absbin(void)
|
||||
{
|
||||
const char *b = getenv("BIN");
|
||||
if (!b) b = "out/bin";
|
||||
if (b[0] == '/') return b;
|
||||
static char buf[2048];
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* reject — `<comp> <fixture> -o /dev/null` must exit nonzero and print
|
||||
* <substr> on stderr. */
|
||||
static int
|
||||
reject(const char *bin, const char *comp, const char *fixture,
|
||||
const char *substr)
|
||||
{
|
||||
int pid = getpid();
|
||||
char errf[256], cmd[4096], line[4096];
|
||||
snprintf(errf, sizeof errf, "/tmp/si948_%s_%d.err", comp, pid);
|
||||
snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>%s",
|
||||
bin, comp, fixture, errf);
|
||||
|
||||
int rc = system(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "948 FAIL: %s accepted %s (expected reject)\n",
|
||||
comp, fixture);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
FILE *f = fopen(errf, "r");
|
||||
if (f) {
|
||||
while (fgets(line, sizeof line, f))
|
||||
if (strstr(line, substr)) { found = 1; break; }
|
||||
fclose(f);
|
||||
}
|
||||
unlink(errf);
|
||||
if (!found) {
|
||||
fprintf(stderr, "948 FAIL: %s rejected %s but no '%s' on stderr\n",
|
||||
comp, fixture, substr);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *comp;
|
||||
const char *fixture;
|
||||
const char *substr;
|
||||
} rows[] = {
|
||||
{ "w6c", "test/wcc/data/selfimport.ww", "self-import" },
|
||||
{ "w6c_ww", "test/wcc/data/selfimport.ww", "self-import" },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) { fprintf(stderr, "948 FAIL: getcwd\n"); return 1; }
|
||||
|
||||
for (size_t i = 0; i < sizeof rows / sizeof rows[0]; i++)
|
||||
if (reject(bin, rows[i].comp, rows[i].fixture, rows[i].substr) != 0)
|
||||
return 1;
|
||||
|
||||
printf("self-import rejected by w6c and w6c_ww\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* 953_arrlit_slice_run -- SLIMMED to the one irreducible asymmetric row.
|
||||
*
|
||||
* The #25/#31 array-literal-slice corpus migrated to fold-2 homes (#5-C6): the
|
||||
* value rows -> test/lang/arrlit_slice_test.ww (@test + T2 byte-id); the
|
||||
* reject_oob / reject_call / reject_ret rows -> test/wcc/data/arrlit_slice_*
|
||||
* runww //ww:error carriers (strong shared substrings, both stages).
|
||||
*
|
||||
* reject_assign CANNOT move to a runww //ww:error carrier: both stages REJECT an
|
||||
* array-LITERAL assigned to a slice, but at DIFFERENT diagnostic LAYERS -- cstage
|
||||
* at assignability ("cannot assign [3]int to []i32") and wwstage at the borrow
|
||||
* gate ("array literal cannot borrow as a slice here; bind it to a `let` first").
|
||||
* BOTH reject CORRECTLY (the borrow is supported only at a `let` init, #31/#33) --
|
||||
* this is a benign diag-LAYER divergence, NOT a miscompile, so NO bug ticket. No
|
||||
* honest shared substring exists for a #20-non-vacuity carrier, and a build-reject
|
||||
* emits no .s (no byte-id surface). It stays a slim C pin asserting rc!=0 BOTH
|
||||
* stages -- the position the stages diverge, most worth pinning.
|
||||
*
|
||||
* Non-vacuity self-check (the pin CAN go RED): `valid_src` assigns a NAMED array
|
||||
* (an addressable backing), which the #258 borrow ACCEPTS on both stages (rc==0);
|
||||
* it proves the pin discriminates the array-LITERAL reject from a legitimate
|
||||
* array-to-slice assign, not "all builds error". Mutation-checked: binding the
|
||||
* literal to a `let` first (`let t=[1,2,3]; s=t;`) flips both stages to accept ->
|
||||
* the reject leg goes RED (verified).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* reject_assign: an array LITERAL in assign position has no addressable backing
|
||||
* -- both stages reject (cstage assignability, wwstage borrow gate). */
|
||||
static const char *reject_src =
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: []i32 = [0, 0];\n"
|
||||
" s = [1, 2, 3];\n"
|
||||
" return s[0];\n"
|
||||
"};\n";
|
||||
|
||||
/* Vacuity control: a NAMED array assigned as a slice borrow (#258) -- both
|
||||
* stages accept. Proves the pin distinguishes accept from reject. */
|
||||
static const char *valid_src =
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: [3]i32 = [1, 2, 3];\n"
|
||||
" let s: []i32 = a[0:1];\n"
|
||||
" s = a;\n"
|
||||
" return s[0];\n"
|
||||
"};\n";
|
||||
|
||||
/* build_only -- write `src`, build via `driver` (no run). Returns the build
|
||||
* exit code (0 = accepted, nonzero = rejected). */
|
||||
static int
|
||||
build_only(const char *driver, const char *src, int seq)
|
||||
{
|
||||
char tmpdir[96], srcp[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/as_%d_%d", getpid(), seq);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(srcp, sizeof srcp, "%s/c.ww", tmpdir);
|
||||
snprintf(outbin, sizeof outbin, "%s/c", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(srcp, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "timeout 20 %s build -o %s %s >/dev/null 2>&1",
|
||||
driver, outbin, srcp);
|
||||
int brc = runwait(cmd);
|
||||
runwait(rmcmd);
|
||||
return brc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[2120], wdrv[2120];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int fail = 0, seq = 0;
|
||||
|
||||
/* reject leg: both stages must REJECT (rc!=0). */
|
||||
if (build_only(cdrv, reject_src, seq++) == 0) {
|
||||
fprintf(stderr, "arrlit_slice[cs]: reject_assign ACCEPTED (want reject)\n");
|
||||
fail++;
|
||||
}
|
||||
if (wwpresent && build_only(wdrv, reject_src, seq++) == 0) {
|
||||
fprintf(stderr, "arrlit_slice[ww]: reject_assign ACCEPTED (want reject)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* vacuity control: both stages must ACCEPT (rc==0). */
|
||||
if (build_only(cdrv, valid_src, seq++) != 0) {
|
||||
fprintf(stderr, "arrlit_slice[cs]: vacuity control REJECTED (pin may be vacuous)\n");
|
||||
fail++;
|
||||
}
|
||||
if (wwpresent && build_only(wdrv, valid_src, seq++) != 0) {
|
||||
fprintf(stderr, "arrlit_slice[ww]: vacuity control REJECTED (pin may be vacuous)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "arrlit_slice: %d checks failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("arrlit_slice: reject_assign pin ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 966_strings_run — execute the lib/strings @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 967_bytes_run / 968_utf8_run / 979_hex_run:
|
||||
* stringstest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/strings/stringstest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "strings_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("strings_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 967_bytes_run — execute the lib/bytes @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 979_hex_run / 968_utf8_run:
|
||||
* bytestest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/bytes/bytestest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "bytes_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("bytes_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 968_utf8_run — execute the lib/encoding/utf8 @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Thin wrapper mirroring 979_hex_run / 977_time_run: utf8test.ww carries
|
||||
* its own `export fn main()` that drives the @test fns and signals which
|
||||
* case failed via the exit code (signalled + 10).
|
||||
*
|
||||
* Slot 968: stdlib block 970–989 was full at landing time.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/encoding/utf8/utf8test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "utf8_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("utf8_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 969_checked_run — execute the lib/math/checked @test fixture under
|
||||
* the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 966_strings_run / 967_bytes_run / 968_utf8_run / 980_memio
|
||||
* _run / 999_random_run. checked_test.ww carries its own `export fn
|
||||
* main()` that drives the @test fns and signals which case failed via
|
||||
* the exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/math/checked/checked_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "checked_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("checked_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 970_fmt_run — execute the lib/fmt @test fixture under the C-side
|
||||
* `ww run` driver and assert exit 0.
|
||||
*
|
||||
* fmt sits below io conceptually (sinks formatted bytes into an
|
||||
* io.stream); slotting at 970 keeps it ahead of the 980-989
|
||||
* stdlib-run sub-band that depends on it. fmttest.ww carries its
|
||||
* own `export fn main()` that drives the @test fns and signals
|
||||
* which case failed via the exit code, so this file is a thin
|
||||
* wrapper — no @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/fmt/fmttest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "fmt_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("fmt_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 971_log_run — execute the lib/log @test fixture under the C-side
|
||||
* `ww run` driver and assert exit 0.
|
||||
*
|
||||
* log sits at the same tier as fmt (both consume io.stream sinks);
|
||||
* slotting at 971, adjacent to 970_fmt_run, keeps both stdlib-runtime
|
||||
* tests ahead of the 980-989 sub-band. logtest.ww carries its own
|
||||
* `export fn main()` that drives the @test fns and signals which
|
||||
* case failed via the exit code, so this file is a thin wrapper —
|
||||
* no @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/log/logtest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "log_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("log_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 972_fnmatch_run — execute the lib/fnmatch @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* fnmatch is a pure leaf (depends on ascii + strings only) sitting
|
||||
* alongside fmt (970) and log (971) in the stdlib-runtime band. The
|
||||
* fixture (fnmatchtest.ww) carries its own `export fn main()` that
|
||||
* drives the @test fns and signals which case failed via the exit
|
||||
* code, so this file is a thin wrapper — no @test scanning, no
|
||||
* synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/fnmatch/fnmatchtest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "fnmatch_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("fnmatch_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 973_shlex_run — execute the lib/shlex @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* shlex is a leaf over io + memio (split returns []str of dup'd
|
||||
* elements; quote writes through *io.stream). The fixture
|
||||
* (shlextest.ww) carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code, so this
|
||||
* file is a thin wrapper — no @test scanning, no synthetic main
|
||||
* generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/shlex/shlextest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "shlex_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("shlex_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 977_time_run — execute the lib/time @test fixture under the C-side
|
||||
* `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 970_fmt_run / 971_log_run / 976_stat_run:
|
||||
* timetest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/time/timetest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "time_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("time_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,458 +0,0 @@
|
||||
/*
|
||||
* 978_intdiv_signed — runtime semantics of integer `/` and `%` across
|
||||
* the {i8,i16,i32,i64,u8,u16,u32,u64} × {/, %} matrix, plus the
|
||||
* width-boundary minima.
|
||||
*
|
||||
* Class B (shared miscompile, not divergence): both cstage and
|
||||
* wwstage previously emitted `MOVQ $0, DX + IDIVQ` on the signed
|
||||
* arm, treating a negative dividend as a huge unsigned 128-bit
|
||||
* value. Bootstrap byte-id passed throughout — both stages stomped
|
||||
* the same way — so only a semantic runtime test catches it. The
|
||||
* fix swaps the prep to `CQO` (sign-extend RAX into RDX:RAX) on
|
||||
* the signed arm of TK_SLASH/TK_PERCENT; unsigned stays MOVQ-zero +
|
||||
* DIVQ. See cmd/w6c/cgen.c TK_SLASH/TK_PERCENT and the matching
|
||||
* selfhost/cmd/wcc/cgenexpr.ww branches.
|
||||
*
|
||||
* Unsigned rows pin the DIVQ arm against future regression — the
|
||||
* cgen.c comment from #41 (high-bit-set u64 / 2) is exactly the
|
||||
* shape that the unsigned-vs-signed dispatch protects.
|
||||
*
|
||||
* Same dual-driver runner as 640_int_cast_signed.c: each row gets
|
||||
* compiled by both cstage `ww` and wwstage `ww_ww` (when built),
|
||||
* with the binary exit code carrying the per-case verdict (42 = ok).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* ---- i64 signed: negative dividend, positive divisor ---------- */
|
||||
{ "i64_div_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = -100i64;\n"
|
||||
" let b: i64 = 1000000000i64;\n"
|
||||
" if (a / b == 0i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i64_mod_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = -100i64;\n"
|
||||
" let b: i64 = 1000000000i64;\n"
|
||||
" if (a % b == -100i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* ---- i64 signed: positive dividend, negative divisor ---------- */
|
||||
{ "i64_div_neg_divisor",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = 50i64;\n"
|
||||
" let b: i64 = -3i64;\n"
|
||||
" if (a / b == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i64_mod_neg_divisor",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = 50i64;\n"
|
||||
" let b: i64 = -3i64;\n"
|
||||
" if (a % b == 2i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* ---- i64 signed: both negative -------------------------------- */
|
||||
{ "i64_div_both_negative",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = -50i64;\n"
|
||||
" let b: i64 = -3i64;\n"
|
||||
" if (a / b == 16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i64_mod_both_negative",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = -50i64;\n"
|
||||
" let b: i64 = -3i64;\n"
|
||||
" if (a % b == -2i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* ---- i64 signed: both positive (sanity / regression anchor) --- */
|
||||
{ "i64_div_both_positive",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = 50i64;\n"
|
||||
" let b: i64 = 3i64;\n"
|
||||
" if (a / b == 16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i64_mod_both_positive",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = 50i64;\n"
|
||||
" let b: i64 = 3i64;\n"
|
||||
" if (a % b == 2i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* ---- i64 boundary: INT64_MIN ---------------------------------- *
|
||||
* INT64_MIN / -1 would SIGFPE (signed overflow); /2 instead.
|
||||
* The literal `-9223372036854775808i64` triggers task #17
|
||||
* (wwstage NEGQ-over-imm drops digits → `MOVQ $-, AX`), so
|
||||
* we spell it `(-INT64_MAX) - 1` to sidestep that orthogonal
|
||||
* bug until #17 lands. */
|
||||
{ "i64_div_INT64_MIN_by_two",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = (-9223372036854775807i64) - 1i64;\n"
|
||||
" let b: i64 = 2i64;\n"
|
||||
" if (a / b == -4611686018427387904i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i64_mod_INT64_MIN_by_two",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = (-9223372036854775807i64) - 1i64;\n"
|
||||
" let b: i64 = 2i64;\n"
|
||||
" if (a % b == 0i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- i32 signed ----------------------------------------------- */
|
||||
{ "i32_div_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = -100i32;\n"
|
||||
" let y: i32 = 7i32;\n"
|
||||
" if (x / y == -14i32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i32_mod_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = -100i32;\n"
|
||||
" let y: i32 = 7i32;\n"
|
||||
" if (x % y == -2i32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i32_div_INT32_MIN_by_two",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = -2147483648i32;\n"
|
||||
" let y: i32 = 2i32;\n"
|
||||
" if (x / y == -1073741824i32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i32_mod_INT32_MIN_by_two",
|
||||
"fn main() i32 = {\n"
|
||||
" let x: i32 = -2147483648i32;\n"
|
||||
" let y: i32 = 2i32;\n"
|
||||
" if (x % y == 0i32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- i16 signed ----------------------------------------------- */
|
||||
{ "i16_div_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i16 = -100i16;\n"
|
||||
" let b: i16 = 7i16;\n"
|
||||
" if (a / b == -14i16) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i16_mod_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i16 = -100i16;\n"
|
||||
" let b: i16 = 7i16;\n"
|
||||
" if (a % b == -2i16) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i16_div_INT16_MIN_by_two",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i16 = -32768i16;\n"
|
||||
" let b: i16 = 2i16;\n"
|
||||
" if (a / b == -16384i16) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- i8 signed ------------------------------------------------ */
|
||||
{ "i8_div_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i8 = -100i8;\n"
|
||||
" let b: i8 = 7i8;\n"
|
||||
" if (a / b == -14i8) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i8_mod_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i8 = -100i8;\n"
|
||||
" let b: i8 = 7i8;\n"
|
||||
" if (a % b == -2i8) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "i8_div_INT8_MIN_by_two",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i8 = -128i8;\n"
|
||||
" let b: i8 = 2i8;\n"
|
||||
" if (a / b == -64i8) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- u64 unsigned: high-bit-set / 2 must not sign-extend ----- */
|
||||
{ "u64_div_high_bit_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u64 = 0x8000000000000001u64;\n"
|
||||
" let b: u64 = 2u64;\n"
|
||||
" if (a / b == 0x4000000000000000u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "u64_mod_high_bit_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u64 = 0x8000000000000001u64;\n"
|
||||
" let b: u64 = 2u64;\n"
|
||||
" if (a % b == 1u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- u32 unsigned: high-bit-set / 2 -------------------------- */
|
||||
{ "u32_div_high_bit_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u32 = 0x80000001u32;\n"
|
||||
" let b: u32 = 2u32;\n"
|
||||
" if (a / b == 0x40000000u32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "u32_mod_high_bit_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u32 = 0x80000001u32;\n"
|
||||
" let b: u32 = 2u32;\n"
|
||||
" if (a % b == 1u32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- u16 unsigned: high-bit-set / 2 -------------------------- */
|
||||
{ "u16_div_high_bit_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u16 = 0xFFFFu16;\n"
|
||||
" let b: u16 = 2u16;\n"
|
||||
" if (a / b == 0x7FFFu16) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- u8 unsigned: high-bit-set / 2 --------------------------- */
|
||||
{ "u8_div_high_bit_set",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u8 = 0xFFu8;\n"
|
||||
" let b: u8 = 2u8;\n"
|
||||
" if (a / b == 0x7Fu8) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- mixed-type-flag row: i64 cast to u64 forces unsigned arm.
|
||||
* `unsignd` flag fires if either operand is unsigned — pin it. */
|
||||
{ "mixed_unsigned_rhs_picks_unsigned",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = 100i64;\n"
|
||||
" let b: u64 = 7u64;\n"
|
||||
" if ((a: u64) / b == 14u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
/* ---- COMPOUND ASSIGN rows ------------------------------------- *
|
||||
*
|
||||
* The siblings of the binary `/` and `%` paths above: `x /= v`,
|
||||
* `x %= v`, and `*p /= v`. Pre-#16's-B2 these emitted the worst
|
||||
* shape catalog:
|
||||
* - cstage cgen.c:3765 IDENT-local: silent no-op (BX held
|
||||
* loaded slot value, the trailing store wrote it back
|
||||
* unchanged — no IDIV emit at all).
|
||||
* - wwstage cgenexpr.ww:5381-5403 IDENT-local: identical silent
|
||||
* no-op shape; the load-bearing sentinel — a future regression
|
||||
* here passes byte-id (no asm = no divergence) AND passes the
|
||||
* selfhost corpus (no consumer of signed compound /=). The
|
||||
* semantic test below is the only catch.
|
||||
* - cstage cgen.c:3549 / wwstage cgenexpr.ww:3338 deref-compound
|
||||
* `*p OP= v`: silent rhs-only store, value of *p clobbered with
|
||||
* the divisor instead of computed quotient.
|
||||
*
|
||||
* Top-level-let compound coverage (cstage cgen.c:3735 / wwstage
|
||||
* cgenexpr.ww:5147) is deferred per task #18 — the inline-driver
|
||||
* 978 fixture can't currently exercise it because of a pre-existing
|
||||
* linker `undefined reference to '<file>.gs'` for single-file
|
||||
* top-level lets that flow through `LEAQ name(SB)`. The cstage:3735
|
||||
* and wwstage:5147 sites are code-review-verified for rule-10
|
||||
* symmetry; runtime coverage follows once #18 lands. */
|
||||
{ "compound_ident_local_div_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = -100i64;\n"
|
||||
" a /= 7i64;\n"
|
||||
" if (a == -14i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "compound_ident_local_mod_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = -100i64;\n"
|
||||
" a %= 7i64;\n"
|
||||
" if (a == -2i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "compound_ident_local_div_neg_divisor",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: i64 = 100i64;\n"
|
||||
" a /= -7i64;\n"
|
||||
" if (a == -14i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "compound_ident_local_div_unsigned",
|
||||
"fn main() i32 = {\n"
|
||||
" let a: u64 = 0x8000000000000001u64;\n"
|
||||
" a /= 2u64;\n"
|
||||
" if (a == 0x4000000000000000u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "compound_deref_div_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [1]i64;\n"
|
||||
" arr[0] = -50i64;\n"
|
||||
" let p: *i64 = &arr[0];\n"
|
||||
" *p /= 3i64;\n"
|
||||
" if (arr[0] == -16i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "compound_deref_mod_neg_dividend",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [1]i64;\n"
|
||||
" arr[0] = -50i64;\n"
|
||||
" let p: *i64 = &arr[0];\n"
|
||||
" *p %= 3i64;\n"
|
||||
" if (arr[0] == -2i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
{ "compound_deref_div_unsigned",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [1]u64;\n"
|
||||
" arr[0] = 0x8000000000000001u64;\n"
|
||||
" let p: *u64 = &arr[0];\n"
|
||||
" *p /= 2u64;\n"
|
||||
" if (arr[0] == 0x4000000000000000u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwid_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/wwid_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/wwid_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
wwtest_fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
|
||||
driver, outbin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
runwait(rmcmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "intdiv_signed: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"intdiv_signed[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"intdiv_signed: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("intdiv_signed: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 979_hex_run — execute the lib/encoding/hex @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 983_base32_run / 977_time_run:
|
||||
* hextest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/encoding/hex/hextest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "hex_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("hex_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 980_memio_run — execute the lib/memio @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Companion to 998_bufio_run; first entry in the 980-989 stdlib-run
|
||||
* sub-band. memiotest.ww carries its own `export fn main()` that
|
||||
* drives the @test fns and signals which case failed via the exit
|
||||
* code, so this file is just a thin wrapper — no @test scanning,
|
||||
* no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/memio/memiotest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "memio_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("memio_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 981_temp_run — execute the lib/temp @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 998_bufio_run. temptest.ww carries
|
||||
* its own `export fn main()` that drives the @test fns and signals
|
||||
* which case failed via the exit code, so this file is just a thin
|
||||
* wrapper — no @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/temp/temptest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "temp_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("temp_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 982_getopt_run — execute the lib/getopt @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 998_bufio_run.
|
||||
* getopttest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code, so
|
||||
* this file is just a thin wrapper — no @test scanning, no
|
||||
* synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/getopt/getopttest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
(void)cwd;
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "getopt_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("getopt_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 983_base32_run — execute the lib/encoding/base32 @test fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. base32_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/encoding/base32/base32_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "base32_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("base32_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 984_base64_run — execute the lib/encoding/base64 @test fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. base64_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/encoding/base64/base64_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "base64_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("base64_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 985_adler32_run — execute the lib/hash/adler32 @test fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. adler32_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/hash/adler32/adler32_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "adler32_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("adler32_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 986_crc16_run — execute the lib/hash/crc16 @test fixture under
|
||||
* the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. crc16_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/hash/crc16/crc16_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "crc16_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("crc16_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 987_crc32_run — execute the lib/hash/crc32 @test fixture under
|
||||
* the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. crc32_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/hash/crc32/crc32_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "crc32_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("crc32_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 988_crc64_run — execute the lib/hash/crc64 @test fixture under
|
||||
* the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. crc64_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/hash/crc64/crc64_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "crc64_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("crc64_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* 989_allocalias_run (#26) — `alloc(T{...})` where T is an alias of a struct
|
||||
* must size + field-fill from the resolved (alias-chased) struct layout.
|
||||
*
|
||||
* THE BUG (wwstage only): cgalloc looked up the struct layout by the literal
|
||||
* head NAME via structlookup, which scans c.structs by name and never matches
|
||||
* an alias. For `type pt = point; alloc(pt{x=1,y=2})` it found nil, kept the
|
||||
* default size 8 (under-alloc for the 16B struct) and skipped the whole
|
||||
* field-store loop — the *pt pointed at uninitialised heap (rc=0, p.x wrong).
|
||||
* cstage sizes from type_default(v->type) and walks u->fields. THE FIX: chase
|
||||
* the alias chain with structlookupchain on the literal's type ref.
|
||||
*
|
||||
* row | shape | exit (cs==ww)
|
||||
* -------------+------------------------------------+--------------
|
||||
* alias | type pt=point; alloc(pt{x=1,y=2}) | 0 (was ww 1)
|
||||
* direct ctrl | alloc(point{x=1,y=2}) | 0
|
||||
* alias returns p.x*0+ok: pre-fix ww exited 1 (p.x != 1, zero-filled heap).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "alias",
|
||||
"package main;\n"
|
||||
"import rt;\n"
|
||||
"type point = struct { x: i64, y: i64 };\n"
|
||||
"type pt = point;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: *pt = alloc(pt{x=1,y=2})!;\n"
|
||||
" if (p.x != 1) { return 1; };\n"
|
||||
" if (p.y != 2) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "direct",
|
||||
"package main;\n"
|
||||
"import rt;\n"
|
||||
"type point = struct { x: i64, y: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: *point = alloc(point{x=1,y=2})!;\n"
|
||||
" if (p.x != 1) { return 1; };\n"
|
||||
" if (p.y != 2) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/alal_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/alal_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/alal_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int gc = run_build(cdrv, &rows[i], i);
|
||||
if (gc < 0) {
|
||||
fprintf(stderr, "allocalias[cstage][%s]: build/run failed "
|
||||
"(got %d)\n", rows[i].label, gc);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if (gc != rows[i].want_exit) {
|
||||
fprintf(stderr, "allocalias[cstage][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, gc, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "allocalias: skip wwstage (no %s)\n", wdrv);
|
||||
continue;
|
||||
}
|
||||
int gw = run_build(wdrv, &rows[i], i);
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "allocalias[%s]: cs=%d != ww=%d "
|
||||
"(alloc(alias{}) under-alloc / zero fields — #26)\n",
|
||||
rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
if (gw != rows[i].want_exit) {
|
||||
fprintf(stderr, "allocalias[wwstage][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, gw, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "allocalias_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("allocalias_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* 989_ampfncollide_run (#4, c2) — `&fn` synthesis must prefer the current
|
||||
* module's fn when a same-leaf fn is declared in a later module.
|
||||
*
|
||||
* THE BUG (review item #4, 2026-06-11): unoptype's TK_AMP fn-ident synth
|
||||
* (check.ww ~2677) and assignableaddrfn (check.ww ~4182) looked the fn up
|
||||
* with bare scopelookup — no (name,module) preference. scopedefineinmodule
|
||||
* PREPENDS, so a LATER module's same-leaf fn heads the bucket chain and the
|
||||
* bare lookup binds ITS signature. Two faces:
|
||||
* - LOUD: `let p: *hfn = &handler` in beta (hfn = fn(i32)i32, beta.handler
|
||||
* is i32->i32) is REJECTED by wwstage ("let: not assignable") because it
|
||||
* binds gamma.handler(str)str, while cstage (scope_lookup_prefer with
|
||||
* cur_mod, check.c:410/1305) binds beta.handler and accepts. Valid code
|
||||
* wrongly rejected.
|
||||
* - SILENT (cat-A): `let p: *gfn = &handler` where gfn = fn(str)str (the
|
||||
* FOREIGN sig) is REJECTED by cstage (beta.handler is i32->i32, not
|
||||
* assignable) but ACCEPTED by wwstage (it binds gamma.handler(str)str,
|
||||
* which matches gfn) — a type-confused fn pointer (LEAQ beta.handler
|
||||
* into a *fn(str)str slot) in a green build.
|
||||
* THE FIX: both sites use scopelookupprefer(c.cur, c.curmod, ...).
|
||||
*
|
||||
* row | shape | result (cs==ww)
|
||||
* -------+-----------------------------------------+-----------------
|
||||
* loud | *hfn = &handler (own-sig slot) | run 42 (was ww reject)
|
||||
* silent | *gfn = &handler (foreign-sig slot) | build FAIL (was ww ok)
|
||||
*
|
||||
* Single-file multi-package source is the sanctioned shape (cmd/ww/main.c).
|
||||
* want = -1 means "build must fail on both stages".
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "loud",
|
||||
"package beta;\n"
|
||||
"export type hfn = fn(x: i32) i32;\n"
|
||||
"export fn handler(x: i32) i32 = { return x + 41; };\n"
|
||||
"export fn usebeta() i32 = {\n"
|
||||
" let p: *hfn = &handler;\n"
|
||||
" return (*p)(1);\n"
|
||||
"};\n"
|
||||
"package gamma;\n"
|
||||
"export fn handler(s: str) str = { return s; };\n"
|
||||
"package main;\n"
|
||||
"import beta;\n"
|
||||
"import gamma;\n"
|
||||
"fn main() int = { return beta.usebeta(): int; };\n",
|
||||
42 },
|
||||
|
||||
{ "silent",
|
||||
"package beta;\n"
|
||||
"export type gfn = fn(s: str) str;\n"
|
||||
"export fn handler(x: i32) i32 = { return x + 41; };\n"
|
||||
"export fn usebeta() i32 = {\n"
|
||||
" let p: *gfn = &handler;\n"
|
||||
" if (p == p) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"package gamma;\n"
|
||||
"export fn handler(s: str) str = { return s; };\n"
|
||||
"package main;\n"
|
||||
"import beta;\n"
|
||||
"import gamma;\n"
|
||||
"fn main() int = { return beta.usebeta(): int; };\n",
|
||||
-1 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/afc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/afc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/afc_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1; /* -1 == build failed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int gc = run_build(cdrv, &rows[i], i);
|
||||
if (gc != rows[i].want) {
|
||||
fprintf(stderr, "ampfncollide[cstage][%s]: got=%d want=%d\n",
|
||||
rows[i].label, gc, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "ampfncollide: skip wwstage (no %s)\n", wdrv);
|
||||
continue;
|
||||
}
|
||||
int gw = run_build(wdrv, &rows[i], i);
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "ampfncollide[%s]: cs=%d != ww=%d "
|
||||
"(&fn cross-module same-leaf misbind — #4 c2)\n",
|
||||
rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
if (gw != rows[i].want) {
|
||||
fprintf(stderr, "ampfncollide[wwstage][%s]: got=%d want=%d\n",
|
||||
rows[i].label, gw, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "ampfncollide_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("ampfncollide_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* 989_arrlit_tail_zero_run (#13) — an UNDER-LENGTH array literal zero-fills
|
||||
* the unspecified tail (Hare: unspecified array elements are zeroed; the
|
||||
* #16-task zero-value ruling), not the last value.
|
||||
*
|
||||
* THE BUG (wwstage only, gate-blind cs!=ww): emitarraylitbytes' int-element
|
||||
* tail loop defaulted each unfilled slot to `last` (the previously emitted
|
||||
* value), so `let g: [4]u64 = [1, 2]` emitted [1,2,2,2] — the DATAW tail
|
||||
* repeated \x02. cstage already zeroes the tail (cg_array_data). THE FIX:
|
||||
* default the tail to 0; replay `last` only inside a `...` repeat.
|
||||
*
|
||||
* The float / struct / nested-array element paths already zero-filled their
|
||||
* tails; only the int path defaulted to `last`. The str/slice element paths
|
||||
* ride a different helper (emitstrarraydata / emitslicedata) and were also
|
||||
* already correct — the str_tail row documents the family is uniformly
|
||||
* zeroed, it is coverage, not a teeth on this fix.
|
||||
*
|
||||
* row | shape | exit (cs==ww)
|
||||
* --------------+--------------------------------+--------------
|
||||
* scalar_tail | [4]u64=[1,2]; sum | 3 (1+2+0+0)
|
||||
* repeat_ctl | [4]u64=[7,9...]; sum (control) | 34 (7+9+9+9)
|
||||
* str_tail | [4]str=["ab","cde"]; tail.len | 5 (2+3, tail len 0)
|
||||
* Pre-fix (int tail = last): scalar_tail ww=7 (1+2+2+2) != cs=3.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "scalar_tail",
|
||||
"package main;\n"
|
||||
"let g: [4]u64 = [1, 2];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return (g[0] + g[1] + g[2] + g[3]): i32;\n"
|
||||
"};\n",
|
||||
3 },
|
||||
|
||||
{ "repeat_ctl",
|
||||
"package main;\n"
|
||||
"let g: [4]u64 = [7, 9...];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return (g[0] + g[1] + g[2] + g[3]): i32;\n"
|
||||
"};\n",
|
||||
34 },
|
||||
|
||||
{ "str_tail",
|
||||
"package main;\n"
|
||||
"let g: [4]str = [\"ab\", \"cde\"];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (g[2].len != 0) { return 21; };\n"
|
||||
" if (g[3].len != 0) { return 22; };\n"
|
||||
" return (g[0].len + g[1].len): i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/atz_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/atz_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/atz_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int gc = run_build(cdrv, &rows[i], i);
|
||||
if (gc < 0) {
|
||||
fprintf(stderr, "arrlit_tail_zero[cstage][%s]: build/run "
|
||||
"failed (got %d)\n", rows[i].label, gc);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if (gc != rows[i].want_exit) {
|
||||
fprintf(stderr, "arrlit_tail_zero[cstage][%s]: exit=%d "
|
||||
"want=%d\n", rows[i].label, gc, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "arrlit_tail_zero: skip wwstage (no %s)\n", wdrv);
|
||||
continue;
|
||||
}
|
||||
int gw = run_build(wdrv, &rows[i], i);
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "arrlit_tail_zero[%s]: cs=%d != ww=%d "
|
||||
"(tail fill divergence — #13)\n", rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
if (gw != rows[i].want_exit) {
|
||||
fprintf(stderr, "arrlit_tail_zero[wwstage][%s]: exit=%d "
|
||||
"want=%d\n", rows[i].label, gw, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "arrlit_tail_zero_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arrlit_tail_zero_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
/*
|
||||
* 989_catA_f2_reject — #38/F2 checker-reject wave: align the wwstage
|
||||
* checker UP to cstage's harec-faithful rejects (cat-A silent-accept
|
||||
* family from the 2026-06-11 stability review). Each row is a program
|
||||
* cstage loud-rejects but pre-fix wwstage silently accepted (then built
|
||||
* a wrong binary). The build-based reject-matrix idiom (sibling
|
||||
* 989_callarg_typecheck.c): a REJECT row must FAIL to build on every
|
||||
* driver; an ACCEPT control must build + run to its expected exit.
|
||||
*
|
||||
* Members (review item → check.ww seam → cstage twin):
|
||||
* #2 arrayelen non-const dim tinfofornode N_TARRAY check.c:715
|
||||
* #6 binoptype/unoptype kinds binoptype/unoptype check.c:1186-1238
|
||||
* #7 size/align unknown type size intercept check.c:93/1538
|
||||
* #10 tuple-literal arity checktuplearrfits type.c:416/check.c:2386
|
||||
* #39 non-tuple massign rhs N_MASSIGN resolvewalk check.c:2562
|
||||
* #8 checktryprop spread-blind trycountvariants check.c:2160 (F3)
|
||||
* #6' match-yield unification exprtype N_MATCH check.c:2080
|
||||
*
|
||||
* Rule-10: every row runs on cstage `ww` and, when present, wwstage
|
||||
* `ww_ww`; both must agree. wwstage rows are gated on out/bin/ww_ww.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
||||
int want_exit;
|
||||
const char *diag; /* cstage/shared reject diagnostic (NULL for build rows) */
|
||||
const char *wdiag; /* wwstage override when stages diverge (NULL = use diag) */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* #2 — a runtime-valued array dimension is non-const → REJECT. Pre-fix
|
||||
* wwstage folded it to a 0-sized slot aliasing the dim variable. */
|
||||
{ "arrlen_runtime",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let n: i32 = 3;\n"
|
||||
" let xs: [n]u8;\n"
|
||||
" xs[0] = 77u8;\n"
|
||||
" return n;\n"
|
||||
"};\n",
|
||||
0, 0, "array length must be an integer literal" },
|
||||
|
||||
/* #2 control — a literal dimension stays valid → run 0. */
|
||||
{ "arrlen_const_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: [3]u8;\n"
|
||||
" xs[0] = 1u8;\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1, 0 },
|
||||
|
||||
/* #6 — str + str is arithmetic on a non-numeric → REJECT. Pre-fix
|
||||
* wwstage emitted an integer ADD of the two header pointers. */
|
||||
{ "str_plus_str",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: str = \"x\";\n"
|
||||
" let b: str = \"y\";\n"
|
||||
" let c: str = a + b;\n"
|
||||
" return c.len: i32;\n"
|
||||
"};\n",
|
||||
0, 0, "arithmetic on non-numeric type" },
|
||||
|
||||
/* #6 — bitwise & on str is non-integer → REJECT. */
|
||||
{ "str_bitand",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: str = \"x\";\n"
|
||||
" let b: str = \"y\";\n"
|
||||
" let n: i32 = (a & b): i32;\n"
|
||||
" return n;\n"
|
||||
"};\n",
|
||||
0, 0, "bitwise on non-integer type" },
|
||||
|
||||
/* #6 — ordered comparison on str is non-numeric → REJECT (EQ/NEQ stay
|
||||
* valid; only the ordered <,<=,>,>= are gated, matching cstage). */
|
||||
{ "str_lt",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: str = \"x\";\n"
|
||||
" let b: str = \"y\";\n"
|
||||
" if (a < b) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0, "ordered comparison on non-numeric" },
|
||||
|
||||
/* #6 — unary ~ on a non-integer (str) → REJECT. */
|
||||
{ "tilde_str",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: str = \"x\";\n"
|
||||
" let n: i32 = (~a): i32;\n"
|
||||
" return n;\n"
|
||||
"};\n",
|
||||
0, 0, "~ on non-integer" },
|
||||
|
||||
/* #6 control — integer arithmetic, comparison, bitwise all valid. */
|
||||
{ "int_ops_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: i32 = 3;\n"
|
||||
" let b: i32 = 4;\n"
|
||||
" let c: i32 = (a + b) & 7;\n"
|
||||
" if (a < b) { c = c + 1; };\n"
|
||||
" return c;\n"
|
||||
"};\n",
|
||||
1, 8 },
|
||||
|
||||
/* #7 — size() of a value (not a type) is an unknown type → REJECT.
|
||||
* Pre-fix wwstage folded the unresolved name to 0 silently. */
|
||||
{ "size_localvar",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: i32 = 5;\n"
|
||||
" return size(v): i32;\n"
|
||||
"};\n",
|
||||
0, 0, "unknown type 'v'" },
|
||||
|
||||
/* #7 control — size() of a real type folds → run 4. */
|
||||
{ "size_type_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = { return size(i32): i32; };\n",
|
||||
1, 4 },
|
||||
|
||||
/* #10 — an over-long tuple literal → REJECT. */
|
||||
{ "tuple_overlong",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i32, i32) = (1, 2, 3);\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0, "not assignable to declared", "tuple literal has 3 elements" },
|
||||
|
||||
/* #10 — a short tuple literal → REJECT (the live miscompile: cgen
|
||||
* filled the missing slot with a stale register). */
|
||||
{ "tuple_short",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i32, i32, i32) = (1, 2);\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0, "not assignable to declared", "tuple literal has 2 elements" },
|
||||
|
||||
/* #10 control — a matching-arity tuple literal → run 3. */
|
||||
{ "tuple_match_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i32, i32) = (1, 2);\n"
|
||||
" return t.0 + t.1;\n"
|
||||
"};\n",
|
||||
1, 3 },
|
||||
|
||||
/* #39 — multi-assign rhs is a tuple ALIAS, not a bare tuple → REJECT
|
||||
* (cstage does not chase the NAMED wrapper here; nor does ww). */
|
||||
{ "massign_alias",
|
||||
"package main;\n"
|
||||
"type pair = (i64, str);\n"
|
||||
"fn f() pair = { return (7i64, \"hey\"); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: i64 = 0i64;\n"
|
||||
" let s: str = \"\";\n"
|
||||
" a, s = f();\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0, "multi-assign rhs is not a tuple" },
|
||||
|
||||
/* #39 — multi-assign rhs is a plain scalar, not a tuple → REJECT. */
|
||||
{ "massign_scalar",
|
||||
"package main;\n"
|
||||
"fn g() i64 = { return 7i64; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: i64 = 0i64;\n"
|
||||
" let s: i64 = 0i64;\n"
|
||||
" a, s = g();\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0, "multi-assign rhs is not a tuple" },
|
||||
|
||||
/* #39 control — a bare-tuple-returning fn drives a valid massign. */
|
||||
{ "massign_tuple_ok",
|
||||
"package main;\n"
|
||||
"fn f() (i64, i64) = { return (3i64, 4i64); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: i64 = 0i64;\n"
|
||||
" let b: i64 = 0i64;\n"
|
||||
" a, b = f();\n"
|
||||
" return (a + b): i32;\n"
|
||||
"};\n",
|
||||
1, 7 },
|
||||
|
||||
/* #8 — a `...inner` spread makes the union multi-success; the F8 gate
|
||||
* must fire over the FLATTENED members → REJECT. Pre-fix wwstage counted
|
||||
* the spread as one success (nsucc=1) and silently accepted. */
|
||||
{ "try_spread_multisuccess",
|
||||
"package main;\n"
|
||||
"type inner = (i32 | bool);\n"
|
||||
"type e = !void;\n"
|
||||
"fn g() (...inner | e) = { return true; };\n"
|
||||
"export fn main() i32 = { let x = g()!; return 0; };\n",
|
||||
0, 0, "multi-success union unwired" },
|
||||
|
||||
/* #8 control — a single-success tagged union try unwraps fine → run 5. */
|
||||
{ "try_single_ok",
|
||||
"package main;\n"
|
||||
"type e = !void;\n"
|
||||
"fn g() (i32 | e) = { return 5; };\n"
|
||||
"export fn main() i32 = { let x = g()!; return x; };\n",
|
||||
1, 5 },
|
||||
|
||||
/* match-yield — arms yielding incompatible types (int vs str) → REJECT.
|
||||
* Pre-fix wwstage took the first arm's type and ran the str arm's pointer
|
||||
* through an int-stamped slot. */
|
||||
{ "match_mixed_yield",
|
||||
"package main;\n"
|
||||
"fn pick(b: bool) (i32 | str) = { if (b) { return 7; }; return \"x\"; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: (i32 | str) = pick(true);\n"
|
||||
" let r = match (v) { case i32 => yield 7; case str => yield \"oops\"; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0, "match arm yields" },
|
||||
|
||||
/* match-yield control — same-family arm yields (untyped_int + i32) must
|
||||
* NOT over-reject (the untyped->concrete promotion cstage admits) → run 9. */
|
||||
{ "match_same_yield_ok",
|
||||
"package main;\n"
|
||||
"fn pick(b: bool) (i32 | str) = { if (b) { return 4; }; return \"x\"; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: (i32 | str) = pick(true);\n"
|
||||
" let r: i32 = match (v) { case i32 => yield 9; case let s: str => yield 5; };\n"
|
||||
" return r;\n"
|
||||
"};\n",
|
||||
1, 9 },
|
||||
|
||||
/* break/continue outside any enclosing for/for-range → REJECT. Pre-fix
|
||||
* wwstage had no loop-nesting guard (check.ww resolvewalk) and silently
|
||||
* accepted, emitting a JMP to an undefined loop label; cstage rejects at
|
||||
* cmd/wcc/check.c:2611-2615 (c->loops == 0). Both diagnostics match. */
|
||||
{ "break_outside_loop",
|
||||
"package main;\n"
|
||||
"export fn main() void = {\n"
|
||||
" break;\n"
|
||||
"};\n",
|
||||
0, 0, "break outside loop" },
|
||||
|
||||
{ "continue_outside_loop",
|
||||
"package main;\n"
|
||||
"export fn main() void = {\n"
|
||||
" continue;\n"
|
||||
"};\n",
|
||||
0, 0, "continue outside loop" },
|
||||
|
||||
/* break in a for's `else` block with NO enclosing loop → REJECT. The
|
||||
* else runs at normal cond-false exit (skipped by break) and targets an
|
||||
* ENCLOSING loop, so it is OUTSIDE this loop's break count — cstage
|
||||
* cmd/wcc/check.c:2546-2549 keeps els out of the count; wwstage mirrors. */
|
||||
{ "break_in_else_outside_loop",
|
||||
"package main;\n"
|
||||
"export fn main() void = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < 1) { i = i + 1; } else { break; };\n"
|
||||
"};\n",
|
||||
0, 0, "break outside loop" },
|
||||
|
||||
/* control — break/continue INSIDE a loop are valid → run 5. */
|
||||
{ "break_continue_inloop_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < 10) {\n"
|
||||
" i = i + 1;\n"
|
||||
" if (i == 3) { continue; };\n"
|
||||
" if (i == 5) { break; };\n"
|
||||
" };\n"
|
||||
" return i;\n"
|
||||
"};\n",
|
||||
1, 5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/f2r_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/f2r_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/f2r_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* build_should_fail — the build must FAIL *and* emit `diag`. A crash
|
||||
* (segfault) wraps as a nonzero "w6c failed" with no diagnostic, so the
|
||||
* substring check distinguishes it from a clean reject (#20). */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, const char *diag,
|
||||
int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], errf[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/f2n_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/f2n_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/f2n_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/err", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
|
||||
runwait(rmcmd);
|
||||
/* build must NOT succeed AND must emit its diagnostic. */
|
||||
return (rc != 0 && hasmsg) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "catA_f2_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "catA_f2_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_fail(drivers[d].drv, rows[i].src,
|
||||
drivers[d].gated && rows[i].wdiag
|
||||
? rows[i].wdiag : rows[i].diag,
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr, "catA_f2_reject[%s][%s]: "
|
||||
"built ok, expected a loud reject\n",
|
||||
drivers[d].name, rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "catA_f2_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("catA_f2_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 989_defercap_run (#40, F13 c1) — the defer stack must fail loud at its cap
|
||||
* in BOTH stages, not silently drop the overflowing deferred call.
|
||||
*
|
||||
* THE BUG (cat-A, both stages diverged AND both wrong at their cap): wwstage
|
||||
* cgstmt N_DEFER pushed only while defertop < DEFER_MAX where DEFER_MAX was 16
|
||||
* (cgen.ww), so a function with 17 defers silently dropped the 17th — the
|
||||
* counter reached 16, exit 1, while cstage (DEFER_MAX 32) emitted all 17.
|
||||
* cstage in turn silently dropped its own 33rd defer. The runtime-correct
|
||||
* target (CAP-SEMANTICS rule: cstage silent at its own cap => loud both):
|
||||
* the cap is the shared 32, and a function exceeding it is a hard compile
|
||||
* error in BOTH stages (cgenstmt.ww N_DEFER + cgen.c N_DEFER fatal).
|
||||
*
|
||||
* row | ndefers | shape | result (cs==ww)
|
||||
* ------------+---------+-------------------------------+----------------
|
||||
* seventeen | 17 | original repro (>old ww 16) | exit 0
|
||||
* at_cap_32 | 32 | exactly the shared cap | exit 0
|
||||
* over_cap_33 | 33 | one past the cap | build FAILS loud
|
||||
*
|
||||
* seventeen was RED pre-c1 on wwstage (counter 16, exit 1 vs cstage exit 0).
|
||||
* over_cap_33 was RED pre-c1 on BOTH stages (silent rc=0 accept dropping the
|
||||
* overflowing defer). at_cap_32 pins the boundary stays accepted byte-id.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; int ndefers; int want_build_fail; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "seventeen", 17, 0, 0 },
|
||||
{ "at_cap_32", 32, 0, 0 },
|
||||
{ "over_cap_33", 33, 1, 0 },
|
||||
};
|
||||
|
||||
/* Emit `package main` with a doit() carrying ndefers `defer bump();` and a
|
||||
* main() that checks the global counter equals ndefers. */
|
||||
static int
|
||||
write_src(const char *path, int ndefers)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs("package main;\n"
|
||||
"let cnt: i32 = 0;\n"
|
||||
"fn bump() void = { cnt = cnt + 1; };\n"
|
||||
"fn doit() void = {\n", f);
|
||||
for (int i = 0; i < ndefers; i++)
|
||||
fputs("\tdefer bump();\n", f);
|
||||
fprintf(f,
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tdoit();\n"
|
||||
"\tif (cnt == %d) { return 0; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n", ndefers);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns the build rc in *brc; the run exit code (or -1) as the value. */
|
||||
static int
|
||||
build_run(const char *driver, const struct row *r, int i, int *brc)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dcap_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/dcap_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/dcap_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
if (write_src(src, r->ndefers) != 0) { *brc = -1; runwait(rmcmd); return -1; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
*brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (*brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int cbrc, gc = build_run(cdrv, &rows[i], i, &cbrc);
|
||||
|
||||
if (rows[i].want_build_fail) {
|
||||
if (cbrc == 0) {
|
||||
fprintf(stderr, "defercap[cstage][%s]: built rc=0, "
|
||||
"expected loud build failure (#40)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (cbrc != 0 || gc != rows[i].want_exit) {
|
||||
fprintf(stderr, "defercap[cstage][%s]: brc=%d exit=%d "
|
||||
"want_exit=%d\n", rows[i].label, cbrc, gc,
|
||||
rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "defercap: skip wwstage (no %s)\n", wdrv);
|
||||
continue;
|
||||
}
|
||||
int wbrc, gw = build_run(wdrv, &rows[i], i, &wbrc);
|
||||
|
||||
if (rows[i].want_build_fail) {
|
||||
if (wbrc == 0) {
|
||||
fprintf(stderr, "defercap[wwstage][%s]: built rc=0, "
|
||||
"expected loud build failure (#40)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
/* both-fail is the rule-10 agreement here */
|
||||
if ((cbrc == 0) != (wbrc == 0)) {
|
||||
fprintf(stderr, "defercap[%s]: build-fail divergence "
|
||||
"cs_rc=%d ww_rc=%d (#40)\n", rows[i].label, cbrc, wbrc);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (wbrc != 0 || gw != rows[i].want_exit) {
|
||||
fprintf(stderr, "defercap[wwstage][%s]: brc=%d exit=%d "
|
||||
"want_exit=%d\n", rows[i].label, wbrc, gw,
|
||||
rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "defercap[%s]: cs=%d != ww=%d "
|
||||
"(defer-cap divergence — #40)\n", rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "defercap_run: %d/%d checks failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("defercap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
/*
|
||||
* 989_fmt_scanoverflow_run — drain item F-A: lib/fmt scandigits dropped
|
||||
* the last-digit overflow case.
|
||||
*
|
||||
* scandigits (lib/fmt/fmt.ww) accumulates a format-directive digit run
|
||||
* into an i32 with a Horner loop. The guard was a one-sided
|
||||
* `if (v > 214748364)`, which never fires when v == 214748364 and the
|
||||
* next digit is '8' or '9': v * 10 + 8/9 = 2147483648/2147483649
|
||||
* overflows i32 and wraps NEGATIVE. A negative explicit arg index then
|
||||
* slips past the signed `idx >= args.len` guard in fprintf (fmt.ww:703)
|
||||
* and `args[idx]` reads far out of bounds -> SIGSEGV.
|
||||
*
|
||||
* The fix completes the canonical Horner pre-multiply guard to the
|
||||
* two-part form `v > MAX/10 || (v == MAX/10 && digit > MAX%10)`
|
||||
* (MAX=2147483647, MAX/10=214748364, MAX%10=7), so the last digit is
|
||||
* rejected and scandigits aborts cleanly via fmtabort (os.exit(255)).
|
||||
*
|
||||
* row | directive | arg | want_exit
|
||||
* -----------------+--------------------+---------+----------
|
||||
* idx_2147483648 | "{2147483648}" | 42i64 | 255 (clean abort, was SIGSEGV)
|
||||
* idx_2147483649 | "{2147483649}" | 42i64 | 255 (clean abort, was SIGSEGV)
|
||||
* idx_9999999999 | "{9999999999}" | 42i64 | 255 (clear overflow, aborts pre+post)
|
||||
* width_2147483648 | "{:2147483648}" | 42i64 | 255 (width call site, last-digit abort)
|
||||
* prec_2147483647 | "{:.2147483647}" | "ok" | 7 (i32 MAX still PARSES, prints "ok")
|
||||
*
|
||||
* scandigits feeds three directive call sites — index (fmt.ww:690),
|
||||
* width (:259) and precision (:255). idx_*, width_* and prec_* exercise
|
||||
* all three; the last-digit guard must fire identically at each.
|
||||
*
|
||||
* The first two rows are the regression proper: on the pre-fix guard the
|
||||
* program is killed by signal 11 (runwait -> -1), not 255. The valid
|
||||
* boundary row proves the fix does not over-reject i32 MAX itself.
|
||||
*
|
||||
* Rule 10: every row runs on cstage `ww` and (when present) wwstage
|
||||
* `ww_ww`; both drivers compile the same fixed lib/fmt so both must agree.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1; /* killed by a signal (e.g. SIGSEGV) */
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "idx_2147483648",
|
||||
"package main;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [64]u8;\n"
|
||||
" let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{2147483648}\", 42i64);\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { return 0; };\n"
|
||||
" case let e: io.error => { return 1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
255 },
|
||||
{ "idx_2147483649",
|
||||
"package main;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [64]u8;\n"
|
||||
" let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{2147483649}\", 42i64);\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { return 0; };\n"
|
||||
" case let e: io.error => { return 1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
255 },
|
||||
{ "idx_9999999999",
|
||||
"package main;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [64]u8;\n"
|
||||
" let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{9999999999}\", 42i64);\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { return 0; };\n"
|
||||
" case let e: io.error => { return 1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
255 },
|
||||
{ "width_2147483648",
|
||||
"package main;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [64]u8;\n"
|
||||
" let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{:2147483648}\", 42i64);\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { return 0; };\n"
|
||||
" case let e: io.error => { return 1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
255 },
|
||||
{ "prec_2147483647",
|
||||
"package main;\n"
|
||||
"import fmt;\n"
|
||||
"import io;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let buf: [64]u8;\n"
|
||||
" let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{:.2147483647}\", \"ok\");\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => { if (s.len == 2) { return 7; }; return 8; };\n"
|
||||
" case let e: io.error => { return 1; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
7 },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ww_ww writes intermediates next to the source (filed task #15); clear
|
||||
* each row's <base>.{combined.ww,s,o} + bare exe. Mirror of 780's. */
|
||||
static void
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[640];
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
||||
rmdir(tmpdir);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
|
||||
const char *src)
|
||||
{
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null",
|
||||
tmpdir, driver, cwd, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[512], base[64], outbin[768];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/fso_%d_d_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main989so");
|
||||
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
||||
mkdir(tmpdir, 0755);
|
||||
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -2; }
|
||||
int rc;
|
||||
int br = build_via_driver(driver, tmpdir, cwd, src);
|
||||
if (br == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
rc = runwait(outbin);
|
||||
} else {
|
||||
rc = -3; /* build failure: distinct from a run signal */
|
||||
}
|
||||
cleanup_tmp(tmpdir, base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int got = run_row(cdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"fmt_scanoverflow[cs][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (wwpresent) {
|
||||
total++;
|
||||
got = run_row(wdrv, cwd, &rows[i], seq++);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"fmt_scanoverflow[ww][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "fmt_scanoverflow: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "fmt_scanoverflow: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("fmt_scanoverflow: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* 989_intoverflow_reject — F14 #53: wwstage parseint dropped the u64
|
||||
* overflow guard the C twin carries (cmd/wcc/lex.c:156
|
||||
* `if (v > (u64)~0ULL / (u64)base)`), so any integer literal that
|
||||
* overflows u64 was silently accepted mod 2^64 by wwstage while cstage
|
||||
* loudly rejected with "bad integer literal". The fix ports the
|
||||
* pre-multiply guard into lib/ww/lex/lex.ww parseint, aligning wwstage
|
||||
* UP to cstage.
|
||||
*
|
||||
* Each REJECT row is a literal that exceeds u64 and must FAIL to build
|
||||
* on BOTH driver twins; each control fits in u64 and must build+run.
|
||||
* Edge rows: U64_MAX decimal and hex (the largest accepted), a 65-bit
|
||||
* hex (smallest-bit overflow), a 21-digit decimal, and a 2^128-1
|
||||
* decimal. The exact-2^64 boundary family is NOT a row here: cstage's
|
||||
* own cheap-check leaks it (report-item #54, a separate both-stages
|
||||
* item), so both stages accept it identically — outside #53's scope.
|
||||
*
|
||||
* Rule-10: every row runs on cstage `ww` and wwstage `ww_ww`; both must
|
||||
* agree. wwstage rows are gated on out/bin/ww_ww.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
||||
int want_exit;
|
||||
const char *diag; /* expected reject diagnostic (NULL for build rows) */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* REJECT — 21-digit decimal, well over u64. */
|
||||
{ "dec_overflow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: u64 = 99999999999999999999u64;\n"
|
||||
" return x: i32;\n"
|
||||
"};\n",
|
||||
0, 0, "bad integer literal" },
|
||||
|
||||
/* REJECT — 65-bit hex (smallest overflow above U64_MAX). */
|
||||
{ "hex_overflow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: u64 = 0x1ffffffffffffffffu64;\n"
|
||||
" return x: i32;\n"
|
||||
"};\n",
|
||||
0, 0, "bad integer literal" },
|
||||
|
||||
/* REJECT — 2^128-1 decimal, far over u64. */
|
||||
{ "dec_huge",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: u64 = 340282366920938463463374607431768211455u64;\n"
|
||||
" return x: i32;\n"
|
||||
"};\n",
|
||||
0, 0, "bad integer literal" },
|
||||
|
||||
/* control — U64_MAX decimal is the largest accepted literal. */
|
||||
{ "u64max_dec_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: u64 = 18446744073709551615u64;\n"
|
||||
" if (x == 18446744073709551615u64) { return 7; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1, 7 },
|
||||
|
||||
/* control — U64_MAX hex, same value via the base-16 path. */
|
||||
{ "u64max_hex_ok",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: u64 = 0xffffffffffffffffu64;\n"
|
||||
" if (x == 18446744073709551615u64) { return 9; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1, 9 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/iov_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/iov_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/iov_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* build_should_fail — the build must FAIL *and* emit `diag`. A crash
|
||||
* (segfault) wraps as a nonzero "w6c failed" with no diagnostic, so the
|
||||
* substring check distinguishes it from a clean reject (#20). cstage
|
||||
* quotes the literal ("bad integer literal '99...'"), wwstage does not;
|
||||
* "bad integer literal" is the shared core. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, const char *diag,
|
||||
int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], errf[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/iovn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/iovn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/iovn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/err", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
|
||||
runwait(rmcmd);
|
||||
/* build must NOT succeed AND must emit its diagnostic. */
|
||||
return (rc != 0 && hasmsg) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "intoverflow_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "intoverflow_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_fail(drivers[d].drv, rows[i].src,
|
||||
rows[i].diag, 100 + i) != 0) {
|
||||
fprintf(stderr, "intoverflow_reject[%s][%s]: "
|
||||
"built ok, expected a loud reject\n",
|
||||
drivers[d].name, rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "intoverflow_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("intoverflow_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* 989_loopcap_run (#42, F13 c2) — the loop-label stack must fail loud at its
|
||||
* cap in BOTH stages, not silently corrupt the heap / emit a wrong target.
|
||||
*
|
||||
* THE BUG (cat-A): wwstage cgfor / cgforrange pushed end+continue labels into
|
||||
* loopendbuf/loopcontbuf (sized exactly LOOP_MAX=16) with NO bounds check, so
|
||||
* nesting depth 17 wrote one past the heap allocation — silent compiler-heap
|
||||
* corruption. The cstage twins DID guard (`if (nloops < LOOP_MAX)`) but then
|
||||
* silently emitted the 16th loop's break/continue label for the 17th loop — a
|
||||
* wrong jump target, also silent. Both stages were wrong at the boundary and
|
||||
* diverged. The runtime-correct target (CAP-SEMANTICS rule): a hard compile
|
||||
* error at the shared cap (LOOP_MAX) in BOTH stages — ww adds the bounds
|
||||
* check, cstage turns its silent skip into a fatal.
|
||||
*
|
||||
* row | depth | shape | result (cs==ww)
|
||||
* ------------+-------+-----------------------------+-----------------
|
||||
* at_cap_16 | 16 | exactly LOOP_MAX nested for | exit 0 (byte-id)
|
||||
* over_cap_17 | 17 | one past the cap | build FAILS loud
|
||||
*
|
||||
* over_cap_17 was RED pre-c2 on BOTH stages (ww OOB write, cs wrong JMP
|
||||
* target, divergent and silent under rc=0). at_cap_16 pins the boundary stays
|
||||
* accepted byte-identically.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; int depth; int want_build_fail; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "at_cap_16", 16, 0, 0 },
|
||||
{ "over_cap_17", 17, 1, 0 },
|
||||
};
|
||||
|
||||
/* Emit `depth` nested `for (n < 1) { ... }` over one shared counter that the
|
||||
* innermost body sets to 1, so every level terminates. */
|
||||
static int
|
||||
write_src(const char *path, int depth)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs("package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet n: i32 = 0;\n\t", f);
|
||||
for (int i = 0; i < depth; i++) fputs("for (n < 1) { ", f);
|
||||
fputs("n = 1;", f);
|
||||
for (int i = 0; i < depth; i++) fputs(" };", f);
|
||||
fputs("\n\treturn 0;\n};\n", f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
build_run(const char *driver, const struct row *r, int i, int *brc)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], cmd[1024], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/lcap_%d_d_%d", getpid(), i);
|
||||
snprintf(src, sizeof src, "%s/lcap_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/lcap_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
if (write_src(src, r->depth) != 0) { runwait(rmcmd); *brc = -1; return -1; }
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
*brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (*brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int cbrc, gc = build_run(cdrv, &rows[i], i, &cbrc);
|
||||
|
||||
if (rows[i].want_build_fail) {
|
||||
if (cbrc == 0) {
|
||||
fprintf(stderr, "loopcap[cstage][%s]: built rc=0, "
|
||||
"expected loud build failure (#42)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (cbrc != 0 || gc != rows[i].want_exit) {
|
||||
fprintf(stderr, "loopcap[cstage][%s]: brc=%d exit=%d "
|
||||
"want_exit=%d\n", rows[i].label, cbrc, gc,
|
||||
rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "loopcap: skip wwstage (no %s)\n", wdrv);
|
||||
continue;
|
||||
}
|
||||
int wbrc, gw = build_run(wdrv, &rows[i], i, &wbrc);
|
||||
|
||||
if (rows[i].want_build_fail) {
|
||||
if (wbrc == 0) {
|
||||
fprintf(stderr, "loopcap[wwstage][%s]: built rc=0, "
|
||||
"expected loud build failure (#42)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
if ((cbrc == 0) != (wbrc == 0)) {
|
||||
fprintf(stderr, "loopcap[%s]: build-fail divergence "
|
||||
"cs_rc=%d ww_rc=%d (#42)\n", rows[i].label, cbrc, wbrc);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (wbrc != 0 || gw != rows[i].want_exit) {
|
||||
fprintf(stderr, "loopcap[wwstage][%s]: brc=%d exit=%d "
|
||||
"want_exit=%d\n", rows[i].label, wbrc, gw,
|
||||
rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "loopcap[%s]: cs=%d != ww=%d "
|
||||
"(loop-cap divergence — #42)\n", rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "loopcap_run: %d/%d checks failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("loopcap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* 989_nullableglobal_reject — #45 silent->loud bridge (ww-core task #15
|
||||
* prereq). A module-level nullable `(*T | void)` GLOBAL has no storage
|
||||
* path in either stage: letemitsize / let_emit_size returned 0 for the
|
||||
* nullable TY_TAGGED, so let_collect skipped registration and emit_lets
|
||||
* skipped DATA. The three READ paths then miscompiled SILENTLY:
|
||||
* - match (g) : cgmatch's #87 arm is let_islet-gated; with no
|
||||
* registration it fell to localfind -> read 0(BP) =
|
||||
* saved BP (garbage tag).
|
||||
* - g is *T : cgtypetest's nullable arm emitted MOVQ name(SB),AX
|
||||
* for a symbol with no DATA -> w6l undefined-reference.
|
||||
* - g as *T : cgtypeassert, same undefined-reference.
|
||||
* cstage's #87 match arm was ALSO `!is_nullable`-gated, so BOTH stages
|
||||
* were wrong (a #263-class both-wrong gap, not an align-up).
|
||||
*
|
||||
* THE BRIDGE (this commit): die LOUD at the size/storage layer
|
||||
* (let_emit_size:1278 / letemitsize cgen.ww:1054) the instant a nullable
|
||||
* global is declared, so all three read paths hit one diagnostic instead
|
||||
* of a silent miscompile. The full storage + read-class arc (real DATA,
|
||||
* nil/void/address-of init, let-registration, the three SB-resolution
|
||||
* read arms) is deferred to task #15 — it is the CSP handle-singleton
|
||||
* prereq (`let c: *Chan | void`), and a silent gap there is exactly what
|
||||
* "stable before CSP" forbids. Byte-id-neutral: the corpus declares zero
|
||||
* nullable globals (verified by grep), so the loud path is unreached in
|
||||
* self-compile and the emitted asm is zero-move.
|
||||
*
|
||||
* The diagnostic core text is identical on both stages
|
||||
* ("nullable-global storage unimplemented (task #15)"); cstage's fatal()
|
||||
* adds the harness-wide "ww: " prefix (err.c) that err.ww does not — the
|
||||
* same per-stage prefix asymmetry every existing both-stage reject
|
||||
* carries (e.g. the #37 / #48 fatals). The matrix asserts rc!=0 AND the
|
||||
* shared core substring on each driver.
|
||||
*
|
||||
* Reject-matrix idiom (sibling 989_catA_f2_reject.c): a REJECT row must
|
||||
* FAIL to build with the diagnostic on every driver; an ACCEPT control
|
||||
* must build + run to its expected exit. Rows run on cstage `ww` and,
|
||||
* when present, wwstage `ww_ww`; both must agree.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static const char *DIAG = "nullable-global storage unimplemented (task #15)";
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must REJECT */
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
#define NP "type np = (*i64 | void);\nlet gv: i64 = 7;\n"
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* match on a nullable global -> loud reject (was: read 0(BP)). */
|
||||
{ "match_nullable_global",
|
||||
"package main;\n" NP "let g: np = &gv;\n"
|
||||
"fn ck() int = { match (g) { case let p: *i64 => return (*p): int; "
|
||||
"case void => return 9; }; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* `g is *T` on a nullable global -> loud reject (was: undefined ref). */
|
||||
{ "is_nullable_global",
|
||||
"package main;\n" NP "let g: np = &gv;\n"
|
||||
"fn ck() int = { if (g is *i64) { return 0; }; return 12; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* `g as *T` on a nullable global -> loud reject (was: undefined ref). */
|
||||
{ "as_nullable_global",
|
||||
"package main;\n" NP "let g: np = &gv;\n"
|
||||
"fn ck() int = { let p: *i64 = g as *i64; return (*p): int; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* A void-initialised nullable global rejects too — storage is
|
||||
* unimplemented regardless of the initialiser (the full arc handles
|
||||
* nil/void as 8 zero bytes; that is task #15). */
|
||||
{ "void_init_nullable_global",
|
||||
"package main;\n" NP "let g: np = void;\n"
|
||||
"fn ck() int = { match (g) { case let p: *i64 => return (*p): int; "
|
||||
"case void => return 9; }; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* A nil-initialised nullable global rejects too — init-invariant,
|
||||
* same as void (the storage gate keys on the nullable TY_TAGGED, not
|
||||
* the initialiser; nil is the silent-match case the reconcile flagged
|
||||
* alongside void/&gv). */
|
||||
{ "nil_init_nullable_global",
|
||||
"package main;\n" NP "let g: np = nil;\n"
|
||||
"fn ck() int = { match (g) { case let p: *i64 => return (*p): int; "
|
||||
"case void => return 9; }; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* The INLINE nullable form (no alias) rejects identically — exercises
|
||||
* the storage gate's direct N_TTAGGED arm rather than the
|
||||
* alias-resolution hop the other rows take, proving form-invariance. */
|
||||
{ "inline_nullable_global",
|
||||
"package main;\nlet gv: i64 = 7;\nlet g: (*i64 | void) = &gv;\n"
|
||||
"fn ck() int = { match (g) { case let p: *i64 => return (*p): int; "
|
||||
"case void => return 9; }; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* CONTROL — a NON-nullable tagged global still builds + runs (the
|
||||
* #87 storage path is untouched). is i64 on g=5 -> 0. */
|
||||
{ "nonnull_tagged_global_ok",
|
||||
"package main;\n"
|
||||
"let g: (i64 | bool) = 5;\n"
|
||||
"fn ck() int = { if (g is i64) { return 0; }; return 12; };\n"
|
||||
"export fn main() int = { return ck(); };\n",
|
||||
1, 0 },
|
||||
|
||||
/* CONTROL — a PLAIN `*T` global (not a nullable tagged) still builds +
|
||||
* runs; the reject is keyed on the nullable TY_TAGGED, not on any
|
||||
* pointer. nil-init (an address-of init is a separate #48-reloc gap,
|
||||
* out of scope here). */
|
||||
{ "plain_ptr_global_ok",
|
||||
"package main;\n"
|
||||
"let p: *i64 = nil;\n"
|
||||
"export fn main() int = { if (p == nil) { return 5; }; return 9; };\n",
|
||||
1, 5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/nbg_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/nbg_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/nbg_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
/* A reject row must (a) fail to build and (b) emit the shared diagnostic
|
||||
* core text. Returns 0 on the expected reject, -1 otherwise. */
|
||||
static int
|
||||
build_should_reject(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], errf[128], rmcmd[160], cmd[1280];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/nbn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/nbn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/nbn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/nbn_%d_%d.err", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
int have_diag = 0;
|
||||
FILE *e = fopen(errf, "rb");
|
||||
if (e) {
|
||||
char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, e);
|
||||
buf[n] = '\0';
|
||||
fclose(e);
|
||||
have_diag = (strstr(buf, DIAG) != NULL);
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
|
||||
/* build must NOT succeed AND the shared diagnostic must appear. */
|
||||
return (rc != 0 && have_diag) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "nullableglobal_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "nullableglobal_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_reject(drivers[d].drv, rows[i].src,
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr, "nullableglobal_reject[%s][%s]: "
|
||||
"expected a loud reject with \"%s\"\n",
|
||||
drivers[d].name, rows[i].label, DIAG);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "nullableglobal_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("nullableglobal_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 989_path_run — execute the lib/path @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 979_hex_run / 967_bytes_run:
|
||||
* pathtest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/path/pathtest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "path_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("path_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 989_sha256_run — execute the lib/crypto/sha256 @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 989_siphash_run / 985_adler32_run (the hash/crypto cluster;
|
||||
* 9xx is full so this shares the 989 prefix — the `short` name keys the
|
||||
* binary, cf the 949_* multi-file precedent). sha256_test.ww carries its
|
||||
* own `export fn main()` that drives the NIST-vector @test fns and signals
|
||||
* which case failed via the exit code, so this file is just a thin
|
||||
* wrapper — no @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/crypto/sha256/sha256_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "sha256_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("sha256_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 989_siphash_run — execute the lib/hash/siphash @test fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Sibling to 980_memio_run / 981_temp_run / 982_getopt_run /
|
||||
* 998_bufio_run. siphash_test.ww carries its own `export fn main()`
|
||||
* that drives the @test fns and signals which case failed via the
|
||||
* exit code, so this file is just a thin wrapper — no @test
|
||||
* scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/hash/siphash/siphash_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "siphash_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("siphash_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* 989_taggedcompoundderef_reject — #18, the last of the tagged-payload
|
||||
* WRITE class. A compound-assign through a deref, `*p OP= v` where p is a
|
||||
* pointer to a tagged union (`*(int|bool)`), is semantically nonsense: you
|
||||
* cannot `+=` a whole tagged value. It MUST be a loud compile-time reject.
|
||||
*
|
||||
* cstage (cmd/w6c/cgen.c) already rejected it: its `*p OP= v` arm gates on
|
||||
* `handled = sz in {1,2,4,8}`, so a tagged pointee (sz>=16) falls through
|
||||
* to the assign-resolver, which fatals "tagged field not wired (rule-7)".
|
||||
*
|
||||
* wwstage (selfhost/cmd/wcc/cgenexpr.ww) SILENTLY MISCOMPILED it: its
|
||||
* compound-deref arm narrowed the store op for ps in {1,2,4} else fell to a
|
||||
* single MOVQ store-and-return — emitting one 8-byte op on the TAG word and
|
||||
* returning, never reaching its own (already-present) resolver reject.
|
||||
* Result: compiled clean, ran WRONG. This is a wwstage align-DOWN (rule-10):
|
||||
* the fix adds the same size gate so a non-scalar pointee falls through to
|
||||
* the identical resolver reject. wwstage-ONLY change; cstage untouched.
|
||||
*
|
||||
* The diagnostic core text is identical on both stages
|
||||
* ("assign-resolver: tagged field not wired (rule-7)"); cstage's fatal()
|
||||
* adds the harness-wide "ww: " prefix that err.ww does not — the same
|
||||
* per-stage prefix asymmetry every existing both-stage reject carries. The
|
||||
* matrix asserts rc!=0 AND the shared core substring on each driver.
|
||||
*
|
||||
* Reject-matrix idiom (sibling 989_nullableglobal_reject.c): a REJECT row
|
||||
* must FAIL to build with the diagnostic on every driver; an ACCEPT control
|
||||
* must build + run to its expected exit. Rows run on cstage `ww` and, when
|
||||
* present, wwstage `ww_ww`; both must agree.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static const char *DIAG = "assign-resolver: tagged field not wired (rule-7)";
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must REJECT */
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
/* `let p = &v;` binds p:*(int|bool); `*p OP= 1` compounds the WHOLE tagged
|
||||
* value -> nonsense -> loud reject on BOTH stages. */
|
||||
#define TAGCOMPOUND(OP) \
|
||||
"package main;\n" \
|
||||
"export fn main() int = {\n" \
|
||||
"\tlet v: (int | bool) = 7;\n" \
|
||||
"\tlet p = &v;\n" \
|
||||
"\t*p " OP " 1;\n" \
|
||||
"\treturn 0;\n" \
|
||||
"};\n"
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Every compound OP on a *tagged deref rejects on both stages. */
|
||||
{ "pluseq_tagged_deref", TAGCOMPOUND("+="), 0, 0 },
|
||||
{ "minuseq_tagged_deref", TAGCOMPOUND("-="), 0, 0 },
|
||||
{ "stareq_tagged_deref", TAGCOMPOUND("*="), 0, 0 },
|
||||
{ "pipeeq_tagged_deref", TAGCOMPOUND("|="), 0, 0 },
|
||||
{ "ampeq_tagged_deref", TAGCOMPOUND("&="), 0, 0 },
|
||||
{ "careteq_tagged_deref", TAGCOMPOUND("^="), 0, 0 },
|
||||
{ "lshifteq_tagged_deref", TAGCOMPOUND("<<="), 0, 0 },
|
||||
{ "rshifteq_tagged_deref", TAGCOMPOUND(">>="), 0, 0 },
|
||||
{ "slasheq_tagged_deref", TAGCOMPOUND("/="), 0, 0 },
|
||||
{ "percenteq_tagged_deref",TAGCOMPOUND("%="), 0, 0 },
|
||||
|
||||
/* CONTROL — scalar compound-deref on *int still compiles + runs (the
|
||||
* gate keys on the non-scalar pointee size; an 8-byte int is handled).
|
||||
* 7 += 1 -> 8. */
|
||||
{ "scalar_int_compound_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet v: int = 7;\n"
|
||||
"\tlet p = &v;\n"
|
||||
"\t*p += 1;\n"
|
||||
"\treturn *p;\n"
|
||||
"};\n",
|
||||
1, 8 },
|
||||
|
||||
/* CONTROL — narrow scalar compound-deref on *u8 still compiles + runs
|
||||
* (1-byte pointee is handled; the narrowing path is undisturbed). */
|
||||
{ "scalar_u8_compound_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet v: u8 = 7;\n"
|
||||
"\tlet p = &v;\n"
|
||||
"\t*p += 1;\n"
|
||||
"\treturn (*p): int;\n"
|
||||
"};\n",
|
||||
1, 8 },
|
||||
|
||||
/* CONTROL — the #17 PLAIN deref store on a *tagged still compiles +
|
||||
* runs (only the COMPOUND form is nonsense; plain `*p = v` widens the
|
||||
* payload). 9 stored, `v is int` -> 0. */
|
||||
{ "plain_tagged_deref_store_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet v: (int | bool) = 7;\n"
|
||||
"\tlet p = &v;\n"
|
||||
"\t*p = 9;\n"
|
||||
"\tif (v is int) { return 0; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
1, 0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], cmd[1024], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tcd_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/tcd_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tcd_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
/* A reject row must (a) fail to build and (b) emit the shared diagnostic
|
||||
* core text. Returns 0 on the expected reject, -1 otherwise. */
|
||||
static int
|
||||
build_should_reject(const char *driver, const char *src, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], errf[128], cmd[1280], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tcn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/tcn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tcn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/tcn_%d_%d.err", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
/* -o into tmpdir keeps the build's <stem>.sepwork inside tmpdir. */
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
int have_diag = 0;
|
||||
FILE *e = fopen(errf, "rb");
|
||||
if (e) {
|
||||
char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, e);
|
||||
buf[n] = '\0';
|
||||
fclose(e);
|
||||
have_diag = (strstr(buf, DIAG) != NULL);
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
|
||||
/* build must NOT succeed AND the shared diagnostic must appear. */
|
||||
return (rc != 0 && have_diag) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "taggedcompoundderef_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "taggedcompoundderef_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_reject(drivers[d].drv, rows[i].src,
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr, "taggedcompoundderef_reject[%s][%s]: "
|
||||
"expected a loud reject with \"%s\"\n",
|
||||
drivers[d].name, rows[i].label, DIAG);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "taggedcompoundderef_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("taggedcompoundderef_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
/*
|
||||
* 989_taggedcompoundplace_reject — #20/#21, the last two members of the
|
||||
* "compound-OP on a whole tagged place" reject class (siblings: #18 deref,
|
||||
* #34 dot). A compound-assign `place OP= v` where `place` has a tagged-union
|
||||
* type is semantically nonsense — you cannot `+=` a whole tagged value — so
|
||||
* it MUST be a loud compile-time reject on BOTH stages.
|
||||
*
|
||||
* Two place-kinds remained after #18 closed the deref member:
|
||||
*
|
||||
* INDEX (#20): `gs[i] OP= v` (global tagged array) / `a[i] OP= v` (local
|
||||
* tagged array). cstage SILENTLY MISCOMPILED: its indexed-tagged element
|
||||
* arm (cmd/w6c/cgen.c) had no `n->op == TK_ASSIGN` gate, so a compound
|
||||
* dropped the OP and plain-stored `(tagged)v` (cs!=ww). wwstage already
|
||||
* rejected via its #133 indexed-compound arm. The fix gates the cstage
|
||||
* plain-store arm on TK_ASSIGN so a compound falls to the same #133
|
||||
* indexed-compound reject — the byte-id twin of wwstage. cstage align-UP.
|
||||
*
|
||||
* IDENT (#21): `g OP= v` where g is a tagged-union local/global. BOTH
|
||||
* stages SILENTLY MISCOMPILED, byte-identically — the ident
|
||||
* load-combine-store tail read+wrote one word of the {payload,tag} box
|
||||
* and ADDED to the TAG word (gate-blind, #17/#263-class). The fix adds a
|
||||
* tagged-ident compound guard to BOTH stages (cmd/w6c/cgen.c and
|
||||
* selfhost/cmd/wcc/cgenexpr.ww) that rejects loud.
|
||||
*
|
||||
* The reject diagnostics follow the established per-place-kind family
|
||||
* (#34 "single-dot field ... (#34/rule-7)", #133 "indexed-lvalue ...
|
||||
* (#133/rule-7)") rather than one uniform string: INDEX rows reach the
|
||||
* #133 indexed-compound reject; IDENT rows reach the new #21 ident-compound
|
||||
* reject. Each row carries its own diagnostic substring, asserted on every
|
||||
* driver alongside rc!=0. cstage's fatal() prefixes "ww: " that err.ww does
|
||||
* not — the same per-stage prefix asymmetry every both-stage reject carries;
|
||||
* the matrix matches on the shared CORE substring only.
|
||||
*
|
||||
* Reject-matrix idiom (sibling 989_taggedcompoundderef_reject.c): a REJECT
|
||||
* row must FAIL to build with its diagnostic on every driver; an ACCEPT
|
||||
* control must build + run to its expected exit. Rows run on cstage `ww`
|
||||
* and, when present, wwstage `ww_ww`; both must agree.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static const char *DIAG_INDEX = "indexed-lvalue compound on tagged element not wired (#133/rule-7)";
|
||||
static const char *DIAG_IDENT = "ident compound on tagged not wired (#21/rule-7)";
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must REJECT */
|
||||
int want_exit;
|
||||
const char *diag; /* reject rows only: required core substring */
|
||||
};
|
||||
|
||||
/* `g` is a (int|bool) local; `g OP= 1` compounds the WHOLE tagged value. */
|
||||
#define IDENTCOMPOUND(OP) \
|
||||
"package main;\n" \
|
||||
"export fn main() int = {\n" \
|
||||
"\tlet g: (int | bool) = 7;\n" \
|
||||
"\tg " OP " 1;\n" \
|
||||
"\treturn 0;\n" \
|
||||
"};\n"
|
||||
|
||||
/* a:[3](int|bool) local; `a[0] OP= 1` compounds the WHOLE tagged element. */
|
||||
#define LIDXCOMPOUND(OP) \
|
||||
"package main;\n" \
|
||||
"export fn main() int = {\n" \
|
||||
"\tlet a: [3](int | bool) = [1, 2, 3];\n" \
|
||||
"\ta[0] " OP " 1;\n" \
|
||||
"\treturn 0;\n" \
|
||||
"};\n"
|
||||
|
||||
/* gs:[3](int|bool) global; `gs[0] OP= 1` compounds the WHOLE tagged element. */
|
||||
#define GIDXCOMPOUND(OP) \
|
||||
"package main;\n" \
|
||||
"let gs: [3](int | bool) = [1, 2, 3];\n" \
|
||||
"export fn main() int = {\n" \
|
||||
"\tgs[0] " OP " 1;\n" \
|
||||
"\treturn 0;\n" \
|
||||
"};\n"
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* IDENT (#21) — every compound OP on a tagged local rejects on both
|
||||
* stages (was a byte-identical tag-word corruption). */
|
||||
{ "ident_pluseq", IDENTCOMPOUND("+="), 0, 0, NULL },
|
||||
{ "ident_minuseq", IDENTCOMPOUND("-="), 0, 0, NULL },
|
||||
{ "ident_stareq", IDENTCOMPOUND("*="), 0, 0, NULL },
|
||||
{ "ident_pipeeq", IDENTCOMPOUND("|="), 0, 0, NULL },
|
||||
{ "ident_careteq", IDENTCOMPOUND("^="), 0, 0, NULL },
|
||||
{ "ident_lshifteq", IDENTCOMPOUND("<<="), 0, 0, NULL },
|
||||
{ "ident_slasheq", IDENTCOMPOUND("/="), 0, 0, NULL },
|
||||
|
||||
/* IDENT (#21) — a tagged GLOBAL compound rejects too. */
|
||||
{ "gident_pluseq", "package main;\n"
|
||||
"let g: (int | bool) = 7;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tg += 1;\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n", 0, 0, NULL },
|
||||
|
||||
/* INDEX (#20) — local tagged-array element compound rejects on both
|
||||
* stages (cstage was silently dropping the OP, plain-storing the RHS). */
|
||||
{ "lidx_pluseq", LIDXCOMPOUND("+="), 0, 0, NULL },
|
||||
{ "lidx_minuseq", LIDXCOMPOUND("-="), 0, 0, NULL },
|
||||
{ "lidx_pipeeq", LIDXCOMPOUND("|="), 0, 0, NULL },
|
||||
{ "lidx_lshifteq", LIDXCOMPOUND("<<="), 0, 0, NULL },
|
||||
|
||||
/* INDEX (#20) — global tagged-array element compound rejects too. */
|
||||
{ "gidx_pluseq", GIDXCOMPOUND("+="), 0, 0, NULL },
|
||||
{ "gidx_stareq", GIDXCOMPOUND("*="), 0, 0, NULL },
|
||||
{ "gidx_careteq", GIDXCOMPOUND("^="), 0, 0, NULL },
|
||||
|
||||
/* CONTROL — scalar IDENT compound (`g += 1` on g:int) still compiles +
|
||||
* runs (the guard keys on a tagged type only). 7 += 1 -> 8. */
|
||||
{ "scalar_ident_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet g: int = 7;\n"
|
||||
"\tg += 1;\n"
|
||||
"\treturn g;\n"
|
||||
"};\n",
|
||||
1, 8, NULL },
|
||||
|
||||
/* CONTROL — scalar INDEX compound (`a[0] += 1` on a:[3]int) still
|
||||
* compiles + runs (the #133 arm wires the scalar element). 7 += 1 -> 8. */
|
||||
{ "scalar_index_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet a: [3]int = [7, 0, 0];\n"
|
||||
"\ta[0] += 1;\n"
|
||||
"\treturn a[0];\n"
|
||||
"};\n",
|
||||
1, 8, NULL },
|
||||
|
||||
/* CONTROL — the #41/#32 PLAIN tagged-ident reassign still compiles +
|
||||
* runs (only the COMPOUND form is nonsense; `g = v` widens). `g is int`
|
||||
* -> 0. */
|
||||
{ "plain_tagged_ident_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet g: (int | bool) = 7;\n"
|
||||
"\tg = 9;\n"
|
||||
"\tif (g is int) { return 0; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
1, 0, NULL },
|
||||
|
||||
/* CONTROL — the #16 PLAIN tagged-index store (local) still compiles +
|
||||
* runs (`a[0] = v` widens the element). `a[0] is int` -> 0. */
|
||||
{ "plain_tagged_lidx_ok",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tlet a: [3](int | bool) = [1, 2, 3];\n"
|
||||
"\ta[0] = 9;\n"
|
||||
"\tif (a[0] is int) { return 0; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
1, 0, NULL },
|
||||
|
||||
/* CONTROL — the #16 PLAIN tagged-index store (global) still compiles +
|
||||
* runs (`gs[0] = v` widens; the store precedes the read so the #19
|
||||
* static-init gap is not exercised). `gs[0] is int` -> 0. */
|
||||
{ "plain_tagged_gidx_ok",
|
||||
"package main;\n"
|
||||
"let gs: [3](int | bool) = [1, 2, 3];\n"
|
||||
"export fn main() int = {\n"
|
||||
"\tgs[0] = 9;\n"
|
||||
"\tif (gs[0] is int) { return 0; };\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n",
|
||||
1, 0, NULL },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tcp_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/tcp_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tcp_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
/* A reject row must (a) fail to build and (b) emit its diagnostic core text.
|
||||
* Returns 0 on the expected reject, -1 otherwise. */
|
||||
static int
|
||||
build_should_reject(const char *driver, const char *src, const char *diag, int i)
|
||||
{
|
||||
char tmpdir[64], s[128], outbin[128], errf[128], rmcmd[160], cmd[1280];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tcq_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/tcq_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tcq_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/tcq_%d_%d.err", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
int have_diag = 0;
|
||||
FILE *e = fopen(errf, "rb");
|
||||
if (e) {
|
||||
char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, e);
|
||||
buf[n] = '\0';
|
||||
fclose(e);
|
||||
have_diag = (strstr(buf, diag) != NULL);
|
||||
}
|
||||
|
||||
runwait(rmcmd);
|
||||
|
||||
/* build must NOT succeed AND the diagnostic must appear. */
|
||||
return (rc != 0 && have_diag) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "taggedcompoundplace_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "taggedcompoundplace_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
const char *diag = rows[i].diag;
|
||||
if (!diag)
|
||||
diag = (strncmp(rows[i].label, "ident", 5) == 0
|
||||
|| strncmp(rows[i].label, "gident", 6) == 0)
|
||||
? DIAG_IDENT : DIAG_INDEX;
|
||||
if (build_should_reject(drivers[d].drv, rows[i].src,
|
||||
diag, 100 + i) != 0) {
|
||||
fprintf(stderr, "taggedcompoundplace_reject[%s][%s]: "
|
||||
"expected a loud reject with \"%s\"\n",
|
||||
drivers[d].name, rows[i].label, diag);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "taggedcompoundplace_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("taggedcompoundplace_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* 989_test_filter — the `ww test <file> [pattern]` fnmatch NAME-FILTER
|
||||
* (task #17 commit-3, drew spec §d). Drives the full driver→binary→
|
||||
* lib/test path on BOTH driver twins (cstage `ww`, wwstage `ww_ww`):
|
||||
*
|
||||
* - no pattern → every @test runs (byte-for-byte the pre-filter
|
||||
* behavior: "N passed, M failed").
|
||||
* - "beta" → only the matching @test runs; the others are
|
||||
* absent from the per-test output.
|
||||
* - "ga*" → glob selects gamma only.
|
||||
* - "zzz" → zero matches → "No tests run", rc 0 (Hare ground
|
||||
* truth, ref/hare/test/+test.ha:114-117).
|
||||
*
|
||||
* Each row asserts rc + required/forbidden stdout substrings AND that the
|
||||
* two driver twins emit byte-identical stdout (rule 10). The fixture is
|
||||
* test/wcc/data/filter_fixture.ww (three trivially-passing @test fns
|
||||
* alpha/beta/gamma). The filter lives in lib/test run() reading os.args;
|
||||
* the -T synth + value table are unchanged, so 990-997 byte-id holds.
|
||||
*
|
||||
* Driver-arg ERROR forms (lone/dir-mode pattern) live in
|
||||
* 949_driver_flagargs. Light driver test, intermediates go to the
|
||||
* driver's own /tmp temp (rule 14), any NNN.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
enum { Nexpect = 4 };
|
||||
|
||||
struct row {
|
||||
const char *pat; /* pattern arg, or NULL for none */
|
||||
int rc; /* expected exit code */
|
||||
const char *must[Nexpect]; /* substrings required in stdout */
|
||||
const char *mustnot[Nexpect]; /* substrings forbidden in stdout */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* no pattern → all three, full count */
|
||||
{ NULL, 0, { "alpha ... ok", "beta ... ok", "gamma ... ok",
|
||||
"3 passed, 0 failed" }, { NULL } },
|
||||
/* exact name → only beta */
|
||||
{ "beta", 0, { "beta ... ok", "1 passed, 0 failed", NULL },
|
||||
{ "alpha", "gamma", NULL } },
|
||||
/* glob → only gamma */
|
||||
{ "ga*", 0, { "gamma ... ok", "1 passed, 0 failed", NULL },
|
||||
{ "alpha", "beta", NULL } },
|
||||
/* no match → "No tests run", success */
|
||||
{ "zzz", 0, { "No tests run", NULL },
|
||||
{ "alpha", "beta", "gamma", "passed" } },
|
||||
/* star matches everything */
|
||||
{ "*", 0, { "alpha ... ok", "beta ... ok", "gamma ... ok",
|
||||
"3 passed, 0 failed" }, { NULL } },
|
||||
};
|
||||
|
||||
/* Run "<bin>/<drv> test <fixture> [pat]" capturing rc + stdout into out
|
||||
* (NUL-terminated). Returns the child's exit code (or -1 on spawn fail). */
|
||||
static int
|
||||
run_drv(const char *bin, const char *drv, const char *pat,
|
||||
char *out, size_t outsz)
|
||||
{
|
||||
int pid = getpid();
|
||||
char outf[64], cmd[2048];
|
||||
snprintf(outf, sizeof outf, "/tmp/tf989_%d.out", pid);
|
||||
if (pat) {
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s test test/wcc/data/filter_fixture.ww '%s' >%s 2>/dev/null",
|
||||
bin, drv, pat, outf);
|
||||
} else {
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s test test/wcc/data/filter_fixture.ww >%s 2>/dev/null",
|
||||
bin, drv, outf);
|
||||
}
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) { unlink(outf); return -1; }
|
||||
rc = WIFEXITED(rc) ? WEXITSTATUS(rc) : 1;
|
||||
out[0] = '\0';
|
||||
FILE *f = fopen(outf, "r");
|
||||
if (f) {
|
||||
size_t got = fread(out, 1, outsz - 1, f);
|
||||
out[got] = '\0';
|
||||
fclose(f);
|
||||
}
|
||||
unlink(outf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
size_t ncwd = strlen(cwd);
|
||||
size_t nbin = strlen(bin);
|
||||
if (nbin >= sizeof absbin - ncwd - 1) return 1;
|
||||
memcpy(absbin, cwd, ncwd);
|
||||
absbin[ncwd] = '/';
|
||||
memcpy(absbin + ncwd + 1, bin, nbin + 1);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
int fail = 0;
|
||||
size_t n = sizeof rows / sizeof rows[0];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const char *pat = rows[i].pat ? rows[i].pat : "(none)";
|
||||
char cout[8192], wout[8192];
|
||||
int crc = run_drv(bin, "ww", rows[i].pat, cout, sizeof cout);
|
||||
int wrc = run_drv(bin, "ww_ww", rows[i].pat, wout, sizeof wout);
|
||||
|
||||
if (crc != rows[i].rc) {
|
||||
fprintf(stderr, "989 FAIL: ww pat=%s rc=%d want=%d\n",
|
||||
pat, crc, rows[i].rc);
|
||||
fail = 1;
|
||||
}
|
||||
for (int k = 0; k < Nexpect && rows[i].must[k]; k++)
|
||||
if (!strstr(cout, rows[i].must[k])) {
|
||||
fprintf(stderr, "989 FAIL: ww pat=%s stdout missing "
|
||||
"'%s' (got '%s')\n", pat, rows[i].must[k], cout);
|
||||
fail = 1;
|
||||
}
|
||||
for (int k = 0; k < Nexpect && rows[i].mustnot[k]; k++)
|
||||
if (strstr(cout, rows[i].mustnot[k])) {
|
||||
fprintf(stderr, "989 FAIL: ww pat=%s stdout has "
|
||||
"forbidden '%s' (got '%s')\n",
|
||||
pat, rows[i].mustnot[k], cout);
|
||||
fail = 1;
|
||||
}
|
||||
/* twin parity: ww_ww must match ww exactly (rc + stdout). */
|
||||
if (wrc != crc) {
|
||||
fprintf(stderr, "989 FAIL: pat=%s ww_ww rc=%d != ww rc=%d\n",
|
||||
pat, wrc, crc);
|
||||
fail = 1;
|
||||
}
|
||||
if (strcmp(wout, cout) != 0) {
|
||||
fprintf(stderr, "989 FAIL: pat=%s ww_ww stdout '%s' != ww '%s'\n",
|
||||
pat, wout, cout);
|
||||
fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) return 1;
|
||||
printf("test name-filter: %zu rows, ww==ww_ww pinned\n", n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 989_trycallcollide_run (#11a, c3) — exprtypeoftry must prefer the current
|
||||
* module's symbol when typing a `?`/`!` operand whose bare leaf collides with
|
||||
* a same-leaf decl in a later module.
|
||||
*
|
||||
* THE BUG (review item #11, 2026-06-11): exprtypeoftry (check.ww ~5850 N_IDENT,
|
||||
* ~5874 N_CALL bare-leaf callee) typed the try operand with bare scopelookup —
|
||||
* no (name,module) preference. scopedefineinmodule PREPENDS, so a later
|
||||
* module's same-leaf decl heads the chain and the operand is typed against ITS
|
||||
* signature. For `op()?` the wrong op's error subset is then checked against
|
||||
* the enclosing fn's return: a spurious "?: error variant not in enclosing
|
||||
* return" rejects valid code (or, with a narrower foreign subset, silently
|
||||
* skips the F8 multi-success reject). cstage types the operand via cexpr with
|
||||
* cur_mod. THE FIX: both arms use scopelookupprefer(c.cur, c.curmod, ...),
|
||||
* mirroring exprtype's own N_IDENT/N_CALL arms (:2897/:3336).
|
||||
*
|
||||
* GATING NOTE: this swap broke 995_self_rebuild once in a CONTAMINATED earlier
|
||||
* attempt (bundled with a u64-guard-buggy scopelookuptype). Re-probed clean
|
||||
* atop c1 (i32-correct scopelookuptype): repro closes AND 990-997/995 green.
|
||||
*
|
||||
* row | shape | run (cs==ww)
|
||||
* -----------+---------------------------------------------+-------------
|
||||
* callcollide| beta.user() does `op()?`; gamma also has | 42 (was ww
|
||||
* | `op` with a wider error subset | build-fail)
|
||||
* noclash | same, gamma.op renamed (no collision) | 42 (control)
|
||||
*
|
||||
* The N_DOT-callee module-keyed resolution stays task #51 (out of scope).
|
||||
* Single-file multi-package source is the sanctioned shape (cmd/ww/main.c).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "callcollide",
|
||||
"package beta;\n"
|
||||
"export type berr = !void;\n"
|
||||
"export fn op() (i32 | berr) = { return 5; };\n"
|
||||
"export fn user() (i32 | berr) = {\n"
|
||||
" let v: i32 = op()?;\n"
|
||||
" return v + 37;\n"
|
||||
"};\n"
|
||||
"package gamma;\n"
|
||||
"export type gerr1 = !void;\n"
|
||||
"export type gerr2 = !u8;\n"
|
||||
"export fn op() (i32 | gerr1 | gerr2) = { return 0; };\n"
|
||||
"package main;\n"
|
||||
"import beta;\n"
|
||||
"import gamma;\n"
|
||||
"fn main() int = {\n"
|
||||
" match (beta.user()) {\n"
|
||||
" case let n: i32 => return n: int;\n"
|
||||
" case berr => return 99;\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
|
||||
{ "noclash",
|
||||
"package beta;\n"
|
||||
"export type berr = !void;\n"
|
||||
"export fn op() (i32 | berr) = { return 5; };\n"
|
||||
"export fn user() (i32 | berr) = {\n"
|
||||
" let v: i32 = op()?;\n"
|
||||
" return v + 37;\n"
|
||||
"};\n"
|
||||
"package gamma;\n"
|
||||
"export type gerr1 = !void;\n"
|
||||
"export fn other() (i32 | gerr1) = { return 0; };\n"
|
||||
"package main;\n"
|
||||
"import beta;\n"
|
||||
"import gamma;\n"
|
||||
"fn main() int = {\n"
|
||||
" match (beta.user()) {\n"
|
||||
" case let n: i32 => return n: int;\n"
|
||||
" case berr => return 99;\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[64], src[128], outbin[128], cmd[1024], rmcmd[160];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tcc_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/tcc_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/tcc_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1; /* -1 == build failed */
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int gc = run_build(cdrv, &rows[i], i);
|
||||
if (gc != rows[i].want) {
|
||||
fprintf(stderr, "trycallcollide[cstage][%s]: got=%d want=%d\n",
|
||||
rows[i].label, gc, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "trycallcollide: skip wwstage (no %s)\n", wdrv);
|
||||
continue;
|
||||
}
|
||||
int gw = run_build(wdrv, &rows[i], i);
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "trycallcollide[%s]: cs=%d != ww=%d "
|
||||
"(exprtypeoftry cross-module try-operand misbind — #11a c3)\n",
|
||||
rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
if (gw != rows[i].want) {
|
||||
fprintf(stderr, "trycallcollide[wwstage][%s]: got=%d want=%d\n",
|
||||
rows[i].label, gw, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "trycallcollide_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("trycallcollide_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
* 989_unknowndecl_reject — F14 #55: parsefile's recovery fallback
|
||||
* (lib/ww/parse/parse.ww) chewed an unrecognized top-level construct to
|
||||
* the next ';' WITHOUT emitting an error or bumping p.errs, so a typo'd
|
||||
* keyword / stray token / stray decl was silently dropped from the AST
|
||||
* and the build succeeded rc=0 with the work gone. The C twin
|
||||
* (parse.c:1395-1400) emits errorf("expected top-level decl") + p->errs++
|
||||
* and rejects. The fix calls errmsg in the fallback arm, aligning wwstage
|
||||
* UP to cstage.
|
||||
*
|
||||
* Each REJECT row carries an unknown top-level construct and must FAIL to
|
||||
* build on BOTH driver twins; each control is well-formed and must
|
||||
* build+run. The fn-drop row is the live silent-loss case (a whole
|
||||
* declared fn vanished with no diagnostic pre-fix).
|
||||
*
|
||||
* Rule-10: every row runs on cstage `ww` and wwstage `ww_ww`; both must
|
||||
* agree. wwstage rows are gated on out/bin/ww_ww.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
||||
int want_exit;
|
||||
const char *diag; /* expected reject diagnostic (NULL for build rows) */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* REJECT — a misspelled `fn` keyword: the whole decl was silently
|
||||
* dropped pre-fix (grep -c helper of the .s gave 0). */
|
||||
{ "misspelled_fn",
|
||||
"package main;\n"
|
||||
"fnn helper() i32 = { return 42; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0, 0, "expected top-level decl" },
|
||||
|
||||
/* REJECT — a stray run of bare identifiers at top level. */
|
||||
{ "stray_idents",
|
||||
"package main;\n"
|
||||
"bogus token here;\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0, 0, "expected top-level decl" },
|
||||
|
||||
/* REJECT — a stray decl whose body has internal ';'s, exercising the
|
||||
* brace-balanced chew recovery (still must error, not silently skip). */
|
||||
{ "stray_block",
|
||||
"package main;\n"
|
||||
"gizmo foo { let a = 1; let b = 2; };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0, 0, "expected top-level decl" },
|
||||
|
||||
/* control — a clean program builds + runs. */
|
||||
{ "clean_ok",
|
||||
"package main;\n"
|
||||
"fn helper() i32 = { return 5; };\n"
|
||||
"export fn main() i32 = { return helper(); };\n",
|
||||
1, 5 },
|
||||
|
||||
/* control — a valid def/type/fn mix builds + runs. */
|
||||
{ "decls_ok",
|
||||
"package main;\n"
|
||||
"def K: i32 = 3;\n"
|
||||
"type t = i32;\n"
|
||||
"export fn main() i32 = { let x: t = K; return x: i32; };\n",
|
||||
1, 3 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/und_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(src, sizeof src, "%s/und_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/und_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) { runwait(rmcmd); return -2; }
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
||||
driver, outbin, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
runwait(rmcmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
filehas(const char *path, const char *needle)
|
||||
{
|
||||
char buf[8192];
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
/* build_should_fail — the build must FAIL *and* emit `diag`. A crash
|
||||
* (segfault) wraps as a nonzero "w6c failed" with no diagnostic, so the
|
||||
* substring check distinguishes it from a clean reject (#20). cstage
|
||||
* names the offending token ("expected top-level decl, got IDENT"),
|
||||
* wwstage prints "parse: expected top-level decl"; the shared core is
|
||||
* "expected top-level decl". */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, const char *diag,
|
||||
int i)
|
||||
{
|
||||
char s[128], tmpdir[64], outbin[128], errf[128], rmcmd[160], cmd[1024];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/undn_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(s, sizeof s, "%s/undn_%d_%d.ww", tmpdir, getpid(), i);
|
||||
snprintf(outbin, sizeof outbin, "%s/undn_%d_%d", tmpdir, getpid(), i);
|
||||
snprintf(errf, sizeof errf, "%s/err", tmpdir);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) { runwait(rmcmd); return -1; }
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
|
||||
driver, outbin, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
int hasmsg = filehas(errf, diag);
|
||||
|
||||
runwait(rmcmd);
|
||||
/* build must NOT succeed AND must emit its diagnostic. */
|
||||
return (rc != 0 && hasmsg) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "unknowndecl_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "unknowndecl_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_fail(drivers[d].drv, rows[i].src,
|
||||
rows[i].diag, 100 + i) != 0) {
|
||||
fprintf(stderr, "unknowndecl_reject[%s][%s]: "
|
||||
"built ok, expected a loud reject\n",
|
||||
drivers[d].name, rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "unknowndecl_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("unknowndecl_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* 989_zerotest_run (#64, F15 c7) — `ww test <dir>` over a directory with no
|
||||
* *_test.ww files must fail loud, not return rc=0 with a silent false-green.
|
||||
*
|
||||
* THE BUG (wwstage driver only, cat-A silent false-green under rc=0):
|
||||
* rundirtests had no zero-count check — a directory with no *_test.ww files
|
||||
* yielded pass=0 fail=0 → `return 0` with no output at all. A typo'd or empty
|
||||
* test dir got a silent green from the wwstage driver, while cstage do_test
|
||||
* errors "ww test: no *_test.ww files in %s" rc=1 (cmd/ww/main.c:945). THE FIX:
|
||||
* count *_test.ww matches and loud-fail (rc=1 + the same message) when zero.
|
||||
*
|
||||
* row | dir contents | result (cs==ww)
|
||||
* --------+---------------------------+----------------------------
|
||||
* empty | no files | rc != 0 (loud "no *_test.ww")
|
||||
* nomatch | foo.ww (not *_test.ww) | rc != 0 (loud)
|
||||
* healthy | foo_test.ww (1 @test) | rc == 0 (control)
|
||||
*
|
||||
* empty / nomatch were RED pre-c7 on wwstage (rc=0, no output, false-green).
|
||||
* healthy pins the real-test path: both stages run it and exit 0.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set up a fresh dir; optionally drop a file with `body` named `fname`.
|
||||
* Returns the dir path in `dir`. */
|
||||
static void
|
||||
make_dir(char *dir, size_t dsz, const char *tag, const char *fname,
|
||||
const char *body)
|
||||
{
|
||||
char cmd[256], fp[256];
|
||||
snprintf(dir, dsz, "/tmp/zt_%d_%s", getpid(), tag);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s && mkdir -p %s", dir, dir);
|
||||
system(cmd);
|
||||
if (fname) {
|
||||
snprintf(fp, sizeof fp, "%s/%s", dir, fname);
|
||||
FILE *f = fopen(fp, "wb");
|
||||
if (f) { fputs(body, f); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
/* `<driver> test <dir>` rc. */
|
||||
static int
|
||||
run_test(const char *driver, const char *dir)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s test %s >/dev/null 2>&1", driver, dir);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
if (!have_ww)
|
||||
fprintf(stderr, "zerotest: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
struct row { const char *tag; const char *fname; const char *body;
|
||||
int want_loud; };
|
||||
struct row rows[] = {
|
||||
{ "empty", NULL, NULL, 1 },
|
||||
{ "nomatch", "foo.ww",
|
||||
"package main;\nexport fn main() i32 = { return 0; };\n", 1 },
|
||||
{ "healthy", "foo_test.ww",
|
||||
"package zt_test;\n@test fn t_ok() void = { };\n", 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int fail = 0, total = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
char dir[128];
|
||||
make_dir(dir, sizeof dir, rows[i].tag, rows[i].fname, rows[i].body);
|
||||
|
||||
int crc = run_test(cdrv, dir);
|
||||
if (rows[i].want_loud) {
|
||||
if (crc == 0) {
|
||||
fprintf(stderr, "zerotest[cstage][%s]: rc=0, expected loud "
|
||||
"no-tests failure\n", rows[i].tag);
|
||||
fail++;
|
||||
}
|
||||
} else if (crc != 0) {
|
||||
fprintf(stderr, "zerotest[cstage][%s]: rc=%d, expected 0\n",
|
||||
rows[i].tag, crc);
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (have_ww) {
|
||||
int wrc = run_test(wdrv, dir);
|
||||
if (rows[i].want_loud) {
|
||||
if (wrc == 0) {
|
||||
fprintf(stderr, "zerotest[wwstage][%s]: rc=0 on a dir with "
|
||||
"no *_test.ww (silent false-green — #64)\n", rows[i].tag);
|
||||
fail++;
|
||||
}
|
||||
if ((crc == 0) != (wrc == 0)) {
|
||||
fprintf(stderr, "zerotest[%s]: loud divergence cs_rc=%d "
|
||||
"ww_rc=%d (#64)\n", rows[i].tag, crc, wrc);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (wrc != 0) {
|
||||
fprintf(stderr, "zerotest[wwstage][%s]: rc=%d, expected 0\n",
|
||||
rows[i].tag, wrc);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char cmd[160];
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "zerotest_run: %d/%d check(s) failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("zerotest_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 998_bufio_run — execute the lib/bufio @test fixture under the
|
||||
* C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Why this exists: lib/bufio/bufio.ww was lifted out of the
|
||||
* w6c-compile sweep in 900_stdlib because its cross-module type
|
||||
* refs only resolve once the driver concatenates `use`d modules.
|
||||
* That note ("Coverage lives at lib/bufio/bufiotest.ww") was true
|
||||
* in intent but not in fact — nothing actually ran bufiotest.ww
|
||||
* under `make test`. Bug #22 (pointer-rooted chained N_DOT) hid
|
||||
* in that gap. This harness closes it.
|
||||
*
|
||||
* bufiotest.ww carries its own `export fn main()` that drives the
|
||||
* @test fns and signals which case failed via the exit code, so
|
||||
* this file is just a thin wrapper — no @test scanning, no
|
||||
* synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/bufio/bufiotest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "bufio_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("bufio_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 999_random_run — execute the lib/math/random @test fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Placed at 999 (past the 980-989 stdlib-run sub-band) because
|
||||
* random number generation exercises many narrow-int cgen paths;
|
||||
* keeping it numbered at the tail makes its position in the test
|
||||
* list visually distinct from the small-utility cluster. Sibling
|
||||
* to 980_memio_run / 998_bufio_run. random_test.ww carries its
|
||||
* own `export fn main()` that drives the @test fns and signals
|
||||
* which case failed via the exit code, so this file is just a
|
||||
* thin wrapper — no @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/math/random/random_test.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "random_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("random_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user