From 8c86e8ecb49752163bda11a16414846afe32792f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 7 Aug 2026 23:03:21 +0900 Subject: [PATCH] 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. --- test/run | 643 --------------------- test/run_test.sh | 316 ---------- test/runww.ww | 351 ----------- test/wcc/630_let_global.c | 374 ------------ test/wcc/640_int_cast_signed.c | 223 ------- test/wcc/650_dot_chain.c | 430 -------------- test/wcc/660_field_signed.c | 372 ------------ test/wcc/670_frame_argcount.c | 368 ------------ test/wcc/680_arr_elem_field.c | 228 -------- test/wcc/681_arr_elem_field_write.c | 578 ------------------ test/wcc/690_amp_dot.c | 324 ----------- test/wcc/691_dot_slice_arg.c | 302 ---------- test/wcc/692_dot_str_chained_arg.c | 176 ------ test/wcc/693_dot_tagged_source.c | 390 ------------- test/wcc/694_tagged_store_intlit.c | 351 ----------- test/wcc/695_match_bind_struct.c | 298 ---------- test/wcc/709_localoff_scope.c | 350 ----------- test/wcc/711_arrlit_str_full.c | 292 ---------- test/wcc/713_struct_field_index.c | 225 ------- test/wcc/714_tagged_return_scratch.c | 299 ---------- test/wcc/715_tagged_widen_f64.c | 264 --------- test/wcc/729_empty_alloc_infer.c | 202 ------- test/wcc/742_parse_error.c | 121 ---- test/wcc/748_size_strategy_convergence.c | 286 --------- test/wcc/751_vararg_seq_percall.c | 191 ------ test/wcc/763_typeeq_fn_ast.c | 261 --------- test/wcc/768_io_types_run.c | 262 --------- test/wcc/780_fmt_mods_run.c | 345 ----------- test/wcc/781_fmt_compositions_run.c | 360 ------------ test/wcc/782_strict_package.c | 163 ------ test/wcc/788_type_value_shadow_run.c | 129 ----- test/wcc/832_tuple_elem_overlong.c | 303 ---------- test/wcc/834_tagged_arr_variant.c | 282 --------- test/wcc/835_nested_union_int_box.c | 283 --------- test/wcc/836_tagged_assignable_cluster.c | 259 --------- test/wcc/837_idcast_tagged_return.c | 185 ------ test/wcc/838_global_tagged_castinit.c | 211 ------- test/wcc/840_zeroinit_run.c | 260 --------- test/wcc/842_dup_main_reject.c | 270 --------- test/wcc/845_tuple_trailing_comma_reject.c | 224 ------- test/wcc/846_voidless_return_reject.c | 203 ------- test/wcc/847_const_reassign_reject.c | 201 ------- test/wcc/849_dupfield_reject.c | 215 ------- test/wcc/850_enum_reject.c | 211 ------- test/wcc/851_index_int_reject.c | 271 --------- test/wcc/852_variadic_body_reject.c | 224 ------- test/wcc/900_stdlib.c | 89 --- test/wcc/902_errno_run.c | 51 -- test/wcc/904_ascii_run.c | 49 -- test/wcc/904_tok_run.c | 53 -- test/wcc/905_nkname_run.c | 57 -- test/wcc/908_ftos_run.c | 55 -- test/wcc/909_stof_run.c | 55 -- test/wcc/922_strconv_int_run.c | 54 -- test/wcc/940_str_forrange_loopvar_run.c | 164 ------ test/wcc/944_alias_cgen_b6_run.c | 128 ---- test/wcc/944_alias_emit_b7_run.c | 161 ------ test/wcc/944_array_zeroinit_run.c | 222 ------- test/wcc/948_selfimport.c | 89 --- test/wcc/953_arrlit_slice_run.c | 132 ----- test/wcc/966_strings_run.c | 50 -- test/wcc/967_bytes_run.c | 49 -- test/wcc/968_utf8_run.c | 51 -- test/wcc/969_checked_run.c | 51 -- test/wcc/970_fmt_run.c | 53 -- test/wcc/971_log_run.c | 53 -- test/wcc/972_fnmatch_run.c | 53 -- test/wcc/973_shlex_run.c | 52 -- test/wcc/977_time_run.c | 49 -- test/wcc/978_intdiv_signed.c | 458 --------------- test/wcc/979_hex_run.c | 49 -- test/wcc/980_memio_run.c | 51 -- test/wcc/981_temp_run.c | 50 -- test/wcc/982_getopt_run.c | 52 -- test/wcc/983_base32_run.c | 51 -- test/wcc/984_base64_run.c | 51 -- test/wcc/985_adler32_run.c | 51 -- test/wcc/986_crc16_run.c | 51 -- test/wcc/987_crc32_run.c | 51 -- test/wcc/988_crc64_run.c | 51 -- test/wcc/989_allocalias_run.c | 149 ----- test/wcc/989_ampfncollide_run.c | 162 ------ test/wcc/989_arrlit_tail_zero_run.c | 155 ----- test/wcc/989_catA_f2_reject.c | 430 -------------- test/wcc/989_defercap_run.c | 174 ------ test/wcc/989_fmt_scanoverflow_run.c | 235 -------- test/wcc/989_intoverflow_reject.c | 226 -------- test/wcc/989_loopcap_run.c | 165 ------ test/wcc/989_nullableglobal_reject.c | 266 --------- test/wcc/989_path_run.c | 49 -- test/wcc/989_sha256_run.c | 52 -- test/wcc/989_siphash_run.c | 51 -- test/wcc/989_taggedcompoundderef_reject.c | 246 -------- test/wcc/989_taggedcompoundplace_reject.c | 316 ---------- test/wcc/989_test_filter.c | 149 ----- test/wcc/989_trycallcollide_run.c | 174 ------ test/wcc/989_unknowndecl_reject.c | 216 ------- test/wcc/989_zerotest_run.c | 145 ----- test/wcc/998_bufio_run.c | 58 -- test/wcc/999_random_run.c | 54 -- 100 files changed, 19334 deletions(-) delete mode 100755 test/run delete mode 100644 test/run_test.sh delete mode 100644 test/runww.ww delete mode 100644 test/wcc/630_let_global.c delete mode 100644 test/wcc/640_int_cast_signed.c delete mode 100644 test/wcc/650_dot_chain.c delete mode 100644 test/wcc/660_field_signed.c delete mode 100644 test/wcc/670_frame_argcount.c delete mode 100644 test/wcc/680_arr_elem_field.c delete mode 100644 test/wcc/681_arr_elem_field_write.c delete mode 100644 test/wcc/690_amp_dot.c delete mode 100644 test/wcc/691_dot_slice_arg.c delete mode 100644 test/wcc/692_dot_str_chained_arg.c delete mode 100644 test/wcc/693_dot_tagged_source.c delete mode 100644 test/wcc/694_tagged_store_intlit.c delete mode 100644 test/wcc/695_match_bind_struct.c delete mode 100644 test/wcc/709_localoff_scope.c delete mode 100644 test/wcc/711_arrlit_str_full.c delete mode 100644 test/wcc/713_struct_field_index.c delete mode 100644 test/wcc/714_tagged_return_scratch.c delete mode 100644 test/wcc/715_tagged_widen_f64.c delete mode 100644 test/wcc/729_empty_alloc_infer.c delete mode 100644 test/wcc/742_parse_error.c delete mode 100644 test/wcc/748_size_strategy_convergence.c delete mode 100644 test/wcc/751_vararg_seq_percall.c delete mode 100644 test/wcc/763_typeeq_fn_ast.c delete mode 100644 test/wcc/768_io_types_run.c delete mode 100644 test/wcc/780_fmt_mods_run.c delete mode 100644 test/wcc/781_fmt_compositions_run.c delete mode 100644 test/wcc/782_strict_package.c delete mode 100644 test/wcc/788_type_value_shadow_run.c delete mode 100644 test/wcc/832_tuple_elem_overlong.c delete mode 100644 test/wcc/834_tagged_arr_variant.c delete mode 100644 test/wcc/835_nested_union_int_box.c delete mode 100644 test/wcc/836_tagged_assignable_cluster.c delete mode 100644 test/wcc/837_idcast_tagged_return.c delete mode 100644 test/wcc/838_global_tagged_castinit.c delete mode 100644 test/wcc/840_zeroinit_run.c delete mode 100644 test/wcc/842_dup_main_reject.c delete mode 100644 test/wcc/845_tuple_trailing_comma_reject.c delete mode 100644 test/wcc/846_voidless_return_reject.c delete mode 100644 test/wcc/847_const_reassign_reject.c delete mode 100644 test/wcc/849_dupfield_reject.c delete mode 100644 test/wcc/850_enum_reject.c delete mode 100644 test/wcc/851_index_int_reject.c delete mode 100644 test/wcc/852_variadic_body_reject.c delete mode 100644 test/wcc/900_stdlib.c delete mode 100644 test/wcc/902_errno_run.c delete mode 100644 test/wcc/904_ascii_run.c delete mode 100644 test/wcc/904_tok_run.c delete mode 100644 test/wcc/905_nkname_run.c delete mode 100644 test/wcc/908_ftos_run.c delete mode 100644 test/wcc/909_stof_run.c delete mode 100644 test/wcc/922_strconv_int_run.c delete mode 100644 test/wcc/940_str_forrange_loopvar_run.c delete mode 100644 test/wcc/944_alias_cgen_b6_run.c delete mode 100644 test/wcc/944_alias_emit_b7_run.c delete mode 100644 test/wcc/944_array_zeroinit_run.c delete mode 100644 test/wcc/948_selfimport.c delete mode 100644 test/wcc/953_arrlit_slice_run.c delete mode 100644 test/wcc/966_strings_run.c delete mode 100644 test/wcc/967_bytes_run.c delete mode 100644 test/wcc/968_utf8_run.c delete mode 100644 test/wcc/969_checked_run.c delete mode 100644 test/wcc/970_fmt_run.c delete mode 100644 test/wcc/971_log_run.c delete mode 100644 test/wcc/972_fnmatch_run.c delete mode 100644 test/wcc/973_shlex_run.c delete mode 100644 test/wcc/977_time_run.c delete mode 100644 test/wcc/978_intdiv_signed.c delete mode 100644 test/wcc/979_hex_run.c delete mode 100644 test/wcc/980_memio_run.c delete mode 100644 test/wcc/981_temp_run.c delete mode 100644 test/wcc/982_getopt_run.c delete mode 100644 test/wcc/983_base32_run.c delete mode 100644 test/wcc/984_base64_run.c delete mode 100644 test/wcc/985_adler32_run.c delete mode 100644 test/wcc/986_crc16_run.c delete mode 100644 test/wcc/987_crc32_run.c delete mode 100644 test/wcc/988_crc64_run.c delete mode 100644 test/wcc/989_allocalias_run.c delete mode 100644 test/wcc/989_ampfncollide_run.c delete mode 100644 test/wcc/989_arrlit_tail_zero_run.c delete mode 100644 test/wcc/989_catA_f2_reject.c delete mode 100644 test/wcc/989_defercap_run.c delete mode 100644 test/wcc/989_fmt_scanoverflow_run.c delete mode 100644 test/wcc/989_intoverflow_reject.c delete mode 100644 test/wcc/989_loopcap_run.c delete mode 100644 test/wcc/989_nullableglobal_reject.c delete mode 100644 test/wcc/989_path_run.c delete mode 100644 test/wcc/989_sha256_run.c delete mode 100644 test/wcc/989_siphash_run.c delete mode 100644 test/wcc/989_taggedcompoundderef_reject.c delete mode 100644 test/wcc/989_taggedcompoundplace_reject.c delete mode 100644 test/wcc/989_test_filter.c delete mode 100644 test/wcc/989_trycallcollide_run.c delete mode 100644 test/wcc/989_unknowndecl_reject.c delete mode 100644 test/wcc/989_zerotest_run.c delete mode 100644 test/wcc/998_bufio_run.c delete mode 100644 test/wcc/999_random_run.c diff --git a/test/run b/test/run deleted file mode 100755 index 9cfe4e7b..00000000 --- a/test/run +++ /dev/null @@ -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 diff --git a/test/run_test.sh b/test/run_test.sh deleted file mode 100644 index ee30de1b..00000000 --- a/test/run_test.sh +++ /dev/null @@ -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' diff --git a/test/runww.ww b/test/runww.ww deleted file mode 100644 index e21c1511..00000000 --- a/test/runww.ww +++ /dev/null @@ -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//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 "" compilation must FAIL with on stderr -// //ww:compile compile must succeed (don't run) -// -// Invoke (pilot): `BIN=out/bin out/bin/ww run test/runww.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 ` 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 ...\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; -}; diff --git a/test/wcc/630_let_global.c b/test/wcc/630_let_global.c deleted file mode 100644 index 407ce091..00000000 --- a/test/wcc/630_let_global.c +++ /dev/null @@ -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 -#include -#include -#include -#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; -} diff --git a/test/wcc/640_int_cast_signed.c b/test/wcc/640_int_cast_signed.c deleted file mode 100644 index b2e7bb37..00000000 --- a/test/wcc/640_int_cast_signed.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/650_dot_chain.c b/test/wcc/650_dot_chain.c deleted file mode 100644 index faf7b556..00000000 --- a/test/wcc/650_dot_chain.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/660_field_signed.c b/test/wcc/660_field_signed.c deleted file mode 100644 index 659127d6..00000000 --- a/test/wcc/660_field_signed.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/670_frame_argcount.c b/test/wcc/670_frame_argcount.c deleted file mode 100644 index 5e0ac6cd..00000000 --- a/test/wcc/670_frame_argcount.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/680_arr_elem_field.c b/test/wcc/680_arr_elem_field.c deleted file mode 100644 index 8744db66..00000000 --- a/test/wcc/680_arr_elem_field.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/681_arr_elem_field_write.c b/test/wcc/681_arr_elem_field_write.c deleted file mode 100644 index 2f029088..00000000 --- a/test/wcc/681_arr_elem_field_write.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/690_amp_dot.c b/test/wcc/690_amp_dot.c deleted file mode 100644 index 048881e8..00000000 --- a/test/wcc/690_amp_dot.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/691_dot_slice_arg.c b/test/wcc/691_dot_slice_arg.c deleted file mode 100644 index e9c4ff66..00000000 --- a/test/wcc/691_dot_slice_arg.c +++ /dev/null @@ -1,302 +0,0 @@ -/* - * 691_dot_slice_arg — pass `.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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/692_dot_str_chained_arg.c b/test/wcc/692_dot_str_chained_arg.c deleted file mode 100644 index 35383919..00000000 --- a/test/wcc/692_dot_str_chained_arg.c +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 692_dot_str_chained_arg — pass `.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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/693_dot_tagged_source.c b/test/wcc/693_dot_tagged_source.c deleted file mode 100644 index 19ea8f13..00000000 --- a/test/wcc/693_dot_tagged_source.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/694_tagged_store_intlit.c b/test/wcc/694_tagged_store_intlit.c deleted file mode 100644 index 8fdad646..00000000 --- a/test/wcc/694_tagged_store_intlit.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/695_match_bind_struct.c b/test/wcc/695_match_bind_struct.c deleted file mode 100644 index e115251b..00000000 --- a/test/wcc/695_match_bind_struct.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/709_localoff_scope.c b/test/wcc/709_localoff_scope.c deleted file mode 100644 index ddf68c2e..00000000 --- a/test/wcc/709_localoff_scope.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/711_arrlit_str_full.c b/test/wcc/711_arrlit_str_full.c deleted file mode 100644 index 5b7252d3..00000000 --- a/test/wcc/711_arrlit_str_full.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/713_struct_field_index.c b/test/wcc/713_struct_field_index.c deleted file mode 100644 index 514ac38e..00000000 --- a/test/wcc/713_struct_field_index.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/714_tagged_return_scratch.c b/test/wcc/714_tagged_return_scratch.c deleted file mode 100644 index f76f0079..00000000 --- a/test/wcc/714_tagged_return_scratch.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/715_tagged_widen_f64.c b/test/wcc/715_tagged_widen_f64.c deleted file mode 100644 index 2e1f4db3..00000000 --- a/test/wcc/715_tagged_widen_f64.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/729_empty_alloc_infer.c b/test/wcc/729_empty_alloc_infer.c deleted file mode 100644 index 6047b8ab..00000000 --- a/test/wcc/729_empty_alloc_infer.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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 .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_.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; -} diff --git a/test/wcc/742_parse_error.c b/test/wcc/742_parse_error.c deleted file mode 100644 index 50694076..00000000 --- a/test/wcc/742_parse_error.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/748_size_strategy_convergence.c b/test/wcc/748_size_strategy_convergence.c deleted file mode 100644 index cc154ca6..00000000 --- a/test/wcc/748_size_strategy_convergence.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/751_vararg_seq_percall.c b/test/wcc/751_vararg_seq_percall.c deleted file mode 100644 index a6e1dddf..00000000 --- a/test/wcc/751_vararg_seq_percall.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/763_typeeq_fn_ast.c b/test/wcc/763_typeeq_fn_ast.c deleted file mode 100644 index 6041b07e..00000000 --- a/test/wcc/763_typeeq_fn_ast.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/768_io_types_run.c b/test/wcc/768_io_types_run.c deleted file mode 100644 index c2f40a05..00000000 --- a/test/wcc/768_io_types_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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 .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; -} diff --git a/test/wcc/780_fmt_mods_run.c b/test/wcc/780_fmt_mods_run.c deleted file mode 100644 index 084d9f3e..00000000 --- a/test/wcc/780_fmt_mods_run.c +++ /dev/null @@ -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 '_' — 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 -#include -#include -#include -#include - -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 - * .{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; -} diff --git a/test/wcc/781_fmt_compositions_run.c b/test/wcc/781_fmt_compositions_run.c deleted file mode 100644 index 609f709b..00000000 --- a/test/wcc/781_fmt_compositions_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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 .{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; -} diff --git a/test/wcc/782_strict_package.c b/test/wcc/782_strict_package.c deleted file mode 100644 index 21d52855..00000000 --- a/test/wcc/782_strict_package.c +++ /dev/null @@ -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 `) 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 - * does not match import path

"). 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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/788_type_value_shadow_run.c b/test/wcc/788_type_value_shadow_run.c deleted file mode 100644 index 63d38e2d..00000000 --- a/test/wcc/788_type_value_shadow_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/832_tuple_elem_overlong.c b/test/wcc/832_tuple_elem_overlong.c deleted file mode 100644 index 65c7f9aa..00000000 --- a/test/wcc/832_tuple_elem_overlong.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/834_tagged_arr_variant.c b/test/wcc/834_tagged_arr_variant.c deleted file mode 100644 index e9046503..00000000 --- a/test/wcc/834_tagged_arr_variant.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/835_nested_union_int_box.c b/test/wcc/835_nested_union_int_box.c deleted file mode 100644 index 5624524a..00000000 --- a/test/wcc/835_nested_union_int_box.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/836_tagged_assignable_cluster.c b/test/wcc/836_tagged_assignable_cluster.c deleted file mode 100644 index 32867b62..00000000 --- a/test/wcc/836_tagged_assignable_cluster.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/837_idcast_tagged_return.c b/test/wcc/837_idcast_tagged_return.c deleted file mode 100644 index b3d7ac48..00000000 --- a/test/wcc/837_idcast_tagged_return.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/838_global_tagged_castinit.c b/test/wcc/838_global_tagged_castinit.c deleted file mode 100644 index 0d68f5cf..00000000 --- a/test/wcc/838_global_tagged_castinit.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/840_zeroinit_run.c b/test/wcc/840_zeroinit_run.c deleted file mode 100644 index aa99c0e0..00000000 --- a/test/wcc/840_zeroinit_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/842_dup_main_reject.c b/test/wcc/842_dup_main_reject.c deleted file mode 100644 index 7073d0b7..00000000 --- a/test/wcc/842_dup_main_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/845_tuple_trailing_comma_reject.c b/test/wcc/845_tuple_trailing_comma_reject.c deleted file mode 100644 index a0353623..00000000 --- a/test/wcc/845_tuple_trailing_comma_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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 .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; -} diff --git a/test/wcc/846_voidless_return_reject.c b/test/wcc/846_voidless_return_reject.c deleted file mode 100644 index d4d23290..00000000 --- a/test/wcc/846_voidless_return_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/847_const_reassign_reject.c b/test/wcc/847_const_reassign_reject.c deleted file mode 100644 index be462875..00000000 --- a/test/wcc/847_const_reassign_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/849_dupfield_reject.c b/test/wcc/849_dupfield_reject.c deleted file mode 100644 index 6a676cbb..00000000 --- a/test/wcc/849_dupfield_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/850_enum_reject.c b/test/wcc/850_enum_reject.c deleted file mode 100644 index a1262c35..00000000 --- a/test/wcc/850_enum_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/851_index_int_reject.c b/test/wcc/851_index_int_reject.c deleted file mode 100644 index b27c527e..00000000 --- a/test/wcc/851_index_int_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/852_variadic_body_reject.c b/test/wcc/852_variadic_body_reject.c deleted file mode 100644 index 3736682d..00000000 --- a/test/wcc/852_variadic_body_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/900_stdlib.c b/test/wcc/900_stdlib.c deleted file mode 100644 index 8a9dd951..00000000 --- a/test/wcc/900_stdlib.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/902_errno_run.c b/test/wcc/902_errno_run.c deleted file mode 100644 index bed10e6f..00000000 --- a/test/wcc/902_errno_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/904_ascii_run.c b/test/wcc/904_ascii_run.c deleted file mode 100644 index fbe4a293..00000000 --- a/test/wcc/904_ascii_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/904_tok_run.c b/test/wcc/904_tok_run.c deleted file mode 100644 index 4b5f1181..00000000 --- a/test/wcc/904_tok_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/905_nkname_run.c b/test/wcc/905_nkname_run.c deleted file mode 100644 index 2b3001d1..00000000 --- a/test/wcc/905_nkname_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/908_ftos_run.c b/test/wcc/908_ftos_run.c deleted file mode 100644 index b4c72e70..00000000 --- a/test/wcc/908_ftos_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/909_stof_run.c b/test/wcc/909_stof_run.c deleted file mode 100644 index e7cd92bd..00000000 --- a/test/wcc/909_stof_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/922_strconv_int_run.c b/test/wcc/922_strconv_int_run.c deleted file mode 100644 index 34947f7d..00000000 --- a/test/wcc/922_strconv_int_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/940_str_forrange_loopvar_run.c b/test/wcc/940_str_forrange_loopvar_run.c deleted file mode 100644 index 292de5ff..00000000 --- a/test/wcc/940_str_forrange_loopvar_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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; -} diff --git a/test/wcc/944_alias_cgen_b6_run.c b/test/wcc/944_alias_cgen_b6_run.c deleted file mode 100644 index aabb2d31..00000000 --- a/test/wcc/944_alias_cgen_b6_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/944_alias_emit_b7_run.c b/test/wcc/944_alias_emit_b7_run.c deleted file mode 100644 index 6900f625..00000000 --- a/test/wcc/944_alias_emit_b7_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/944_array_zeroinit_run.c b/test/wcc/944_array_zeroinit_run.c deleted file mode 100644 index 8c45076e..00000000 --- a/test/wcc/944_array_zeroinit_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/948_selfimport.c b/test/wcc/948_selfimport.c deleted file mode 100644 index f3e25079..00000000 --- a/test/wcc/948_selfimport.c +++ /dev/null @@ -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 -#include -#include -#include - -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 — ` -o /dev/null` must exit nonzero and print - * 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; -} diff --git a/test/wcc/953_arrlit_slice_run.c b/test/wcc/953_arrlit_slice_run.c deleted file mode 100644 index 82e6ace9..00000000 --- a/test/wcc/953_arrlit_slice_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/966_strings_run.c b/test/wcc/966_strings_run.c deleted file mode 100644 index df0fe8ea..00000000 --- a/test/wcc/966_strings_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/967_bytes_run.c b/test/wcc/967_bytes_run.c deleted file mode 100644 index bc3e418d..00000000 --- a/test/wcc/967_bytes_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/968_utf8_run.c b/test/wcc/968_utf8_run.c deleted file mode 100644 index 448e6ad3..00000000 --- a/test/wcc/968_utf8_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/969_checked_run.c b/test/wcc/969_checked_run.c deleted file mode 100644 index d22e8f57..00000000 --- a/test/wcc/969_checked_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/970_fmt_run.c b/test/wcc/970_fmt_run.c deleted file mode 100644 index 73e8f7b5..00000000 --- a/test/wcc/970_fmt_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/971_log_run.c b/test/wcc/971_log_run.c deleted file mode 100644 index 0969126a..00000000 --- a/test/wcc/971_log_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/972_fnmatch_run.c b/test/wcc/972_fnmatch_run.c deleted file mode 100644 index 19689878..00000000 --- a/test/wcc/972_fnmatch_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/973_shlex_run.c b/test/wcc/973_shlex_run.c deleted file mode 100644 index 8f48febf..00000000 --- a/test/wcc/973_shlex_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/977_time_run.c b/test/wcc/977_time_run.c deleted file mode 100644 index d4794dc8..00000000 --- a/test/wcc/977_time_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/978_intdiv_signed.c b/test/wcc/978_intdiv_signed.c deleted file mode 100644 index 75c52ae6..00000000 --- a/test/wcc/978_intdiv_signed.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#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 '.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; -} diff --git a/test/wcc/979_hex_run.c b/test/wcc/979_hex_run.c deleted file mode 100644 index 4548ba14..00000000 --- a/test/wcc/979_hex_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/980_memio_run.c b/test/wcc/980_memio_run.c deleted file mode 100644 index 9813e080..00000000 --- a/test/wcc/980_memio_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/981_temp_run.c b/test/wcc/981_temp_run.c deleted file mode 100644 index 2aad76c0..00000000 --- a/test/wcc/981_temp_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/982_getopt_run.c b/test/wcc/982_getopt_run.c deleted file mode 100644 index 90d6c8e4..00000000 --- a/test/wcc/982_getopt_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/983_base32_run.c b/test/wcc/983_base32_run.c deleted file mode 100644 index ec9dc814..00000000 --- a/test/wcc/983_base32_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/984_base64_run.c b/test/wcc/984_base64_run.c deleted file mode 100644 index fb7a0836..00000000 --- a/test/wcc/984_base64_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/985_adler32_run.c b/test/wcc/985_adler32_run.c deleted file mode 100644 index 2cc6584c..00000000 --- a/test/wcc/985_adler32_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/986_crc16_run.c b/test/wcc/986_crc16_run.c deleted file mode 100644 index 29a07036..00000000 --- a/test/wcc/986_crc16_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/987_crc32_run.c b/test/wcc/987_crc32_run.c deleted file mode 100644 index aa89ed29..00000000 --- a/test/wcc/987_crc32_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/988_crc64_run.c b/test/wcc/988_crc64_run.c deleted file mode 100644 index 1af08831..00000000 --- a/test/wcc/988_crc64_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/989_allocalias_run.c b/test/wcc/989_allocalias_run.c deleted file mode 100644 index a8d1f206..00000000 --- a/test/wcc/989_allocalias_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_ampfncollide_run.c b/test/wcc/989_ampfncollide_run.c deleted file mode 100644 index ba4ab5e7..00000000 --- a/test/wcc/989_ampfncollide_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_arrlit_tail_zero_run.c b/test/wcc/989_arrlit_tail_zero_run.c deleted file mode 100644 index b0a0f0fe..00000000 --- a/test/wcc/989_arrlit_tail_zero_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_catA_f2_reject.c b/test/wcc/989_catA_f2_reject.c deleted file mode 100644 index a63d8747..00000000 --- a/test/wcc/989_catA_f2_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_defercap_run.c b/test/wcc/989_defercap_run.c deleted file mode 100644 index 04af64cb..00000000 --- a/test/wcc/989_defercap_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_fmt_scanoverflow_run.c b/test/wcc/989_fmt_scanoverflow_run.c deleted file mode 100644 index c0764f57..00000000 --- a/test/wcc/989_fmt_scanoverflow_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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 .{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; -} diff --git a/test/wcc/989_intoverflow_reject.c b/test/wcc/989_intoverflow_reject.c deleted file mode 100644 index 1fcf894e..00000000 --- a/test/wcc/989_intoverflow_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_loopcap_run.c b/test/wcc/989_loopcap_run.c deleted file mode 100644 index 24e1efd2..00000000 --- a/test/wcc/989_loopcap_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_nullableglobal_reject.c b/test/wcc/989_nullableglobal_reject.c deleted file mode 100644 index 296e4847..00000000 --- a/test/wcc/989_nullableglobal_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_path_run.c b/test/wcc/989_path_run.c deleted file mode 100644 index c14c9c91..00000000 --- a/test/wcc/989_path_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/989_sha256_run.c b/test/wcc/989_sha256_run.c deleted file mode 100644 index 63f29c9a..00000000 --- a/test/wcc/989_sha256_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/989_siphash_run.c b/test/wcc/989_siphash_run.c deleted file mode 100644 index a27d6067..00000000 --- a/test/wcc/989_siphash_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/989_taggedcompoundderef_reject.c b/test/wcc/989_taggedcompoundderef_reject.c deleted file mode 100644 index edf5a373..00000000 --- a/test/wcc/989_taggedcompoundderef_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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 .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; -} diff --git a/test/wcc/989_taggedcompoundplace_reject.c b/test/wcc/989_taggedcompoundplace_reject.c deleted file mode 100644 index 12fe84f0..00000000 --- a/test/wcc/989_taggedcompoundplace_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_test_filter.c b/test/wcc/989_test_filter.c deleted file mode 100644 index 1253a5a0..00000000 --- a/test/wcc/989_test_filter.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 989_test_filter — the `ww test [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 -#include -#include -#include -#include - -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 "/ test [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; -} diff --git a/test/wcc/989_trycallcollide_run.c b/test/wcc/989_trycallcollide_run.c deleted file mode 100644 index 1a09555a..00000000 --- a/test/wcc/989_trycallcollide_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_unknowndecl_reject.c b/test/wcc/989_unknowndecl_reject.c deleted file mode 100644 index e2dea5df..00000000 --- a/test/wcc/989_unknowndecl_reject.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/989_zerotest_run.c b/test/wcc/989_zerotest_run.c deleted file mode 100644 index 133e514a..00000000 --- a/test/wcc/989_zerotest_run.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 989_zerotest_run (#64, F15 c7) — `ww test

` 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 -#include -#include -#include -#include -#include - -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); } - } -} - -/* ` test ` 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; -} diff --git a/test/wcc/998_bufio_run.c b/test/wcc/998_bufio_run.c deleted file mode 100644 index 07c77962..00000000 --- a/test/wcc/998_bufio_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/999_random_run.c b/test/wcc/999_random_run.c deleted file mode 100644 index 60b348ef..00000000 --- a/test/wcc/999_random_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -}