diff --git a/Makefile b/Makefile index 36650fb0..73a357eb 100644 --- a/Makefile +++ b/Makefile @@ -8,14 +8,8 @@ PREFIX ?= /usr/local CC ?= cc AR ?= ar -# Parallelise the build by default. The C-compile DAG and the five -# wwstage builds write disjoint outputs (each .o is distinct; each -# wwstage tool's side files land at its own selfhost/cmd//main.* -# stem), so -j is order-independent. test/run's Phase-2 byte-id gates -# (950/990-997) are a single serial recipe — MAKEFLAGS -j does not -# reach inside them. Override with `make -j1` if needed. -NPROC ?= $(shell nproc 2>/dev/null || echo 4) -MAKEFLAGS += -j$(NPROC) +# Fixture-cell concurrency is separate from caller-owned Make scheduling. +JOBS ?= 1 # Wrap the C compiler with ccache when present — content-addressed, so # byte-identical to plain cc; just skips recompiling unchanged TUs. @@ -57,6 +51,55 @@ RT_S = rt/start.s rt/syscall.s rt/alloc.s rt/streq.s rt/abort.s RT_WW = rt/ensure.ww rt/malloc.ww RT_OBJ = $(RT_S:rt/%.s=$(OBJ)/rt/%.o) $(RT_WW:rt/%.ww=$(OBJ)/rt/%.o) +WWTEST_BIN = $(BIN)/wwtest +WWTEST_SRC = cmd/wwtest/wwtest.ww \ + internal/wwpackage/package.ww lib/os/exec/exec.ww \ + lib/ascii/ascii.ww lib/bytes/bytes.ww \ + lib/encoding/utf8/utf8.ww lib/os/os.ww lib/rt/malloc.ww \ + lib/strconv/strconv.ww \ + lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \ + lib/types/types.ww + +WWFIXTURE_BIN = $(BIN)/wwfixture +WWFIXTURE_SRC = cmd/wwfixture/wwfixture.ww \ + internal/wwfixture/command.ww internal/wwfixture/corpus.ww \ + internal/wwfixture/protocol.ww internal/wwfixture/run.ww \ + internal/wwfixture/types.ww lib/os/exec/exec.ww \ + lib/ascii/ascii.ww lib/bytes/bytes.ww \ + lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \ + lib/encoding/hex/hex.ww lib/encoding/utf8/utf8.ww \ + lib/endian/endian.ww lib/errors/errors.ww lib/fmt/fmt.ww \ + lib/fnmatch/fnmatch.ww lib/hash/hash.ww \ + lib/io/io.ww lib/io/stream.ww lib/io/types.ww \ + lib/math/floats.ww lib/math/math.ww lib/memio/memio.ww \ + lib/os/os.ww lib/rt/malloc.ww \ + lib/strconv/decimal.ww lib/strconv/ftos.ww \ + lib/strconv/ftos_data.ww lib/strconv/stof.ww \ + lib/strconv/stof_data.ww lib/strconv/strconv.ww \ + lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \ + lib/types/types.ww + +WWFIXTURE_BUILD_TOOLS = $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a +WWFIXTURE_TOOLS = $(WWFIXTURE_BUILD_TOOLS) $(BIN)/w6c_ww \ + $(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/ww_ww + +EXEC_TEST_SRC = test/wwfixture/process/main.ww \ + lib/os/exec/exec.ww lib/os/os.ww lib/rt/malloc.ww \ + lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \ + lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \ + lib/types/types.ww +EXEC_TEST_C = $(BIN)/test_exec +EXEC_TEST_WW = $(BIN)/test_exec_ww + +PACKAGE_TEST_BIN = $(BIN)/test_package +PACKAGE_TEST_SRC = test/package/package_test.ww lib/os/exec/exec.ww \ + lib/test/run.ww lib/fnmatch/fnmatch.ww \ + lib/ascii/ascii.ww lib/bytes/bytes.ww \ + lib/encoding/utf8/utf8.ww lib/os/os.ww lib/rt/malloc.ww \ + lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \ + lib/types/types.ww + # Cstage: one-time C bootstrap (see BOOTSTRAP.md). Built by `cc`. # Goes away at v1.0, replaced by a checked-in stage-0 binary. CSTAGE_BINS = $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(BIN)/wwdump @@ -66,10 +109,10 @@ CSTAGE_BINS = $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(BIN)/wwdump # (tests 990-994). WWSTAGE_BINS = $(BIN)/wwdump_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/ww_ww -BINS = $(CSTAGE_BINS) $(WWSTAGE_BINS) +BINS = $(CSTAGE_BINS) $(WWSTAGE_BINS) $(WWTEST_BIN) LIBS = $(LIB)/libwcc.a $(LIB)/libwwrt.a -all: cstage wwstage +all: cstage wwstage $(WWTEST_BIN) cstage: $(CSTAGE_BINS) $(LIBS) wwstage: $(WWSTAGE_BINS) @@ -248,2666 +291,379 @@ $(OBJ)/rt/%.o: $(OBJ)/rt/%.s $(BIN)/w6a | $(OBJ)/rt $(LIB)/libwwrt.a: $(RT_OBJ) | $(LIB) $(AR) rcs $@ $(RT_OBJ) +$(WWTEST_BIN): $(WWTEST_SRC) $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \ + $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) + @mkdir -p $(BIN)/wwtest.d + @d=$$(mktemp -d "$(CURDIR)/$(BIN)/wwtest.d/build.XXXXXX") || exit 1; \ + rc=0; published=0; \ + (cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \ + -I $(CURDIR)/internal $(CURDIR)/cmd/wwtest/wwtest.ww) || rc=$$?; \ + rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \ + if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \ + if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \ + exit $$rc + +wwfixture: $(WWFIXTURE_BIN) + +test-wwfixture-process: $(EXEC_TEST_C) $(EXEC_TEST_WW) + $(CURDIR)/$(EXEC_TEST_C) + $(CURDIR)/$(EXEC_TEST_WW) + +test-wwfixture: wwfixture test-wwfixture-process + sh test/wwfixture/integration.sh + +$(WWFIXTURE_BIN): $(WWFIXTURE_SRC) $(WWFIXTURE_BUILD_TOOLS) | $(BIN) + @mkdir -p $(BIN)/wwfixture.d + @d=$$(mktemp -d "$(CURDIR)/$(BIN)/wwfixture.d/build.XXXXXX") || exit 1; \ + rc=0; published=0; \ + (cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \ + -I $(CURDIR)/internal $(CURDIR)/cmd/wwfixture/wwfixture.ww) || rc=$$?; \ + rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \ + if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \ + if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \ + exit $$rc + +$(EXEC_TEST_C): $(EXEC_TEST_SRC) $(WWFIXTURE_TOOLS) | $(BIN) + @mkdir -p $(BIN)/test_exec.d + @d=$$(mktemp -d "$(CURDIR)/$(BIN)/test_exec.d/build.XXXXXX") || exit 1; \ + rc=0; published=0; \ + (cd "$$d" && $(CURDIR)/$(BIN)/ww build -o main \ + -I $(CURDIR)/internal $(CURDIR)/test/wwfixture/process/main.ww) || rc=$$?; \ + rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \ + if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \ + if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \ + exit $$rc + +$(EXEC_TEST_WW): $(EXEC_TEST_SRC) $(WWFIXTURE_TOOLS) | $(BIN) + @mkdir -p $(BIN)/test_exec_ww.d + @d=$$(mktemp -d "$(CURDIR)/$(BIN)/test_exec_ww.d/build.XXXXXX") || exit 1; \ + rc=0; published=0; \ + (cd "$$d" && $(CURDIR)/$(BIN)/ww_ww build -o main \ + -I $(CURDIR)/internal $(CURDIR)/test/wwfixture/process/main.ww) || rc=$$?; \ + rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \ + if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \ + if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \ + exit $$rc + +$(PACKAGE_TEST_BIN): $(PACKAGE_TEST_SRC) $(WWTEST_BIN) \ + $(BIN)/ww $(BIN)/ww_ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + @mkdir -p $(BIN)/test_package.d + @d=$$(mktemp -d "$(CURDIR)/$(BIN)/test_package.d/build.XXXXXX") || exit 1; \ + rc=0; published=0; \ + (cd "$$d" && $(CURDIR)/$(BIN)/ww test -c -o main \ + -I $(CURDIR)/internal $(CURDIR)/test/package/package_test.ww) || rc=$$?; \ + rm -rf -- "$$d/main.sepwork" || { echo "$@: scratch cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then mv "$$d/main" "$(CURDIR)/$@" && published=1 || rc=$$?; fi; \ + if [ $$published -eq 0 ]; then rm -f -- "$$d/main" || { echo "$@: output cleanup failed" >&2; if [ $$rc -eq 0 ]; then rc=1; fi; }; fi; \ + if ! rmdir "$$d"; then echo "$@: workspace cleanup failed" >&2; if [ $$published -eq 1 ]; then rm -f -- "$(CURDIR)/$@" || echo "$@: published-output cleanup failed" >&2; fi; if [ $$rc -eq 0 ]; then rc=1; fi; fi; \ + exit $$rc + +test-package: all $(PACKAGE_TEST_BIN) + @WW_PACKAGE_REPO=$(CURDIR) $(CURDIR)/$(PACKAGE_TEST_BIN) + # ---- directories ------------------------------------------------------- $(BIN) $(LIB) $(OBJ)/wcc $(OBJ)/ww $(OBJ)/wwdump $(OBJ)/w6c $(OBJ)/w6a $(OBJ)/w6l $(OBJ)/rt: mkdir -p $@ # ---- tests ------------------------------------------------------------- -# Each phase adds a $(BIN)/test_ target; the runner walks them. -TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ - $(BIN)/test_w6c $(BIN)/test_w6a $(BIN)/test_w6a_parsenum \ - $(BIN)/test_dataw $(BIN)/test_datar \ - $(BIN)/test_w6l $(BIN)/test_data_link \ - $(BIN)/test_w6l_manyflags \ - $(BIN)/test_arch \ - $(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \ - $(BIN)/test_selfimport $(BIN)/test_missingpkg \ - $(BIN)/test_driver_flagargs \ - $(BIN)/test_let_global $(BIN)/test_def_neg_global \ - $(BIN)/test_def_const_fold \ - $(BIN)/test_int_cast_signed $(BIN)/test_uniesc_run $(BIN)/test_dot_chain \ - $(BIN)/test_amp_dot $(BIN)/test_arr_elem_field \ - $(BIN)/test_arr_elem_field_write \ - $(BIN)/test_arr_enum_elem \ - $(BIN)/test_arr_strslice_elem \ - $(BIN)/test_struct_packed \ - $(BIN)/test_arr_tagged_elem \ - $(BIN)/test_dup_main_reject \ - $(BIN)/test_tuple_trailing_comma_reject \ - $(BIN)/test_voidless_return_reject \ - $(BIN)/test_const_reassign_reject \ - $(BIN)/test_dupfield_reject \ - $(BIN)/test_enum_reject \ - $(BIN)/test_variadic_body_reject \ - $(BIN)/test_index_int_reject \ - $(BIN)/test_size_untyped_int \ - $(BIN)/test_tagged_staticinit \ - $(BIN)/test_arr_infer_len \ - $(BIN)/test_arr_cap_reject \ - $(BIN)/test_tagged_subset_reject \ - $(BIN)/test_callarg_typecheck \ - $(BIN)/test_catA_f2_reject \ - $(BIN)/test_intoverflow_reject \ - $(BIN)/test_unknowndecl_reject \ - $(BIN)/test_nullableglobal_reject \ - $(BIN)/test_taggedcompoundderef_reject \ - $(BIN)/test_taggedcompoundplace_reject \ - $(BIN)/test_libprecond_abort \ - $(BIN)/test_ffivariadic_run \ - $(BIN)/test_m1mangle_run \ - $(BIN)/test_m1mangle_sym \ - $(BIN)/test_m1usehint_run \ - $(BIN)/test_m1union_run \ - $(BIN)/test_structlocal_frame \ - $(BIN)/test_arrlit_tail_zero_run \ - $(BIN)/test_allocalias_run \ - $(BIN)/test_slttypepref_run \ - $(BIN)/test_defercap_run \ - $(BIN)/test_loopcap_run \ - $(BIN)/test_enumcap_run \ - $(BIN)/test_wwdumpgate_run \ - $(BIN)/test_datargate_run \ - $(BIN)/test_dynentry_run \ - $(BIN)/test_zerotest_run \ - $(BIN)/test_ampfncollide_run \ - $(BIN)/test_trycallcollide_run \ - $(BIN)/test_fnptrcollide_run \ - $(BIN)/test_arr_ptr_global \ - $(BIN)/test_def_arr_infer_len \ - $(BIN)/test_def_arr_len \ - $(BIN)/test_def_str_table \ - $(BIN)/test_arr_zero_vs_infer \ - $(BIN)/test_def_str_index_reject \ - $(BIN)/test_struct_global_byval_arg \ - $(BIN)/test_inferred_global_let \ - $(BIN)/test_inferred_float_global \ - $(BIN)/test_errfirst_union_try \ - $(BIN)/test_alias_tuple_coerce \ - $(BIN)/test_str_eq \ - $(BIN)/test_overlong_arrlit \ - $(BIN)/test_tuple_elem_overlong \ - $(BIN)/test_tagged_arr_variant \ - $(BIN)/test_nested_union_int_box \ - $(BIN)/test_tagged_assignable_cluster \ - $(BIN)/test_idcast_tagged_return \ - $(BIN)/test_global_tagged_castinit \ - $(BIN)/test_xmod_nominal_typeeqast \ - $(BIN)/test_inferred_array_global \ - $(BIN)/test_slice_str_global_arg \ - $(BIN)/test_slice_str_global_zero \ - $(BIN)/test_slice_literal_global \ - $(BIN)/test_global_arr_elem_field \ - $(BIN)/test_structlit_slice_field \ - $(BIN)/test_arrglob_nodup_run \ - $(BIN)/test_globptr_field_store_run \ - $(BIN)/test_globptr_field_read_run \ - $(BIN)/test_dot_str_chained_arg \ - $(BIN)/test_dot_slice_arg \ - $(BIN)/test_dot_tagged_source \ - $(BIN)/test_tagged_store_intlit \ - $(BIN)/test_match_bind_struct \ - $(BIN)/test_modtype_leaf_collision \ - $(BIN)/test_samemod_prefer \ - $(BIN)/test_cgreturn_struct \ - $(BIN)/test_cgassign_struct \ - $(BIN)/test_dot_explicit_deref \ - $(BIN)/test_nested_structlit \ - $(BIN)/test_dot_structlit \ - $(BIN)/test_nested_call_rhs \ - $(BIN)/test_fnlabel_mangle \ - $(BIN)/test_cgreturn_variant_zero \ - $(BIN)/test_arrlit_str_full \ - $(BIN)/test_redecl \ - $(BIN)/test_empty_alloc_infer \ - $(BIN)/test_struct_field_index \ - $(BIN)/test_tagged_return_scratch \ - $(BIN)/test_tagged_widen_f64 \ - $(BIN)/test_match_spill_pointer_payload \ - $(BIN)/test_match_field_inplace \ - $(BIN)/test_match_global_field \ - $(BIN)/test_struct_byval_param \ - $(BIN)/test_struct_multi_return_scratch \ - $(BIN)/test_signed_data_emit \ - $(BIN)/test_tagged_call_arg \ - $(BIN)/test_sret_struct_return \ - $(BIN)/test_tagged_sret_run \ - $(BIN)/test_tagged_memarg_run \ - $(BIN)/test_tagscr_sizes_run \ - $(BIN)/test_global_sret_run \ - $(BIN)/test_sret_narrow_field \ - $(BIN)/test_match_slice_variant \ - $(BIN)/test_array_zeroinit_run \ - $(BIN)/test_alias_accept_run \ - $(BIN)/test_alias_cgen_b5_run \ - $(BIN)/test_alias_cgen_b6_run \ - $(BIN)/test_alias_emit_b7_run \ - $(BIN)/test_variant_chain_b95_run \ - $(BIN)/test_peellint_gate \ - $(BIN)/test_xmod_fnptr_const_run \ - $(BIN)/test_errtype_compare \ - $(BIN)/test_intbinop_mismatch \ - $(BIN)/test_str_forrange_loopvar_run \ - $(BIN)/test_composite_call_arg \ - $(BIN)/test_letdecl_zeroinit \ - $(BIN)/test_nested_if_labels \ - $(BIN)/test_alias_leaf_collision \ - $(BIN)/test_modcall_widen_slice \ - $(BIN)/test_match_4arm_cross_module \ - $(BIN)/test_match_4arm_cross_module_run \ - $(BIN)/test_free_noop_run \ - $(BIN)/test_enum_modshadow \ - $(BIN)/test_struct_modshadow \ - $(BIN)/test_def_modshadow \ - $(BIN)/test_cstage_label_ssot \ - $(BIN)/test_direnum \ - $(BIN)/test_module_decl \ - $(BIN)/test_chained_index \ - $(BIN)/test_chained_write \ - $(BIN)/test_dotbase_chained \ - $(BIN)/test_parse_error \ - $(BIN)/test_variadic_pack \ - $(BIN)/test_letcopy_struct \ - $(BIN)/test_fnparams_bare_leaf_shadow \ - $(BIN)/test_fnret_bare_leaf_shadow \ - $(BIN)/test_fnret34_modshadow \ - $(BIN)/test_strdef_inline \ - $(BIN)/test_def_modqual_modshadow \ - $(BIN)/test_size_strategy_convergence \ - $(BIN)/test_sumtype_forward \ - $(BIN)/test_mklabel_modscoped \ - $(BIN)/test_vararg_seq_percall \ - $(BIN)/test_modparam_callee \ - $(BIN)/test_convwrap_audit \ - $(BIN)/test_slice_of_slice_index \ - $(BIN)/test_amp_dot_idx \ - $(BIN)/test_alias_chain_unwrap \ - $(BIN)/test_letbind_void_bang_void \ - $(BIN)/test_cgalloc_str_field \ - $(BIN)/test_param_shadow_mod \ - $(BIN)/test_localoff_scope \ - $(BIN)/test_cast_enum_movl \ - $(BIN)/test_check_enum_fold \ - $(BIN)/test_def_widen_const \ - $(BIN)/test_inferred_struct_arg_push \ - $(BIN)/test_struct_abi_size \ - $(BIN)/test_typeeq_fn_ast \ - $(BIN)/test_amp_fn_ident \ - $(BIN)/test_star_fn_deref \ - $(BIN)/test_star_fn_deref_call \ - $(BIN)/test_match_variant_dispatch \ - $(BIN)/test_strict_package \ - $(BIN)/test_io_types_run \ - $(BIN)/test_dot_aliased_ptr \ - $(BIN)/test_return_tagged_forward \ - $(BIN)/test_widen_transitive \ - $(BIN)/test_typeassert_nonident \ - $(BIN)/test_isas_spread_variant \ - $(BIN)/test_tagged_widen_named_variant \ - $(BIN)/test_io_vtable_run \ - $(BIN)/test_io_handle_run \ - $(BIN)/test_memio_vstream_run \ - $(BIN)/test_fmt_handle_run \ - $(BIN)/test_fmt_mods_run \ - $(BIN)/test_fmt_scanoverflow_run \ - $(BIN)/test_fmt_compositions_run \ - $(BIN)/test_fmt_int_run \ - $(BIN)/test_amp_fn_assign_run \ - $(BIN)/test_type_value_shadow_run \ - $(BIN)/test_xmod_alias_struct_collide_run \ - $(BIN)/test_xmod_struct_argpush_collide_run \ - $(BIN)/test_xmod_struct_field_layout_collide_run \ - $(BIN)/test_xmod_variant_match \ - $(BIN)/test_xmod_qualstructlit_run \ - $(BIN)/test_spread_variant_match \ - $(BIN)/test_xmod_ident_prefer \ - $(BIN)/test_xmod_valglobal_run \ - $(BIN)/test_xmod_valglobal_dot_run \ - $(BIN)/test_zeroinit_run \ - $(BIN)/test_tuple_sret_callee \ - $(BIN)/test_append_wide_elem \ - $(BIN)/test_delete_elem \ - $(BIN)/test_delete_range \ - $(BIN)/test_insert_elem \ - $(BIN)/test_arrlit_overlong \ - $(BIN)/test_idx_structlit_store \ - $(BIN)/test_inferred_let_struct \ - $(BIN)/test_agg_assign_width \ - $(BIN)/test_placeaddr_store \ - $(BIN)/test_tryprop_multisuccess \ - $(BIN)/test_append_place \ - $(BIN)/test_struct_tuple_field_slot \ - $(BIN)/test_named_ptr_alias_variant_widen \ - $(BIN)/test_single_field_struct_zeroinit \ - $(BIN)/test_structvariant_largeunion_return \ - $(BIN)/test_narrow_alias_deref_store \ - $(BIN)/test_bufio_vstream_run \ - $(BIN)/test_log_vstream_run \ - $(BIN)/test_use_promote_alias \ - $(BIN)/test_field_signed $(BIN)/test_frame_argcount \ - $(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \ - $(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \ - $(BIN)/test_dyn_ww $(BIN)/test_selfcheck \ - $(BIN)/test_attest_record $(BIN)/test_attest_drop \ - $(BIN)/test_declns_sep $(BIN)/test_modresetadj_run \ - $(BIN)/test_wwileaf_run \ - $(BIN)/test_wwispread_sep \ - $(BIN)/test_fmt_run $(BIN)/test_log_run $(BIN)/test_fnmatch_run \ - $(BIN)/test_shlex_run $(BIN)/test_getenv_run $(BIN)/test_dirs_run \ - $(BIN)/test_dirs_toolong_run \ - $(BIN)/test_stat_run $(BIN)/test_time_run \ - $(BIN)/test_intdiv_signed \ - $(BIN)/test_strings_run \ - $(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \ - $(BIN)/test_path_run \ - $(BIN)/test_test_filter \ - $(BIN)/test_strconv_int_run \ - $(BIN)/test_stof_run $(BIN)/test_ftos_run \ - $(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \ - $(BIN)/test_errno_run \ - $(BIN)/test_base32_run $(BIN)/test_base64_run \ - $(BIN)/test_adler32_run $(BIN)/test_crc16_run \ - $(BIN)/test_crc32_run $(BIN)/test_crc64_run \ - $(BIN)/test_siphash_run $(BIN)/test_sha256_run \ - $(BIN)/test_regex_run \ - $(BIN)/test_lib_byteid \ - $(BIN)/test_m2wwi_run \ - $(BIN)/test_m3sep_run \ - $(BIN)/test_barefn_collide_run \ - $(BIN)/test_sepbuild_run \ - $(BIN)/test_sepscratch_run \ - $(BIN)/test_sepdotpath_run \ - $(BIN)/test_seproot_export_run \ - $(BIN)/test_syntaxexport_run \ - $(BIN)/test_sepstructdef_run \ - $(BIN)/test_sepcycle_dup \ - $(BIN)/test_separchive_run \ - $(BIN)/test_pkgcache_run \ - $(BIN)/test_pkgcache_concurrent_run \ - $(BIN)/test_pkgcache_poison_run \ - $(BIN)/test_c6soak_run \ - $(BIN)/test_septest_run \ - $(BIN)/test_coloimport_sep \ - $(BIN)/test_depmain_sep \ - $(BIN)/test_checked_run \ - $(BIN)/test_defdim_struct_run \ - $(BIN)/test_arrlit_slice_run \ - $(BIN)/test_def_mangle_run \ - $(BIN)/test_arr_module_index_run \ - $(BIN)/test_array_init_acceptiffits_run \ - $(BIN)/test_amp_def_global_run \ - $(BIN)/test_f64crossmod_run \ - $(BIN)/test_tuprecv_run \ - $(BIN)/test_tuprecv_f64_run \ - $(BIN)/test_assert_builtin_run \ - $(BIN)/test_modqualdestr_run \ - $(BIN)/test_structparam_run \ - $(BIN)/test_structret_run \ - $(BIN)/test_floats_run \ - $(BIN)/test_types_sizelim_run \ - $(BIN)/test_types_intlim_run \ - $(BIN)/test_opaque_guards \ - $(BIN)/test_sort_run \ - $(BIN)/test_bufio_run $(BIN)/test_random_run \ - $(BIN)/test_asserttyped_gap \ - $(BIN)/test_cast_base_index \ - $(BIN)/test_ascii_run $(BIN)/test_tok_run $(BIN)/test_nkname_run -$(BIN)/test_smoke: test/wcc/000_smoke.c $(LIB)/libwcc.a | $(BIN) - $(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwcc - -$(BIN)/test_lex: test/wcc/100_lex.c $(LIB)/libwcc.a | $(BIN) - $(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwcc - -$(BIN)/test_parse: test/wcc/200_parse.c $(LIB)/libwcc.a | $(BIN) - $(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwcc - -$(BIN)/test_check: test/wcc/300_check.c $(LIB)/libwcc.a | $(BIN) - $(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwcc - -$(BIN)/test_w6c: test/wcc/400_w6c.c $(BIN)/w6c | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_w6a: test/wcc/500_w6a.c $(BIN)/w6c $(BIN)/w6a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dataw: test/wcc/510_dataw.c $(BIN)/w6a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 530_w6a_parsenum (F14 #62): w6a vs w6a_ww must agree on strtoll-edge -# immediate shapes ($ 5 / $08) and both reject a bare -(BP). Needs both -# assembler twins. -$(BIN)/test_w6a_parsenum: test/wcc/530_w6a_parsenum.c $(BIN)/w6a $(BIN)/w6a_ww \ - | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_datar: test/wcc/520_datar.c $(BIN)/w6a $(BIN)/w6l | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_w6l: test/wcc/600_w6l.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_w6l_manyflags: test/wcc/632_w6l_manyflags.c $(BIN)/w6c $(BIN)/w6a \ - $(BIN)/w6l $(BIN)/w6l_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_data_link: test/wcc/620_data_link.c $(BIN)/w6a $(BIN)/w6l | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arch: test/wcc/610_arch.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(OBJ)/rt/start.o | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_e2e: test/wcc/700_e2e.c $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a $(OBJ)/rt/start.o | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_ffi: test/wcc/800_ffi.c $(BIN)/w6c | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dyn: test/wcc/810_dyn.c $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_stdlib: test/wcc/900_stdlib.c $(BIN)/w6c | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_selfimport: test/wcc/948_selfimport.c $(BIN)/w6c $(BIN)/w6c_ww \ - | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_missingpkg: test/wcc/949_missingpkg.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_driver_flagargs: test/wcc/949_driver_flagargs.c $(BIN)/ww \ - $(BIN)/ww_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_test_filter drives `ww test [pattern]` end-to-end on BOTH -# driver twins, so it needs the full cstage + wwstage tool sets (ww_ww -# shells to w6c_ww/w6a_ww/w6l_ww) plus libwwrt for the link. #17. -$(BIN)/test_test_filter: test/wcc/989_test_filter.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -Werror -o $@ $< - -# 989_callarg_typecheck drives `ww build` + `ww test -c` then ` -T` -# on BOTH driver/compiler twins (#24 general call-arg typecheck + #34 -# fn-rvalue stamp + #29 rune-lit coerce), so it needs the full cstage + -# wwstage tool sets plus libwwrt for the link. -$(BIN)/test_callarg_typecheck: test/wcc/989_callarg_typecheck.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_catA_f2_reject drives the #38/F2 checker-reject wave (review items -# #2/#6/#7/#10/#39): each REJECT row must FAIL to build on BOTH driver -# twins, each control must build+run. Needs the full cstage + wwstage tool -# sets plus libwwrt for the link. -$(BIN)/test_catA_f2_reject: test/wcc/989_catA_f2_reject.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_intoverflow_reject (F14 #53): an integer literal that overflows u64 -# must FAIL to build on BOTH driver twins (wwstage parseint dropped the C -# twin's overflow guard). Needs the full cstage + wwstage tool sets plus -# libwwrt for the control links. -$(BIN)/test_intoverflow_reject: test/wcc/989_intoverflow_reject.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_unknowndecl_reject (F14 #55): an unrecognized top-level construct -# must FAIL to build on BOTH driver twins (wwstage's parsefile fallback -# silently chewed it with no error). Needs the full cstage + wwstage tool -# sets plus libwwrt for the control links. -$(BIN)/test_unknowndecl_reject: test/wcc/989_unknowndecl_reject.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 782_strict_package (#24a): every primary section must open with a `package` -# clause; a missing clause on the first decl is a hard error, symmetric on -# cstage w6c + wwstage w6c_ww. Each reject row reddens under a gate revert. -$(BIN)/test_strict_package: test/wcc/782_strict_package.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_nullableglobal_reject (#45 silent->loud bridge, task #15 prereq): a -# module-level nullable `(*T | void)` GLOBAL has no storage path, so a -# match/is/as on it silently miscompiled (0(BP) read / undefined ref) on -# BOTH stages. The bridge dies LOUD at the size/storage layer; the full -# storage + read-class arc is deferred to #15 (CSP handle-singleton -# prereq). Needs the full cstage + wwstage tool sets plus libwwrt for the -# control links. -$(BIN)/test_nullableglobal_reject: test/wcc/989_nullableglobal_reject.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_taggedcompoundderef_reject (#18, last of the tagged-payload WRITE -# class): `*p OP= v` where p is a *tagged is nonsense and must loud-reject. -# cstage already gated (sz>=16 falls to the assign-resolver fatal); wwstage -# silently miscompiled (single MOVQ on the tag word). The align-down adds -# the same size gate so both stages reach the identical resolver reject. -# Reject rows assert build-fail + shared diagnostic; control rows (scalar -# compound + #17 plain deref store) build + run. Needs both tool sets + -# libwwrt for the control links. -$(BIN)/test_taggedcompoundderef_reject: test/wcc/989_taggedcompoundderef_reject.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_libprecond_abort: test/wcc/989_libprecond_abort.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_taggedcompoundplace_reject (#20/#21, last two members of the -# compound-OP-on-tagged-place reject class): `gs[i] OP= v` / `a[i] OP= v` -# (INDEX, #20) and `g OP= v` (IDENT, #21) on a tagged-union place are -# nonsense and must loud-reject. cstage silently dropped the indexed compound -# (cs!=ww); BOTH stages silently corrupted the tag word for the ident -# compound (byte-id-green). The fix gates the cstage indexed plain-store arm -# on TK_ASSIGN (compound falls to the #133 reject) and adds a tagged-ident -# compound guard to both stages. Reject rows assert build-fail + the -# per-place-kind diagnostic (#133 for index, #21 for ident); control rows -# (scalar ident/index compound + plain tagged ident/local-index/global-index -# store) build + run. Needs both tool sets + libwwrt for the control links. -$(BIN)/test_taggedcompoundplace_reject: test/wcc/989_taggedcompoundplace_reject.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_ffivariadic_run (C1, catB-54): a ww caller of a C variadic fn must set -# the SysV AL register to the count of XMM regs used for the variadic float -# args — a RUNTIME-only correctness property (byte-id is blind to AL). The cc -# fixture is built self-contained (zero relocs + zero undef syms, verified via -# readelf -r / nm) so w6l links it with no libc, then wrapped in libffifix.a -# under $(OUT)/ffivariadic (the harness finds it via $(BIN)/../ffivariadic). -# Runs on BOTH cstage `ww` and wwstage `ww_ww` (C2). Fixture flags are fixed -# (NOT $(CFLAGS), which is -O0/-std=c99) to match ken's verified -# self-contained build. -$(OUT)/ffivariadic/libffifix.a: test/wcc/data/ffivariadic/fixture.c - @mkdir -p $(OUT)/ffivariadic - $(CC) -O1 -fno-stack-protector -fno-asynchronous-unwind-tables \ - -fcf-protection=none -c -o $(OUT)/ffivariadic/fixture.o $< - $(AR) rcs $@ $(OUT)/ffivariadic/fixture.o - -$(BIN)/test_ffivariadic_run: test/wcc/989_ffivariadic_run.c \ - $(OUT)/ffivariadic/libffifix.a \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_m1mangle_run / _sym (M1, #22 + #32): path-qualified symbol mangling -# and root-unit entry detection. _run builds+runs a tiny package tree on -# BOTH stages (rule-10); _sym asserts the emitted symbol names + cs.s==ww.s. -$(BIN)/test_m1mangle_run: test/wcc/989_m1mangle_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_m1mangle_sym: test/wcc/989_m1mangle_sym.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_m1usehint_run (M1, #40): the cgen mangle-hint must be MODULE-scoped. -# Two same-leaf exported fns across directory-packages, each `import`ed by a -# different module aliasing `math`; every `math.pick()` must route to its own -# import (not the file-global first-match). Builds+runs on BOTH stages + pins -# cstage.s==wwstage.s — the gate-VISIBLE #263-class hint miscompile. -$(BIN)/test_m1usehint_run: test/wcc/989_m1usehint_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_m1union_run (M1, #199b): a `match` on a cross-module fn's tagged-union -# return must keep each nominal void-alias variant on a distinct tag. Builds+ -# runs the utf8.next 4-arm match per-arm on BOTH stages (rule-10) and pins -# cstage.s==wwstage.s on the all-arms program — the gate-VISIBLE regression -# teeth for the variant-collapse the doc flagged as byte-id-only. -$(BIN)/test_m1union_run: test/wcc/989_m1union_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_structlocal_frame (#75): a sub-8 nested-composite struct LOCAL must -# reserve its frame slot at the struct's NATURAL size (round8(ti.size)), -# not the slot-padded ti.slotsize. wwstage's slotsize() TY_STRUCT arm -# over-reserved, so outer{a:u8,p:inner{x:u8,y:u8},z:i64} emitted frame $32 -# / -24(BP) vs cstage's $16 / -16(BP) — runtime-invisible (both exit 0) -# but a cs!=ww .s divergence (rule 10) and a latent byte-id landmine. The -# FRAME-ABSOLUTE twin of the runtime nestfield rows (migrated to -# test/lang/nestfield_test.ww): emits .s via w6c and w6c_ww and byte-diffs -# (no link, no run). -$(BIN)/test_structlocal_frame: test/wcc/989_structlocal_frame.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_arrlit_tail_zero_run (#13): an under-length array literal zero-fills the -# unspecified tail, not the last value. Builds+runs on BOTH driver twins -# (rule-10), pinning the absolute value (pre-fix ww tail = last value). -$(BIN)/test_arrlit_tail_zero_run: test/wcc/989_arrlit_tail_zero_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_allocalias_run (#26): alloc(T{...}) for an alias T sizes + field-fills -# from the alias-chased struct layout, not a name-keyed lookup of the literal -# head. Builds+runs on BOTH driver twins (rule-10). -$(BIN)/test_allocalias_run: test/wcc/989_allocalias_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_slttypepref_run (#58/#50, c1): scopelookuptype prefers the current -# module's SK_TYPE when a value binding shadows a type leaf two modules both -# export. Builds+runs on BOTH driver twins (rule-10). See the test header. -$(BIN)/test_slttypepref_run: test/wcc/989_slttypepref_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_defercap_run (#40, F13 c1): the defer stack fails loud at its shared -# cap (32) in BOTH stages instead of silently dropping the overflowing -# deferred call (wwstage was capped at 16). Builds/rejects on both driver -# twins (rule-10). See the test header. -$(BIN)/test_defercap_run: test/wcc/989_defercap_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_loopcap_run (#42, F13 c2): the loop-label stack fails loud at its cap -# (LOOP_MAX) in BOTH stages instead of an OOB heap write (ww) / wrong jump -# target (cstage). Builds/rejects on both driver twins (rule-10). See header. -$(BIN)/test_loopcap_run: test/wcc/989_loopcap_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_enumcap_run (#65, F13 c3): the driver's directory enumerator grows -# dynamically (mirror cstage realloc-doubling) instead of silently capping at -# 256 files. Builds on both driver twins (`--sep`) and asserts a byte-identical -# per-package sep unit (bigmod.unit.ww) on a >256-file module (rule-10). See -# the test header. -$(BIN)/test_enumcap_run: test/wcc/989_enumcap_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_wwdumpgate_run (#52, F15 c4): wwdump_ww's -c/-r arms gate on parse-stage -# errors instead of silently emitting asm / a resolve report on a broken AST. -# Runs wwdump + wwdump_ww directly (rule-10). See the test header. -$(BIN)/test_wwdumpgate_run: test/wcc/989_wwdumpgate_run.c \ - $(BIN)/wwdump $(BIN)/wwdump_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_datargate_run (#59, F15 c5): w6a loud-rejects a DATAR relocation whose -# slot was never defined by a prior DATAW instead of silently dropping the -# reloc (zero-filled pointer slot in the .o). Runs w6a + w6a_ww directly and -# asserts a byte-identical .o on the healthy path (rule-10). See the header. -$(BIN)/test_datargate_run: test/wcc/989_datargate_run.c \ - $(BIN)/w6a $(BIN)/w6a_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_dynentry_run (#63, F15 c6): w6l's dynamic e_entry is rebased onto the -# actual .text file offset once the dynamic headers overflow the first page, -# instead of the stale base+0x1000+val (which mapped into the headers → -# SIGSEGV). BOTH stages (#263). Builds + runs ~160-dyn-sym binaries on both -# driver twins and asserts exit 42 + byte-identical output (rule-10). Skips -# without libc. See the test header. -$(BIN)/test_dynentry_run: test/wcc/989_dynentry_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_zerotest_run (#64, F15 c7): `ww test ` over a directory with no -# *_test.ww files fails loud (rc=1 + "no *_test.ww files in ...") instead of a -# silent rc=0 false-green. Runs both driver twins (rule-10). See the header. -$(BIN)/test_zerotest_run: test/wcc/989_zerotest_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_ampfncollide_run (#4, c2): `&fn` synthesis (unoptype TK_AMP + -# assignableaddrfn) prefers the current module's fn when a same-leaf fn is -# declared in a later module. Builds/rejects on BOTH driver twins (rule-10). -# See the test header. -$(BIN)/test_ampfncollide_run: test/wcc/989_ampfncollide_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_trycallcollide_run (#11a, c3): exprtypeoftry prefers the current -# module's symbol when typing a `?`/`!` operand whose bare leaf collides with -# a same-leaf decl in a later module. Builds+runs on BOTH driver twins -# (rule-10). See the test header. -$(BIN)/test_trycallcollide_run: test/wcc/989_trycallcollide_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_fnptrcollide_run (F7-c7, #14): a value ident whose leaf collides with -# a fn name must not be mis-folded into the fn's TEXT reloc. Build-must-fail -# on BOTH driver twins (rule-10); wwstage gated. See the test header. -$(BIN)/test_fnptrcollide_run: test/wcc/989_fnptrcollide_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_neg_global: test/wcc/631_def_neg_global.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_const_fold: test/wcc/732_def_const_fold.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_int_cast_signed: test/wcc/640_int_cast_signed.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_uniesc_run: test/wcc/110_uniesc_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_chain: test/wcc/650_dot_chain.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_amp_dot: test/wcc/690_amp_dot.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_elem_field: test/wcc/680_arr_elem_field.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_elem_field_write: test/wcc/681_arr_elem_field_write.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_enum_elem: test/wcc/682_arr_enum_elem.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_strslice_elem: test/wcc/683_arr_strslice_elem.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_packed: test/wcc/671_struct_packed.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_infer_len: test/wcc/684_arr_infer_len.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_cap_reject: test/wcc/817_arr_cap_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_subset_reject: test/wcc/989_tagged_subset_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_ptr_global: test/wcc/818_arr_ptr_global.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cast_base_index: test/wcc/830_cast_base_index.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_str_index_reject: test/wcc/821_def_str_index_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_global_byval_arg: test/wcc/822_struct_global_byval_arg.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_inferred_global_let: test/wcc/823_inferred_global_let.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_inferred_float_global: test/wcc/824_inferred_float_global.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_errfirst_union_try: test/wcc/825_errfirst_union_try.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_tuple_coerce: test/wcc/826_alias_tuple_coerce.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_str_eq: test/wcc/827_str_eq.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_overlong_arrlit: test/wcc/828_overlong_arrlit.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tuple_elem_overlong: test/wcc/832_tuple_elem_overlong.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_arr_variant: test/wcc/834_tagged_arr_variant.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_nested_union_int_box: test/wcc/835_nested_union_int_box.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_assignable_cluster: test/wcc/836_tagged_assignable_cluster.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_idcast_tagged_return: test/wcc/837_idcast_tagged_return.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_global_tagged_castinit: test/wcc/838_global_tagged_castinit.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_xmod_nominal_typeeqast: test/wcc/839_xmod_nominal_typeeqast.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_inferred_array_global: test/wcc/833_inferred_array_global.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_slice_str_global_arg: test/wcc/829_slice_str_global_arg.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_arr_infer_len: test/wcc/814_def_arr_infer_len.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_arr_len: test/wcc/816_def_arr_len.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_str_table: test/wcc/819_def_str_table.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_zero_vs_infer: test/wcc/820_arr_zero_vs_infer.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_tagged_elem: test/wcc/685_arr_tagged_elem.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dup_main_reject: test/wcc/842_dup_main_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tuple_trailing_comma_reject: test/wcc/845_tuple_trailing_comma_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_voidless_return_reject: test/wcc/846_voidless_return_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_const_reassign_reject: test/wcc/847_const_reassign_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dupfield_reject: test/wcc/849_dupfield_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_enum_reject: test/wcc/850_enum_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_variadic_body_reject: test/wcc/852_variadic_body_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_index_int_reject: test/wcc/851_index_int_reject.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_staticinit: test/wcc/843_tagged_staticinit.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_size_untyped_int: test/wcc/844_size_untyped_int.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_slice_str_global_zero: test/wcc/686_slice_str_global_zero.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_slice_literal_global: test/wcc/687_slice_literal_global.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_global_arr_elem_field: test/wcc/688_global_arr_elem_field.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_structlit_slice_field: test/wcc/689_structlit_slice_field.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 689_arrglob_nodup_run (#12): a no-init array global emits its zero-fill -# DATAW row exactly once (the str-size arm was size-keyed and double-matched -# arrays). Builds+runs both twins; asserts one DATAW row per stage. -$(BIN)/test_arrglob_nodup_run: test/wcc/689_arrglob_nodup_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 689_globptr_field_store_run (#6): a module-global *struct ptr base field -# store (compound / non-scalar / chained / indexed) no longer SEGVs in cstage -# and is byte-identical to wwstage (cstage aligned UP via decline-to-resolver). -$(BIN)/test_globptr_field_store_run: test/wcc/689_globptr_field_store_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 689_globptr_field_read_run (#15): reading a field through a module-global -# *struct ptr base (single-dot `gp.f`) no longer SEGVs in cstage / collapses -# to a bare symbol in wwstage; both load the ptr value via SB then the field -# offset, byte-identically. Read twin of 689_globptr_field_store_run. -$(BIN)/test_globptr_field_read_run: test/wcc/689_globptr_field_read_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_str_chained_arg: test/wcc/692_dot_str_chained_arg.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_slice_arg: test/wcc/691_dot_slice_arg.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_tagged_source: test/wcc/693_dot_tagged_source.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_store_intlit: test/wcc/694_tagged_store_intlit.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_bind_struct: test/wcc/695_match_bind_struct.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_modtype_leaf_collision: test/wcc/696_modtype_leaf_collision.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_samemod_prefer: test/wcc/697_samemod_prefer.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cgreturn_struct: test/wcc/698_cgreturn_struct.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cgassign_struct: test/wcc/701_cgassign_struct.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_explicit_deref: test/wcc/702_dot_explicit_deref.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_nested_structlit: test/wcc/703_nested_structlit.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_structlit: test/wcc/704_dot_structlit.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_nested_call_rhs: test/wcc/705_nested_call_rhs.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fnlabel_mangle: test/wcc/706_fnlabel_mangle.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cgreturn_variant_zero: test/wcc/707_cgreturn_variant_zero.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_param_shadow_mod: test/wcc/708_param_shadow_mod.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_localoff_scope: test/wcc/709_localoff_scope.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cast_enum_movl: test/wcc/710_cast_enum_movl.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_check_enum_fold: test/wcc/759_check_enum_fold.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_widen_const: test/wcc/760_def_widen_const.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_inferred_struct_arg_push: test/wcc/761_inferred_struct_arg_push.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_abi_size: test/wcc/762_struct_abi_size.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_typeeq_fn_ast: test/wcc/763_typeeq_fn_ast.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_amp_fn_ident: test/wcc/764_amp_fn_ident.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_star_fn_deref: test/wcc/765_star_fn_deref.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_star_fn_deref_call: test/wcc/766_star_fn_deref_call.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_variant_dispatch: test/wcc/767_match_variant_dispatch.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_io_types_run: test/wcc/768_io_types_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dot_aliased_ptr: test/wcc/769_dot_aliased_ptr.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_return_tagged_forward: test/wcc/770_return_tagged_forward.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_widen_transitive: test/wcc/771_widen_transitive.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_typeassert_nonident: test/wcc/772_typeassert_nonident.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_isas_spread_variant: test/wcc/773_isas_spread_variant.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_widen_named_variant: test/wcc/774_tagged_widen_named_variant.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_io_vtable_run: test/wcc/775_io_vtable_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_io_handle_run: test/wcc/791_io_handle_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_memio_vstream_run: test/wcc/776_memio_vstream_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/memio/memio.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fmt_handle_run: test/wcc/777_fmt_handle_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/fmt/fmt.ww lib/memio/memio.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #206: bare &fn assignable into * / (*|void). Both -# stages (cstage + wwstage byte-id); single-file probes, no lib imports. -$(BIN)/test_amp_fn_assign_run: test/wcc/783_amp_fn_assign_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #225: a value binding (param/let/fn) that shadows a same-named TYPE -# must not hide it in type-annotation or cast position. cstage was -# kind-blind in resolve_typename; wwstage already separated the type and -# value namespaces. Driver build + run on both stages + cs.s == ww.s. -$(BIN)/test_type_value_shadow_run: test/wcc/788_type_value_shadow_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #223: a struct-field access through a pointer-ALIAS receiver whose -# leaf collides with another module's same-leaf STRUCT. cstage driver -# build + run for runtime, cs-sep vs ww-sep .s cmp (concatenated -# main.sepwork/*.s) for rule-10 byte-id (the #223 discriminator). Builds its -# own 2-module fixtures in a private mktemp dir — not a selfhost-driver test. -$(BIN)/test_xmod_alias_struct_collide_run: test/wcc/784_xmod_alias_struct_collide_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 793 #21/#224: inferred-let struct LOCAL whose leaf collides cross-module -# miscompiled the by-value arg push + recv + direct field read (all -# name-keyed); fix routes them off the stamped tinfo. Own 2-module fixtures -# in a private mktemp dir — not a selfhost-driver test. -$(BIN)/test_xmod_struct_argpush_collide_run: test/wcc/793_xmod_struct_argpush_collide_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 797 #31 (RULING R2 commit-1): inferred-let struct field-LAYOUT receiver -# arms — local *struct READ, let-COPY size, &p.f / &o.f addr-of — keyed -# off the bare leaf that mis-resolves cross-module; fix routes them off the -# stamped tinfo (tichase(recv.type_)->fields / structabisizetn). Direct -# continuation of 793 (#21). Own 2-module fixtures in a private mktemp dir. -$(BIN)/test_xmod_struct_field_layout_collide_run: test/wcc/797_xmod_struct_field_layout_collide_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #13: cross-module decomposition of an imported union's variants -# (`case pkg.a`). Pre-fix the wwstage checker rejected it; cstage built -# it. cstage driver build + run pins routing; ww-sep build (w6c_ww must -# now accept) + cs-sep==ww-sep byte-id is the discriminator. -# Builds its own 2-module fixtures in a private mktemp dir. -$(BIN)/test_xmod_variant_match: test/wcc/787_xmod_variant_match.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #76: a qualified struct literal `pkg.Type{...}` (type named through an -# imported module). Pre-fix it failed to parse (the dotted typeref folded -# to an N_DOT chain the checker's resolve_type had no arm for); the fix -# flattens the chain to one N_TNAME at the struct-lit handoff in BOTH -# stages. cstage driver build + run pins parse + runtime field values; -# cs-sep vs ww-sep .s cmp (concatenated main.sepwork/*.s) is the rule-10 -# byte-id gate. Builds its own 2-module fixtures in a private mktemp dir. -$(BIN)/test_xmod_qualstructlit_run: test/wcc/848_xmod_qualstructlit_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(BIN)/wwdump $(BIN)/wwdump_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #209: a `match` over a tagged union with a `...inner` SPREAD variant -# (e.g. fmt's `field = (...formattable | *mods)`). The wwstage checker -# walked the raw AST u.list and false-rejected every flattened member arm -# ("case: not a variant" / "variant not handled"), so any fmt-importing -# unit was wwstage-uncompilable. Fix flattens the spread in the checker's -# match validity + exhaustiveness walk (selfhost/cmd/wcc/check.ww). The -# w6c_ww-accepts + cs-sep==ww-sep byte-id on the driver sep build is the -# discriminator. Self-contained single-file probe. -$(BIN)/test_spread_variant_match: test/wcc/792_spread_variant_match.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #55: bare value-ident in an imported module must resolve same-module- -# preferred (exprtype N_IDENT scopelookup -> scopelookupprefer). cstage -# driver build + run pins routing; the ww-sep build (w6c_ww) must ACCEPT -# (pre-fix it rejected the wrong-module-typed ident). Builds its own -# 2-module fixtures in a private mktemp dir. -$(BIN)/test_xmod_ident_prefer: test/wcc/794_xmod_ident_prefer.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #1 (cgen residual of #55): cross-module bare value-global load must -# mangle reference-site by resolved module / definition-site by the -# decl's own module — runtime + cs==ww byte-id. Self-contained single- -# file multi-package probe (953 model). -$(BIN)/test_xmod_valglobal_run: test/wcc/795_xmod_valglobal_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #229 (N_DOT-base + addr-of sibling of #1): cross-module DOTTED value- -# global read AND &-address-of must mangle by the dotted module name, not -# cur_mod — runtime + cs==ww byte-id. Self-contained multi-package probe. -$(BIN)/test_xmod_valglobal_dot_run: test/wcc/796_xmod_valglobal_dot_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #16: a bare `let x: T;` (no rhs) must zero-fill its slot. cgen emitted -# the zero-fill only for 8B-primitive and >8B-composite slots — a SUB-8 -# aggregate (`let c: [3]u8;`) fell through to NOTHING and read stack -# garbage (cstage == wwstage, gate-blind; ken's bytes-967 root cause). -# Runtime (cstage dirty()→probe()) + cs==ww byte-id, both dimensions/row. -$(BIN)/test_zeroinit_run: test/wcc/840_zeroinit_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #10 Fold A (wide tuple-return / sret, CALLEE side): an over-cap tuple -# return (> 4 GP or > 2 SSE eightbytes) now compiles via sret instead of -# loud-stopping at the SEND. Compile + cs==ww byte-id only — the receive -# stays loud-stopped (Fold B wires the round-trip). Self-contained probes. -$(BIN)/test_tuple_sret_callee: test/wcc/798_tuple_sret_callee.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #10 Fold B (wide tuple-return / sret, RECEIVE side): the call/receive -# end of the over-cap tuple ABI — single-var-let, destructure, reassign, -# return-forward. Runtime round-trip (the net Fold A deferred — byte-id is -# blind to a SEND/RECEIVE layout mismatch) + cs==ww byte-id. Self-contained -# single-file probes (953 model), no selfhost traversal. -# #34: append() stores the full element width per element kind — runtime -# readback (tagged box / str+slice 3-word header / struct fill / spread -# word-copy / 2-append realloc survival) + per-row cs==ww byte-id (which -# pins the wwstage elemsizeofc membsz, scratch order, and frame canary) -# + the rule-7 loud-stop for the deferred struct-from-call source shape. -$(BIN)/test_append_wide_elem: test/wcc/800_append_wide_elem.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #35 (delete-half): delete(xs[i]) single-element slice removal — runtime -# rows across every element kind (scalar/narrow/str/struct/56B tagged) + -# the deref-of-local regex fold-2b shape + checker reject rows + cs==ww -# asm byte-id (the converged-by-construction shift loop). -$(BIN)/test_delete_elem: test/wcc/804_delete_elem.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #35 (range-half, fold-5a prereq P2): delete(xs[lo:hi]) range slice -# removal — runtime rows across defaults (full/head/tail/empty/end -# boundary), the esz copy-tail spread against the dynamic src offset -# (1/2/4/8/16/24), the single-elem equivalence, cap preservation, -# operand order-of-eval + aliasing pins, the deref-of-local base, the -# indexed-base regex.ha:333 consumer shape EXACT + checker reject rows -# w/ diagnostic text + cs==ww asm byte-id (the converged-by-construction -# rdl_l/rdl_e shift loop). -$(BIN)/test_delete_range: test/wcc/809_delete_range.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #35 (insert-half): insert(xs[idx], v) single-element slice insertion — -# delete()'s twin, lowered as append-desugar + rotate-right. Runtime rows -# across every element kind (scalar/narrow/str/struct/56B tagged incl. -# the regex fold-3 typed-local + cast-rvalue consumer shapes) + idx==len -# end-insert + pre-grow idx evaluation + the deref-of-local base + -# checker reject rows w/ exact diagnostic text + cs==ww asm byte-id -# (the converged-by-construction rotate loop + @insscr frame slot). -$(BIN)/test_insert_elem: test/wcc/807_insert_elem.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# F6 (task #4, regex fold-2b): (*ts)[i].field = v / OP= v through the -# cgplaceaddr resolver — runtime rows across widths/signedness/str + -# compound ops + loud-tail reject rows w/ exact diagnostic text + cs==ww -# asm byte-id. -# #71: overlong array literal (more elements than the declared [N]) is -# a loud checker reject in BOTH stages — local/module/def/struct-field + -# the repeat-marker form (which used to clobber the saved BP) — plus -# accept guards (exact-N, repeat-fill, [_] inference) w/ runtime + asm -# byte-id on both drivers. -$(BIN)/test_arrlit_overlong: test/wcc/808_arrlit_overlong.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_placeaddr_store: test/wcc/805_placeaddr_store.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# Task #20: struct-LITERAL store into an INDEXED element (`a[i] = -# pt{...}`, `(*ts)[i].caps[k] = capture{...}`) and a DEREF place -# (`*p = pt{...}`) — pre-fix a byte-identical SILENT no-op in both -# stages (gate-blind; the regex fold-5a groupstart capture store). -# Runtime readback rows + asm byte-id per row on both drivers. -$(BIN)/test_idx_structlit_store: test/wcc/809_idx_structlit_store.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #24 (fold-5 prereq, PG5 wwstage pair): field read/assign through an -# annotation-less struct-literal binding `let p = pt{...}` — pre-fix -# the wwstage checker planted the struct BODY node (N_TSTRUCT) as the -# inferred let's type, no N_TNAME-keyed cgen arm matched, and `p.f` -# leaked the FIELD NAME as a global symbol (`MOVQ f(SB)`, link-fail; -# #211 name-leak family) while a tagged-field assign hit the -# assign-resolver loud bound. Runtime rows + asm byte-id per row. -$(BIN)/test_inferred_let_struct: test/wcc/811_inferred_let_struct.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# Task #49 (Family A, ken silent-set triage): whole-aggregate ASSIGN -# `b = a` copied word0 only — one MOVQ for any struct/array/tuple — in -# BOTH stages, byte-identical, gate-blind. Same class at the structlit -# fill field-from-ident, the deref place (#31-A) and the module-let -# global. Fixed via the ONE mem-to-mem funnel (cg_aggcopy/aggcopy fed -# by aggarg_srcaddr); non-addressable aggregate rhs dies loud. Runtime -# readback rows (the only oracle for a gate-blind class) + per-row -# asm byte-id. -$(BIN)/test_agg_assign_width: test/wcc/812_agg_assign_width.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# F8+F9 (tasks #5/#12, regex fold-2b): `?` interim single-success gate -# (|success| > 1 loud-rejected on BOTH stages until task #14's -# subset-union typing) + direct `f()? is T` / `match (f()?)` reject -# parity — reject rows w/ exact per-stage diagnostic text, accept rows -# runtime + cs==ww asm byte-id. -$(BIN)/test_tryprop_multisuccess: test/wcc/806_tryprop_multisuccess.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# FA1 (task #15, regex fold-2b): append(*p, v) / append(*p, xs...) -# through a pointer-to-slice place via the cgplaceaddr resolver — -# runtime rows across element kinds + spread + realloc/frame-integrity -# + loud-tail reject rows w/ exact diagnostic text + cs==ww asm byte-id. -$(BIN)/test_append_place: test/wcc/806_append_place.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #237: a tuple-typed struct field must contribute its real slot width to -# the enclosing struct's slotsize — pure cs==ww .s byte-id (frame size). -$(BIN)/test_struct_tuple_field_slot: test/wcc/930_struct_tuple_field_slot.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #15: widening a bare *vtable into a NAMED-alias variant (`stream` = -# *vtable) of `(file | stream)` must compute the right tag, not default -# to tag 0. Both-stage byte-id + runtime, plus a degenerate-ambiguity -# reject row (drew's >=2 guard). Self-contained single-file probes. -$(BIN)/test_named_ptr_alias_variant_widen: test/wcc/789_named_ptr_alias_variant_widen.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #213: bare `let b: T;` of an 8B single-field struct must zero-init the -# slot (cstage does; wwstage skipped → read-before-init garbage + 778 -# cs!=ww). Both-stage byte-id + a read-before-init correctness row. -$(BIN)/test_single_field_struct_zeroinit: test/wcc/790_single_field_struct_zeroinit.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #9: returning a STRUCT variant of a LARGE (>4-eightbyte) tagged union. -# Both-stage byte-id + runtime (reads tag AND the widened &fn field, so a -# dropped-store regression can't hide behind self-consistent byte-id). -# Self-contained single-file probes via the driver, no lib imports. -$(BIN)/test_structvariant_largeunion_return: test/wcc/785_structvariant_largeunion_return.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #11: narrow deref-store through *(!i32-alias) must emit MOVL (4B), not -# MOVQ (8B which over-writes the adjacent 4 bytes). Both-stage byte-id + -# runtime over-write guard (sentinel high word survives iff MOVL). -# Self-contained single-file probes via the driver, no lib imports. -$(BIN)/test_narrow_alias_deref_store: test/wcc/786_narrow_alias_deref_store.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fmt_mods_run: test/wcc/780_fmt_mods_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/fmt/fmt.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fmt_scanoverflow_run: test/wcc/989_fmt_scanoverflow_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/fmt/fmt.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fmt_compositions_run: test/wcc/781_fmt_compositions_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/fmt/fmt.ww \ - lib/memio/memio.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fmt_int_run: test/wcc/815_fmt_int_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/fmt/fmt.ww \ - lib/memio/memio.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_bufio_vstream_run: test/wcc/778_bufio_vstream_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/bufio/bufio.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_log_vstream_run: test/wcc/779_log_vstream_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - lib/log/log.ww \ - lib/fmt/fmt.ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arrlit_str_full: test/wcc/711_arrlit_str_full.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_redecl: test/wcc/712_redecl.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_empty_alloc_infer: test/wcc/729_empty_alloc_infer.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_field_index: test/wcc/713_struct_field_index.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_return_scratch: test/wcc/714_tagged_return_scratch.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_widen_f64: test/wcc/715_tagged_widen_f64.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_spill_pointer_payload: test/wcc/716_match_spill_pointer_payload.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_field_inplace: test/wcc/831_match_field_inplace.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_global_field: test/wcc/841_match_global_field.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_byval_param: test/wcc/717_struct_byval_param.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_multi_return_scratch: test/wcc/718_struct_multi_return_scratch.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_signed_data_emit: test/wcc/719_signed_data_emit.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_call_arg: test/wcc/720_tagged_call_arg.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_sret_struct_return: test/wcc/721_sret_struct_return.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_sret_run: test/wcc/926_tagged_sret_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagged_memarg_run: test/wcc/929_tagged_memarg_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tagscr_sizes_run: test/wcc/926_tagscr_sizes_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_global_sret_run: test/wcc/940_global_sret_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_sret_narrow_field: test/wcc/730_sret_narrow_field.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_slice_variant: test/wcc/722_match_slice_variant.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_str_forrange_loopvar_run: test/wcc/940_str_forrange_loopvar_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_array_zeroinit_run: test/wcc/944_array_zeroinit_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_accept_run: test/wcc/944_alias_accept_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_cgen_b5_run: test/wcc/944_alias_cgen_b5_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_cgen_b6_run: test/wcc/944_alias_cgen_b6_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_emit_b7_run: test/wcc/944_alias_emit_b7_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_variant_chain_b95_run: test/wcc/944_variant_chain_b95_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_peellint_gate: test/wcc/944_peellint_gate.c tools/peellint | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_xmod_fnptr_const_run: test/wcc/949_xmod_fnptr_const_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_errtype_compare: test/wcc/949_errtype_compare.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_intbinop_mismatch: test/wcc/949_intbinop_mismatch.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_composite_call_arg: test/wcc/723_composite_call_arg.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_letdecl_zeroinit: test/wcc/724_letdecl_zeroinit.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_nested_if_labels: test/wcc/725_nested_if_labels.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_leaf_collision: test/wcc/726_alias_leaf_collision.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_modcall_widen_slice: test/wcc/727_modcall_widen_slice.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_4arm_cross_module: test/wcc/728_match_4arm_cross_module.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_enum_modshadow: test/wcc/733_enum_modshadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_struct_modshadow: test/wcc/734_struct_modshadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_modshadow: test/wcc/735_def_modshadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cstage_label_ssot: test/wcc/736_cstage_label_ssot.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_direnum: test/wcc/737_direnum.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_module_decl: test/wcc/738_module_decl.c $(LIB)/libwcc.a | $(BIN) - $(CC) $(CFLAGS) -Icmd/wcc -o $@ $< -Lout/lib -lwcc - -$(BIN)/test_chained_index: test/wcc/739_chained_index.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_chained_write: test/wcc/740_chained_write.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dotbase_chained: test/wcc/741_dotbase_chained.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_parse_error: test/wcc/742_parse_error.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_variadic_pack: test/wcc/743_variadic_pack.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_letcopy_struct: test/wcc/744_letcopy_struct.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fnparams_bare_leaf_shadow: test/wcc/732_fnparams_bare_leaf_shadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fnret_bare_leaf_shadow: test/wcc/731_fnret_bare_leaf_shadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fnret34_modshadow: test/wcc/745_fnret34_modshadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_strdef_inline: test/wcc/746_strdef_inline.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_modqual_modshadow: test/wcc/747_def_modqual_modshadow.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_size_strategy_convergence: test/wcc/748_size_strategy_convergence.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_sumtype_forward: test/wcc/749_sumtype_forward.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_mklabel_modscoped: test/wcc/750_mklabel_modscoped.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_vararg_seq_percall: test/wcc/751_vararg_seq_percall.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_modparam_callee: test/wcc/752_modparam_callee.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_convwrap_audit: test/wcc/753_convwrap_audit.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_slice_of_slice_index: test/wcc/754_slice_of_slice_index.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_amp_dot_idx: test/wcc/755_amp_dot_idx.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_alias_chain_unwrap: test/wcc/756_alias_chain_unwrap.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_letbind_void_bang_void: test/wcc/757_letbind_void_bang_void.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_cgalloc_str_field: test/wcc/758_cgalloc_str_field.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_match_4arm_cross_module_run: test/wcc/929_match_4arm_cross_module_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_free_noop_run: test/wcc/930_free_noop_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_use_promote_alias: test/wcc/699_use_promote_alias.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_field_signed: test/wcc/660_field_signed.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_frame_argcount: test/wcc/670_frame_argcount.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_selfhost: test/wcc/990_selfhost.c $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww $(BIN)/wwdump $(BIN)/wwdump_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_w6a_ww: test/wcc/991_w6a_ww.c $(BIN)/w6a $(BIN)/w6a_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_w6l_ww: test/wcc/992_w6l_ww.c $(BIN)/w6l $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# Tier-C fold (#46 c6): the sep-build legs drive BOTH tool trios — the cstage -# `ww --sep` shells out to w6c/w6a/w6l (toolpath self_dir), the wwstage -# `ww_ww --sep` to the _ww trio — plus both drivers + libwwrt. -$(BIN)/test_w6c_ww: test/wcc/994_w6c_ww.c $(BIN)/w6c_ww $(BIN)/wwdump_ww \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_ww_ww: test/wcc/993_ww_ww.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_self_rebuild: test/wcc/995_self_rebuild.c $(BIN)/ww_ww \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/wwdump_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_selfcheck: test/wcc/950_selfcheck.c $(BIN)/wwdump_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_asserttyped_gap: test/wcc/901_asserttyped_gap.c $(BIN)/ww $(BIN)/wwdump_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dyn_ww: test/wcc/996_dyn_ww.c $(BIN)/ww $(BIN)/w6l $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_attest_record: test/wcc/911_attest_record.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# C2b — #6 @test drop/keep/link via direct w6c/w6c_ww on import-free fixtures -# (both stages → all six tools + libwwrt for the linkfail leg). -$(BIN)/test_attest_drop: test/wcc/911_attest_drop.c $(BIN)/w6c $(BIN)/w6c_ww \ - $(BIN)/w6a $(BIN)/w6a_ww $(BIN)/w6l $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# C2c — #23/#30 decl-namespace (dup family + builtin carve-out via direct -# w6c/w6c_ww) + module-fn coexistence (dir-package `ww build --sep` + run, -# both driver stages + cross-order byte-id) → all six tools + drivers + -# libwwrt for the builtin_redecl link. -$(BIN)/test_declns_sep: test/wcc/989_declns_sep.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #9 (BUG-A) — the lexer must clear a pending modpath when a `//ww:module-reset` -# is recognized in the same skipws run (empty/export-less inlined module body). -# Feeds the composed adjacency unit DIRECTLY to w6c/w6c_ww (the driver trips #11 -# first), then assembles+links+runs → all four frontend/asm/link tools + libwwrt. -$(BIN)/test_modresetadj_run: test/wcc/989_modresetadj_run.c \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #11 (BUG-C) — a decl-less primary module's `.wwi` `package` line must carry -# the real leaf, not the default "main", else its importer rejects the dep. -# Table-driven over empty/comment-only/nested-path shapes; feeds the composed -# producer + importer units straight to w6c/w6c_ww (.wwi leaf + byte-id + -# import resolves cs==ww). Frontends only — no asm/link/run leg. -$(BIN)/test_wwileaf_run: test/wcc/989_wwileaf_run.c \ - $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# #95 — .wwi producer must preserve the `...` union-spread marker; dir-package -# `ww build --sep` + run, both driver stages, 2-arm + 3-arm shapes + cs==ww. -$(BIN)/test_wwispread_sep: test/wcc/989_wwispread_sep.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fmt_run: test/wcc/970_fmt_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_log_run: test/wcc/971_log_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_fnmatch_run: test/wcc/972_fnmatch_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_shlex_run: test/wcc/973_shlex_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_getenv_run: test/wcc/974_getenv_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dirs_run: test/wcc/975_dirs_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 975_dirs_toolong_run (F14 #69): an over-long composed path must abort -# loudly, not silently mkdir a truncated wrong directory. Builds+runs a -# dirs.config fixture on BOTH driver twins under a short and a ~260B HOME. -$(BIN)/test_dirs_toolong_run: test/wcc/975_dirs_toolong_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_stat_run: test/wcc/976_stat_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_time_run: test/wcc/977_time_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_intdiv_signed: test/wcc/978_intdiv_signed.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_hex_run: test/wcc/979_hex_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_utf8_run: test/wcc/968_utf8_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_bytes_run: test/wcc/967_bytes_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_path_run: test/wcc/989_path_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_ascii_run: test/wcc/904_ascii_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tok_run: test/wcc/904_tok_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_nkname_run: test/wcc/905_nkname_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_strconv_int_run: test/wcc/922_strconv_int_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_stof_run: test/wcc/909_stof_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_ftos_run: test/wcc/908_ftos_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_strings_run: test/wcc/966_strings_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_memio_run: test/wcc/980_memio_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_temp_run: test/wcc/981_temp_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_getopt_run: test/wcc/982_getopt_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_errno_run: test/wcc/902_errno_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_base32_run: test/wcc/983_base32_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_base64_run: test/wcc/984_base64_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_adler32_run: test/wcc/985_adler32_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_crc16_run: test/wcc/986_crc16_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_crc32_run: test/wcc/987_crc32_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_crc64_run: test/wcc/988_crc64_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_siphash_run: test/wcc/989_siphash_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_sha256_run: test/wcc/989_sha256_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_regex_run: test/wcc/989_regex_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_lib_byteid: test/wcc/989_lib_byteid.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_m2wwi_run: test/wcc/989_m2wwi_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6c_ww $(BIN)/wwdump $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# M3 `.wwi` separate-compile CONSUMER gate (task #22 arc). Drives both -# toolchains: w6c/w6c_ww (-c codegen filter), w6a/w6a_ww, w6l/w6l_ww for -# the per-package .s byte-id + cs==ww .s/exe + determinism + behavioral -# soak on the synth leaf->mid->root fixture. COLD: .wwi materialized fresh. -$(BIN)/test_m3sep_run: test/wcc/989_m3sep_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww $(BIN)/w6l $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_sepbuild_run drives `ww build --sep` + `ww_ww build --sep` (the -# M3-tail commit-3 build_one_sep driver) end-to-end on the real lib chain -# root->os->{rt,time}. Needs BOTH driver stages + both compiler stages -# (the keystone re-runs w6c -c on the bodies-unit). -$(BIN)/test_sepbuild_run: test/wcc/989_sepbuild_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_sepscratch_run (#59) — the driver must rm its per-build `.sepwork` -# scratch on `run`/`test` (keepscratch 0) and KEEP it on `build -o` -# (keepscratch 1, the byte-id gates read it). Drives BOTH driver stages on -# a trivial root, so it needs both driver + both compiler + both linker -# stages + libwwrt (a `run` end-to-end build). -$(BIN)/test_sepscratch_run: test/wcc/989_sepscratch_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_barefn_collide_run — #84 cgen bare-module fn-leaf collision gate. A -# package-less root's bare `fn run` must stay BARE (distinct from an -# imported `aa.run`), not mis-mangle onto the import (a #263-class silent -# dead-dup). Drives BOTH driver stages on a real dir-package import, so it -# needs both driver + both compiler + both linker stages + libwwrt. -$(BIN)/test_barefn_collide_run: test/wcc/989_barefn_collide_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_sepdotpath_run — sep-build of a DOTTED-path (multi-component) package -# (#57): the producer must tag a primary body by its full dotted import path -# (`//ww:module-reset a.b`), not the leaf `package` clause, so definer == -# importer. Drives BOTH driver stages (synthetic a.b + c graph) and re-greps -# the producer .s, so it needs both driver + both compiler + both linker -# stages + libwwrt for the link. -$(BIN)/test_sepdotpath_run: test/wcc/989_sepdotpath_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_seproot_export_run — sep-build of a ROOT whose `export fn` references an -# unexported LOCAL type (#69, BUG-1): the producer must NOT pass `-I` for the -# root, since `-I` triggers check_exported_type, which rejects that legal -# terminal-binary pattern. Drives BOTH driver stages + replays the forced -# `-I` reject directly on the produced root unit (both compilers), so it needs -# both driver + both compiler + both linker stages + libwwrt for the link. -$(BIN)/test_seproot_export_run: test/wcc/989_seproot_export_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_syntaxexport_run — BUG-A residual (#72) at the surviving syntax->wcc -# boundary after the #74 consolidation: syntax's exported fns name frontend -# types, so the producer's check_exported_type (recursing exported-struct -# FIELDS too) rejects until those 16 types carry `export`. Drives BOTH driver -# stages on the real lib/ww/syntax via a qualified-ref root + replays the -# producer on the produced unit (both compilers, export-strip = the bug), so -# it needs both driver + both compiler + both linker stages + libwwrt. -$(BIN)/test_syntaxexport_run: test/wcc/989_syntaxexport_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_sepstructdef_run — sep-build of a dep exporting aggregate-initializer -# defs (N_STRUCTLIT info / N_ARRLIT arr) (#70, BUG-2): the producer must emit a -# value-LESS prototype `export def X: T;` for an aggregate-init def (DATA-global -# per #52), and the def parser must accept that bodyless form. Drives BOTH -# driver stages + reads back the dep `.wwi`, so it needs both driver + both -# compiler + both linker stages + libwwrt for the link. -$(BIN)/test_sepstructdef_run: test/wcc/989_sepstructdef_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_sepcycle_dup — M3-tail commit-4 negative gates (#46, #31): a loud -# dep-cycle reject (both driver stages, cs==ww stderr) + the #31 -# duplicate-symbol reject (both linker stages). Needs both driver stages, -# both compiler stages (w6c/w6a build the dup .o), both linker stages, and -# libwwrt.a (the dup non-vacuity single-.o link). -$(BIN)/test_sepcycle_dup: test/wcc/989_sepcycle_dup.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_separchive_run — M3-tail commit-5a gate (#46, #62): per-package `.a` -# substrate (root=.o force-load, deps=.a) + the archive-path #31 dup-detect -# (PASS 3). Asserts build+run, `.a` determinism (3x), cs.a==ww.a byte-id, -# and the masked-dup reject THROUGH the `.a` path (both stages, achievable -# parity). Needs both driver + both compiler + both linker stages + libwwrt. -$(BIN)/test_separchive_run: test/wcc/989_separchive_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_pkgcache_run — M3-tail commit-5b gate (#46, #63): the out/.pkgcache -# content-keyed package cache. COLD/dev-only (off every byte-id/bootstrap -# gate): MISS compiles, HIT skips + non-vacuously reuses byte-identical -# artifacts, and the key busts on each input class (src / dep .wwi / compiler -# md5 / flags) — both driver stages, with cs==ww cached P.o/P.wwi. Needs both -# driver + both compiler + both linker stages + libwwrt.a. -$(BIN)/test_pkgcache_run: test/wcc/989_pkgcache_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_pkgcache_concurrent_run — #104 smoke: concurrent --sep builds sharing one -# out/.pkgcache all produce correct, byte-identical binaries (not a torn-read -# atomicity guard — that is closed by construction, #105). N concurrent builds -# share one cache + one lib pkg; each binary must be byte-identical to an -# isolated reference + run correctly. COLD/dev-only (off every byte-id/bootstrap -# gate). Needs both driver + compiler + asm + linker stages + libwwrt.a. -$(BIN)/test_pkgcache_concurrent_run: test/wcc/989_pkgcache_concurrent_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_pkgcache_poison_run — #10 (BUG-B): a 0-byte cached P.wwi/P.o under a valid -# P.key (torn producer write) must self-heal — rejected on both lookup (MISS, -# re-derive) and store (never commit a 0-byte). Table-driven over which artifact -# is poisoned {wwi/o/both}, both driver stages. COLD/dev-only (off every byte-id/ -# bootstrap gate). Needs both driver + compiler + asm + linker stages + libwwrt.a. -$(BIN)/test_pkgcache_poison_run: test/wcc/989_pkgcache_poison_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_c6soak_run — M3-tail commit-6 dual-path soak gate, phase-1 leg (#46 -# c6). Builds real lib chains (encoding.utf8 dotted + strings/bytes same-leaf -# `contains`) BOTH combined and --sep, asserts combined==sep run-equivalence -# (the M4 behavioral oracle, NOT binary identity), cs==ww per-pkg .s/.wwi, and -# the §3 distinct-label non-vacuity. Needs both driver + both compiler + both -# linker stages + libwwrt.a. (Tier-C capstone folds into 994_w6c_ww.) -$(BIN)/test_c6soak_run: test/wcc/989_c6soak_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_septest_run — M4 E1 (#79-B) gate: `ww test --sep`. Drives BOTH driver -# stages on a passing + a failing inline @test fixture (COLD per-(case,stage) -# WW_PKGCACHE), asserting run-exit (pass=0, fail=1 = the non-vacuity teeth), -# cs==ww per-pkg .s/.wwi (rule 10), and the synth `CALL test.run` in __root.s -# (the -T synth fired under sep). Needs both driver + both compiler + both -# linker stages + libwwrt.a for the test-binary link. -$(BIN)/test_septest_run: test/wcc/989_septest_run.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_coloimport_sep — #98 E3 flip-blocker: a co-located `_test` entry must -# not let its srcd shadow a self-named transitive `import ` into the -# sibling file. Drives BOTH driver stages on a co-located fixture, asserting -# run-exit 0 (pre-fix the importer's fold makes w6c reject = the non-vacuity -# teeth), the dir-package registers its own .unit.ww/.wwi, and cs==ww per-pkg -# .s/.wwi (rule 10). Needs both driver + compiler + asm + linker stages. -$(BIN)/test_coloimport_sep: test/wcc/989_coloimport_sep.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 989_depmain_sep — #99 E3 flip-blocker: an imported dir-package's non-exported -# `fn main` must mangle on its import path under `ww build --sep`, not emit a -# bare `TEXT main` that collides with the root unit's entry (= w6l dup-symbol). -# Drives BOTH driver stages on dotted- and single-component import fixtures, -# asserting sep build+link+run exit (pre-fix the bare-main collision makes w6l -# reject = the non-vacuity teeth), the dep's main mangles with exactly one bare -# root main, cs==ww per-pkg .s/.wwi (rule 10), and combined unaffected. Needs -# both driver + compiler + asm + linker stages. -$(BIN)/test_depmain_sep: test/wcc/989_depmain_sep.c $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ - $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_bufio_run: test/wcc/998_bufio_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_random_run: test/wcc/999_random_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_checked_run: test/wcc/969_checked_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_defdim_struct_run: test/wcc/951_defdim_struct_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arrlit_slice_run: test/wcc/953_arrlit_slice_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_def_mangle_run: test/wcc/913_def_mangle_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_arr_module_index_run: test/wcc/915_arr_module_index_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_array_init_acceptiffits_run: test/wcc/920_array_init_acceptiffits_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_amp_def_global_run: test/wcc/921_amp_def_global_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_floats_run: test/wcc/952_floats_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_types_sizelim_run: test/wcc/958_types_sizelim_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_types_intlim_run: test/wcc/959_types_intlim_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_opaque_guards: test/wcc/961_opaque_guards.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_sort_run: test/wcc/963_sort_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_f64crossmod_run: test/wcc/953_f64crossmod_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tuprecv_run: test/wcc/954_tuprecv_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -# 956 SLIM PIN (#5-C4): asserttyped stamp dimension only — runs w6c_ww and greps -# its stderr (no run/link). Value rows + byte-id live in test/lang/tuprecv_f64_test.ww. -$(BIN)/test_tuprecv_f64_run: test/wcc/956_tuprecv_f64_run.c $(BIN)/w6c_ww | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_assert_builtin_run: test/wcc/957_assert_builtin_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_modqualdestr_run: test/wcc/956_modqualdestr_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_structparam_run: test/wcc/946_structparam_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_structret_run: test/wcc/946_structret_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -sizelint: - @sh tools/sizelint - -peellint: - @sh tools/peellint - -test-harness: test/run test/run_test.sh - @sh test/run_test.sh - -test: all sizelint peellint test-harness $(TESTS) test-lang test-lang-byteid test-run - @WW=$(BIN)/ww BIN=$(BIN) sh test/run - -test-unit: all sizelint peellint test-harness $(TESTS) - @WW=$(BIN)/ww BIN=$(BIN) UNIT=1 sh test/run - -# ---- test-run: ww-native behavior harness (fold 2) --------------------- -# Drives test/runww.ww over every test/wcc/data//case.ww, building -# and running each case through the cstage `ww` driver (T1, plus the ERROR -# arm's wwstage w6c_ww leg) and checking its leading `//ww:` directive. The -# harness prints `runww: all ok` and exits 0 only if every case passes; the -# target propagates that exit code. Wired into `make test` (pre-push only). -# -# runww execve's its child `ww`/`w6c_ww` with a NIL envp, so a WW_PKGCACHE= -# override can't reach them — the children write the DEFAULT out/.pkgcache, -# the same cache the recipe's $(TESTS)/byte-id gates use. The order-only -# prereqs (after `|`) force `make test -j` to schedule test-run strictly -# LAST among the prereqs, so it cannot run concurrently with any default- -# cache writer (`all`'s selfhost builds are already ordered via the normal -# `all` prereq; test-lang/test-lang-byteid use isolated caches, but ordering -# after them is belt-and-suspenders). The recipe's 990-997/pkgcache default- -# cache tests run only after ALL prereqs complete, so no overlap there. -RUNWW_CASES = $(wildcard test/wcc/data/*/case.ww) -test-run: all | $(TESTS) test-lang test-lang-byteid - @BIN=$(BIN) $(BIN)/ww run test/runww.ww $(RUNWW_CASES) - -# ---- test-lang: in-language @test behavior corpus (fold 2) ------------- -# Runs each test/lang/*_test.ww through the cstage `ww test` subcommand. -# Each file is its own package, so one file per invocation. A failing -# @test exits non-zero and fails the target. Imports (rt/strings) resolve -# via the driver's self_dir/../../lib fallback, so no lib env is needed. -# NOT yet wired into `make test` (fold 6, gated on the T1 corpus). -test-lang: all +# Five in-process C compiler units. Every other surviving C gate has a named +# artifact owner below and is built by one generic rule; source presence is +# the registration, so adding a case never requires a TESTS edit and a second +# per-case Make rule. +UNIT_SOURCES = test/wcc/000_smoke.c test/wcc/100_lex.c \ + test/wcc/200_parse.c test/wcc/300_check.c test/wcc/400_w6c.c +# Byte/artifact residuals that the blanket test-data-byteid comparator +# cannot subsume: asm-pattern/symbol/frame observers, .wwi round-trips, +# inline sources with no corpus twin, and the wwstage-driver-leg gates. +# 61 pure data-fixture byte-id carriers were retired into test-data-byteid +# (fold-4 migrate-and-retire; audit 2026-08-07). +BYTEID_WRAPPER_SOURCES = test/wcc/631_def_neg_global.c \ + test/wcc/753_convwrap_audit.c \ + test/wcc/754_slice_of_slice_index.c test/wcc/755_amp_dot_idx.c \ + test/wcc/756_alias_chain_unwrap.c \ + test/wcc/758_cgalloc_str_field.c \ + test/wcc/785_structvariant_largeunion_return.c \ + test/wcc/786_narrow_alias_deref_store.c \ + test/wcc/789_named_ptr_alias_variant_widen.c \ + test/wcc/790_single_field_struct_zeroinit.c \ + test/wcc/795_xmod_valglobal_run.c \ + test/wcc/796_xmod_valglobal_dot_run.c \ + test/wcc/798_tuple_sret_callee.c \ + test/wcc/815_fmt_int_run.c test/wcc/844_size_untyped_int.c \ + test/wcc/926_tagscr_sizes_run.c test/wcc/930_free_noop_run.c \ + test/wcc/940_global_sret_run.c test/wcc/946_structparam_run.c \ + test/wcc/946_structret_run.c test/wcc/951_defdim_struct_run.c \ + test/wcc/989_m1mangle_sym.c test/wcc/989_m1union_run.c \ + test/wcc/989_m2wwi_run.c test/wcc/989_structlocal_frame.c \ + test/wcc/989_wwileaf_run.c test/wcc/989_lib_byteid.c +BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ + test/wcc/990_selfhost.c test/wcc/991_w6a_ww.c \ + test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \ + test/wcc/994_w6c_ww.c test/wcc/995_self_rebuild.c +PLATFORM_WRAPPER_SOURCES = test/wcc/996_dyn_ww.c +ALL_WRAPPER_SOURCES = $(wildcard test/wcc/*.c) +COMPILER_WRAPPER_SOURCES = $(filter-out $(UNIT_SOURCES) \ + $(BYTEID_WRAPPER_SOURCES) $(BOOTSTRAP_WRAPPER_SOURCES) \ + $(PLATFORM_WRAPPER_SOURCES),$(ALL_WRAPPER_SOURCES)) + +UNIT_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(UNIT_SOURCES)) +BYTEID_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(BYTEID_WRAPPER_SOURCES)) +BOOTSTRAP_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(BOOTSTRAP_WRAPPER_SOURCES)) +PLATFORM_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(PLATFORM_WRAPPER_SOURCES)) +COMPILER_WRAPPER_BINS = $(patsubst test/wcc/%.c,$(BIN)/test_%,$(COMPILER_WRAPPER_SOURCES)) +WRAPPER_TOOLS = $(CSTAGE_BINS) $(WWSTAGE_BINS) $(WWTEST_BIN) $(LIBS) + +$(filter-out $(BIN)/test_400_w6c,$(UNIT_BINS)): $(BIN)/test_%: \ + test/wcc/%.c $(LIB)/libwcc.a | $(BIN) + $(CC) $(CFLAGS) $(INCS) -o $@ $< $(LIB)/libwcc.a + +$(BIN)/test_200_parse $(BIN)/test_300_check: test/wcc/wwtestpkg.h + +$(BIN)/test_400_w6c: test/wcc/400_w6c.c \ + test/wcc/wwtestpkg.h $(OBJ)/w6c/cgen.o $(OBJ)/w6c/txt.o \ + $(LIB)/libwcc.a | $(BIN) + $(CC) $(CFLAGS) $(INCS) -Icmd/w6c -o $@ $< \ + $(OBJ)/w6c/cgen.o $(OBJ)/w6c/txt.o $(LIB)/libwcc.a + +$(BIN)/test_%: test/wcc/%.c test/wcc/wwtestpkg.h $(WRAPPER_TOOLS) | $(BIN) + $(CC) $(CFLAGS) $(INCS) -o $@ $< $(LIB)/libwcc.a + +# Native library tests execute their source owner directly. The retired C +# launchers added no assertion beyond this exit status. +LIBRARY_TESTS = lib/errors/errnotest.ww lib/ascii/asciitest.ww \ + lib/ww/syntax/toktest.ww lib/ww/syntax/asttest.ww \ + lib/strconv/test/ftostest.ww lib/strconv/test/stoftest.ww \ + lib/strconv/test/inttest.ww lib/strings/stringstest.ww \ + lib/bytes/bytestest.ww lib/encoding/utf8/utf8test.ww \ + lib/bufio/bufiotest.ww lib/math/random/random_test.ww \ + lib/math/checked/checked_test.ww lib/fmt/fmttest.ww \ + lib/log/logtest.ww lib/fnmatch/fnmatchtest.ww \ + lib/shlex/shlextest.ww lib/time/timetest.ww \ + lib/encoding/hex/hextest.ww lib/memio/memiotest.ww \ + lib/temp/temptest.ww lib/getopt/getopttest.ww \ + lib/encoding/base32/base32_test.ww \ + lib/encoding/base64/base64_test.ww \ + lib/hash/adler32/adler32_test.ww lib/hash/crc16/crc16_test.ww \ + lib/hash/crc32/crc32_test.ww lib/hash/crc64/crc64_test.ww \ + lib/path/pathtest.ww lib/crypto/sha256/sha256_test.ww \ + lib/hash/siphash/siphash_test.ww + +# These real sources have a standalone-compilation contract that is not +# subsumed by a library @test import owner. +LIBRARY_STANDALONE_SOURCES = lib/io/io.ww lib/strconv/strconv.ww \ + lib/hash/fnv/fnv.ww lib/net/net.ww + +test-unit: $(UNIT_BINS) + @set -e; for t in $(UNIT_BINS); do \ + echo "unit $$t"; $(CURDIR)/$$t; \ + done + +# One compile-only declarative fixture is the ordinary C/WW smoke slice. +test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww + @$(CURDIR)/$(WWFIXTURE_BIN) -j 1 \ + -run compiler.r78_strict_package_main_accept + +# Complete declarative corpus plus the residual native artifact/integration +# gates that cannot be represented by a single compiler fixture. +test-compiler: $(WWFIXTURE_BIN) $(WWFIXTURE_TOOLS) $(COMPILER_WRAPPER_BINS) + @$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS) + @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ + echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ + done + +test-lang: cstage @set -e; for f in $(wildcard test/lang/*_test.ww); do \ - echo "ww test $$f"; WW_PKGCACHE=$(OUT)/langcache $(BIN)/ww test $$f; \ - done; echo "test-lang: all ok" + echo "ww test $$f"; $(BIN)/ww test $$f; \ + done -# ---- test-lang-byteid: T2 cross-stage .s byte-id gate (fold 4) --------- -# For every test/lang/*_test.ww, compiles it TWICE through the SAME cstage -# `ww` orchestrator swapping ONLY the frontend (WW_W6C = w6c vs w6c_ww) and -# asserts every per-package .s (incl the synth-main __root.s) is -# byte-identical cs-vs-ww. This is the rule-10 stage-symmetry gate that -# test/runww.ww:9-15 reserves ("T2"); strictly more sensitive than re-running -# behavior. .s-ONLY by design: .o/.wwi are the assembler's surface (gated by -# 991/992) — diffing them here would false-attribute an assembler bug to cgen. -# WW_W6A/WW_W6L are pinned to the NON-_ww binaries on BOTH legs: the .s is the -# frontend's product (the assembler runs after), so the SOLE variable is -# WW_W6C. Distinct WW_PKGCACHE per leg is mandatory — a shared cache lets the -# ww leg HIT the cs-built package and never run w6c_ww (false GREEN). Depends -# on `all` so both frontends are at HEAD (stale-binary is the #1 false-pass). -# Pre-push (`make test`) ONLY — never test-commit/test-unit (byte-id is being -# demoted to pre-push; a content-keyed T2 would defeat that demotion). -# -# The recipe is parameterized over an input-class = (file-list, build-verb); -# only the test/lang class ships now. SEAM for fold-6: add a second class -# (`ww build --sep` over selfhost/data) plus //ww:error reject-routing (assert -# both legs fail) — no reject cases exist in test/lang today. -LANGBYTEID_DIR = $(OUT)/langbyteid -LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww)) -LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 153 -$(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) -test-lang-byteid: all - @set -e; \ - mkdir -p $(LANGBYTEID_DIR); \ - n=0; \ +test-library: cstage $(BIN)/w6c_ww $(LIBRARY_STANDALONE_SOURCES) + @set -e; for f in $(LIBRARY_TESTS); do \ + echo "ww test $$f"; \ + $(BIN)/ww test -I $(CURDIR)/lib/ww $$f; \ + done + @set -e; for f in $(LIBRARY_STANDALONE_SOURCES); do \ + echo "library compile c $$f"; \ + $(CURDIR)/$(BIN)/w6c $(CURDIR)/$$f > /dev/null; \ + echo "library compile ww $$f"; \ + $(CURDIR)/$(BIN)/w6c_ww $(CURDIR)/$$f > /dev/null; \ + done + +test-native-byteid: $(BYTEID_WRAPPER_BINS) + @set -e; for t in $(BYTEID_WRAPPER_BINS); do \ + echo "byte-id $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ + done + +test-bootstrap-native: $(BOOTSTRAP_WRAPPER_BINS) + @set -e; for t in $(BOOTSTRAP_WRAPPER_BINS); do \ + echo "bootstrap $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ + done + +test-platform: $(PLATFORM_WRAPPER_BINS) + @set -e; for t in $(PLATFORM_WRAPPER_BINS); do \ + echo "platform $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ + done + +# Public composition. The default developer gate is intentionally small; +# byte identity and self-host fixed points are never hidden prerequisites. +test: test-unit test-compiler-smoke + +test-commit: test-unit test-compiler test-package test-lang test-library + +# Compiler-output identity stops after w6c has emitted every package `.s`. +# Driver -S still performs discovery, unit composition, dependency ordering, +# and `.wwi` production; it never invokes w6a, archive creation, or w6l. +LANGBYTEID_DIR = $(OUT)/langbyteid +LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww)) +LANGBYTEID_EXPECTED_MIN = 158 +$(if $(LANGBYTEID_FILES),,$(error test-byteid: empty language corpus)) +test-lang-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww + @work=$$(mktemp -d "$(CURDIR)/$(LANGBYTEID_DIR).XXXXXX") || exit 1; \ + rc=0; n=0; base=; \ for f in $(LANGBYTEID_FILES); do \ - base=$(LANGBYTEID_DIR)/`basename $$f .ww`; \ - rm -rf $$base.cs.sepwork $$base.ww.sepwork $$base.cs.cache $$base.ww.cache; \ - WW_W6C=$(BIN)/w6c WW_W6A=$(BIN)/w6a WW_W6L=$(BIN)/w6l \ - WW_PKGCACHE=$$base.cs.cache \ - $(BIN)/ww $(LANGBYTEID_VERB) -o $$base.cs $$f; \ - WW_W6C=$(BIN)/w6c_ww WW_W6A=$(BIN)/w6a WW_W6L=$(BIN)/w6l \ - WW_PKGCACHE=$$base.ww.cache \ - $(BIN)/ww $(LANGBYTEID_VERB) -o $$base.ww $$f; \ - cs_set=`cd $$base.cs.sepwork && ls *.s | sort`; \ - ww_set=`cd $$base.ww.sepwork && ls *.s | sort`; \ + base=$$work/`basename $$f .ww`; \ + WW_W6C=$(BIN)/w6c $(BIN)/ww test -S -o $$base.cs $$f \ + || { rc=$$?; break; }; \ + WW_W6C=$(BIN)/w6c_ww $(BIN)/ww test -S -o $$base.ww $$f \ + || { rc=$$?; break; }; \ + test -d $$base.cs.sepwork && test -d $$base.ww.sepwork || { \ + echo "test-byteid: FAIL $$f: missing .sepwork"; rc=1; break; }; \ + cs_set=`cd $$base.cs.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \ + ww_set=`cd $$base.ww.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \ if [ "$$cs_set" != "$$ww_set" ]; then \ - echo "test-lang-byteid: FAIL $$f: .s set differs cs-vs-ww"; \ - echo " cs: $$cs_set"; echo " ww: $$ww_set"; exit 1; \ + echo "test-byteid: FAIL $$f: .s set differs"; rc=1; break; \ fi; \ echo "$$cs_set" | grep -qx __root.s || { \ - echo "test-lang-byteid: FAIL $$f: no __root.s emitted"; exit 1; }; \ + echo "test-byteid: FAIL $$f: no __root.s"; rc=1; break; }; \ ns=0; \ for s in $$cs_set; do \ cmp -s $$base.cs.sepwork/$$s $$base.ww.sepwork/$$s || { \ - echo "test-lang-byteid: FAIL $$f: $$s differs cs-vs-ww (rule-10)"; \ - exit 1; }; \ + echo "test-byteid: FAIL $$f: $$s differs"; rc=1; break; }; \ ns=`expr $$ns + 1`; \ done; \ - test $$ns -ge 1 || { echo "test-lang-byteid: FAIL $$f: 0 .s compared"; exit 1; }; \ - echo "ww test-byteid $$f ($$ns .s cs==ww)"; \ - n=`expr $$n + 1`; \ + if [ $$rc -ne 0 ]; then break; fi; \ + test $$ns -ge 1 || { rc=1; break; }; \ + rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \ + echo "test-byteid: FAIL $$f: scratch cleanup"; rc=1; break; }; \ + base=; n=`expr $$n + 1`; \ done; \ - test $$n -ge $(LANGBYTEID_EXPECTED_MIN) || { \ - echo "test-lang-byteid: corpus shrank ($$n < $(LANGBYTEID_EXPECTED_MIN))"; exit 1; }; \ - echo "test-lang-byteid: $$n files cs==ww" + if [ -n "$$base" ]; then \ + rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \ + echo "test-byteid: scratch cleanup failed"; \ + if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + fi; \ + if [ $$rc -eq 0 ] && [ $$n -lt $(LANGBYTEID_EXPECTED_MIN) ]; then \ + echo "test-byteid: corpus shrank ($$n < $(LANGBYTEID_EXPECTED_MIN))"; \ + rc=1; \ + fi; \ + rmdir "$$work" || { echo "test-byteid: workspace cleanup failed"; \ + if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then \ + echo "test-byteid: $$n language files compared at compiler output"; \ + fi; \ + exit $$rc -# ---- test-commit: per-commit default (task #10 T1) --------------------- -# Full suite, but content-key-SKIPS the expensive wwstage byte-id / -# self-compile gates (950/989_lib_byteid/990-995/997) when their input set is -# byte-identical to the last green run — reported as `cached`, never a -# silent skip. NOT a substitute for the push bar: `make test` stays the -# no-skip bootstrap gate. Cache lives in out/.testcache (gitignored, wiped -# by `make clean`); a green `make test` primes it. The host-/usr/lib dynamic -# gate 996 always runs and is never cached. See test/run. -test-commit: all sizelint peellint test-harness $(TESTS) test-lang - @WW=$(BIN)/ww BIN=$(BIN) COMMIT=1 sh test/run +# Blanket compiler-output identity over the declarative fixture corpus: +# every non-error case.ww builds through the fixed cstage driver twice, +# swapping only the frontend (WW_W6C), and every emitted per-package .s +# must be byte-identical. This is the fold-4 end-state comparator that +# retired the per-snippet byte-id C carriers; //ww:error fixtures have no +# .s and their both-stage reject parity is owned by the fixture corpus. +DATABYTEID_DIR = $(OUT)/databyteid +DATABYTEID_FILES = $(wildcard test/wcc/data/*/case.ww) +DATABYTEID_EXPECTED_MIN = 910 +# Known cs!=ww divergences (loud over blind, the 989_lib_byteid DIVERGE +# discipline): each entry must still build on both stages AND still differ. +# When a compiler fix lands the entry fails demanding graduation out of +# this list rather than silently widening coverage. Tracked with the #59 +# divergence family. +DATABYTEID_DIVERGED = r660_i16_out_param_negative r71_tagged_return_scratch_mixed \ + r76_typeeq_fn_diff_arity r76_typeeq_fn_diff_param_type \ + r76_typeeq_fn_io_vtable_shape r76_typeeq_fn_variadic_vs_fixed \ + r940_str_forrange_arg r989_libprecond_decodedsize_aligned +$(if $(DATABYTEID_FILES),,$(error test-byteid: empty data corpus)) +test-data-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww + @work=$$(mktemp -d "$(CURDIR)/$(DATABYTEID_DIR).XXXXXX") || exit 1; \ + rc=0; n=0; pn=0; base=; \ + for f in $(DATABYTEID_FILES); do \ + head -1 $$f | grep -q '^//ww:error' && continue; \ + d=`basename \`dirname $$f\``; \ + base=$$work/$$d; \ + WW_W6C=$(BIN)/w6c $(BIN)/ww build -S -o $$base.cs $$f \ + || { rc=$$?; break; }; \ + WW_W6C=$(BIN)/w6c_ww $(BIN)/ww build -S -o $$base.ww $$f \ + || { rc=$$?; break; }; \ + test -d $$base.cs.sepwork && test -d $$base.ww.sepwork || { \ + echo "test-byteid: FAIL $$f: missing .sepwork"; rc=1; break; }; \ + cs_set=`cd $$base.cs.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \ + ww_set=`cd $$base.ww.sepwork && find . -maxdepth 1 -type f -name '*.s' -printf '%f\n' | sort`; \ + if [ "$$cs_set" != "$$ww_set" ]; then \ + echo "test-byteid: FAIL $$f: .s set differs"; rc=1; break; \ + fi; \ + echo "$$cs_set" | grep -qx __root.s || { \ + echo "test-byteid: FAIL $$f: no __root.s"; rc=1; break; }; \ + ns=0; diffs=0; firstdiff=; \ + for s in $$cs_set; do \ + cmp -s $$base.cs.sepwork/$$s $$base.ww.sepwork/$$s || { \ + diffs=`expr $$diffs + 1`; \ + if [ -z "$$firstdiff" ]; then firstdiff=$$s; fi; }; \ + ns=`expr $$ns + 1`; \ + done; \ + case " $(DATABYTEID_DIVERGED) " in \ + *" $$d "*) \ + if [ $$diffs -eq 0 ]; then \ + echo "test-byteid: $$f: now byte-identical — graduate out of DATABYTEID_DIVERGED"; \ + rc=1; break; \ + fi; \ + pn=`expr $$pn + 1`;; \ + *) \ + if [ $$diffs -ne 0 ]; then \ + echo "test-byteid: FAIL $$f: $$firstdiff differs"; \ + rc=1; break; \ + fi;; \ + esac; \ + test $$ns -ge 1 || { rc=1; break; }; \ + rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \ + echo "test-byteid: FAIL $$f: scratch cleanup"; rc=1; break; }; \ + base=; n=`expr $$n + 1`; \ + done; \ + if [ -n "$$base" ]; then \ + rm -rf -- $$base.cs.sepwork $$base.ww.sepwork || { \ + echo "test-byteid: scratch cleanup failed"; \ + if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + fi; \ + if [ $$rc -eq 0 ] && [ $$n -lt $(DATABYTEID_EXPECTED_MIN) ]; then \ + echo "test-byteid: data corpus shrank ($$n < $(DATABYTEID_EXPECTED_MIN))"; \ + rc=1; \ + fi; \ + rmdir "$$work" || { echo "test-byteid: workspace cleanup failed"; \ + if [ $$rc -eq 0 ]; then rc=1; fi; }; \ + if [ $$rc -eq 0 ]; then \ + echo "test-byteid: $$n data fixtures compared at compiler output ($$pn pinned-divergent)"; \ + fi; \ + exit $$rc -# ---- smoke: inner-loop cross-stage byte-id check on a SMALL input ------ -# Compiles one self-contained fixture through cstage w6c AND wwstage -# w6c_ww and diffs the emitted .s. Catches a cs!=ww emission divergence -# (rule 10) in seconds — the per-fold inner-loop check Rob's cadence -# calls for. NOT a substitute for the full 990-997 byte-id gate, which -# self-compiles the tools via `--sep`: ALWAYS run `make test` before landing -# a cgen/ABI/compiler-lib fold. w6c consumes a flat unit (no import -# resolution), so FIXTURE must be import-free or a pre-resolved sep unit -# (.sepwork/__root.unit.ww). -# make smoke # default: rt/malloc.ww -# make smoke FIXTURE=rt/ensure.ww # any self-contained .ww -FIXTURE ?= rt/malloc.ww -smoke: $(BIN)/w6c $(BIN)/w6c_ww - @$(BIN)/w6c $(FIXTURE) > $(OUT)/smoke.cs.s || { echo "smoke: cstage w6c failed on $(FIXTURE)"; exit 1; } - @$(BIN)/w6c_ww $(FIXTURE) > $(OUT)/smoke.ww.s || { echo "smoke: wwstage w6c_ww failed on $(FIXTURE)"; exit 1; } - @if cmp -s $(OUT)/smoke.cs.s $(OUT)/smoke.ww.s; then \ - echo "smoke OK: cs == ww .s for $(FIXTURE) (`wc -l < $(OUT)/smoke.cs.s` lines)"; \ - else \ - echo "smoke FAIL: cs != ww .s for $(FIXTURE)"; \ - diff -u $(OUT)/smoke.cs.s $(OUT)/smoke.ww.s | head -40; \ - exit 1; \ - fi +test-byteid: test-lang-byteid test-data-byteid test-native-byteid + +test-bootstrap: bootstrap test-bootstrap-native + +test-all: test-commit test-byteid test-bootstrap test-platform test-wwfixture install: all mkdir -p $(PREFIX)/bin $(PREFIX)/lib cp $(BIN)/ww $(PREFIX)/bin/ + cp $(WWTEST_BIN) $(PREFIX)/bin/ cp $(LIB)/libwcc.a $(PREFIX)/lib/ clean: rm -rf $(OUT) -# ---- bootstrap (phase 10) ---------------------------------------------- +# ---- explicit bootstrap ------------------------------------------------ # Four-stage self-host on the SEPARATE-COMPILATION path: Cstage (these C # tools) builds the wwstage tools, then the wwstage compiler reproduces # itself three times with cmp ww2 == ww3 == ww4. @@ -2948,20 +704,17 @@ bootstrap: all @echo @echo "=== stage 2: ww2 = stage-1 w6c_ww sep-compiles w6c ===" @WW_W6C=$(BIN)/w6c_ww WW_W6A=$(BIN)/w6a_ww WW_W6L=$(BIN)/w6l_ww \ - WW_PKGCACHE=$(BS)/pc2 \ - $(BIN)/ww build --sep -o $(BS)/ww2 $(W6CINC) $(W6CSRC) + $(BIN)/ww build -o $(BS)/ww2 $(W6CINC) $(W6CSRC) @ls -l $(BS)/ww2 @echo @echo "=== stage 3: ww3 = ww2 sep-compiles w6c ===" @WW_W6C=$(BS)/ww2 WW_W6A=$(BIN)/w6a_ww WW_W6L=$(BIN)/w6l_ww \ - WW_PKGCACHE=$(BS)/pc3 \ - $(BIN)/ww build --sep -o $(BS)/ww3 $(W6CINC) $(W6CSRC) + $(BIN)/ww build -o $(BS)/ww3 $(W6CINC) $(W6CSRC) @ls -l $(BS)/ww3 @echo @echo "=== stage 4: ww4 = ww3 sep-compiles w6c ===" @WW_W6C=$(BS)/ww3 WW_W6A=$(BIN)/w6a_ww WW_W6L=$(BIN)/w6l_ww \ - WW_PKGCACHE=$(BS)/pc4 \ - $(BIN)/ww build --sep -o $(BS)/ww4 $(W6CINC) $(W6CSRC) + $(BIN)/ww build -o $(BS)/ww4 $(W6CINC) $(W6CSRC) @ls -l $(BS)/ww4 @echo @echo "=== fixed-point gates ===" @@ -3056,4 +809,9 @@ nocc: @echo @echo "NOCC OK: $(STAGE0)/* reproduces itself from source. cc not invoked." -.PHONY: all cstage wwstage test test-unit test-commit test-harness test-run test-lang test-lang-byteid smoke sizelint peellint install clean bootstrap nocc bootstrap-snapshot +.PHONY: all cstage wwstage wwfixture test-wwfixture-process test-wwfixture \ + test test-unit test-compiler-smoke test-compiler test-package \ + test-lang test-library test-commit test-lang-byteid \ + test-data-byteid test-native-byteid test-byteid \ + test-bootstrap-native test-bootstrap \ + test-platform test-all install clean bootstrap nocc bootstrap-snapshot diff --git a/test/wcc/701_cgassign_struct.c b/test/wcc/701_cgassign_struct.c deleted file mode 100644 index 22e29227..00000000 --- a/test/wcc/701_cgassign_struct.c +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 701_cgassign_struct — whole-struct receive ABI for sizes <= 24B - * (receive side of #4's cgreturn, task #5 #27). - * - * #4 wired the producer side: `return s;` materialises bytes into - * AX (bytes 0..7), DX (8..15), CX (16..23), zero-padded to 24B. The - * #4 test (698_cgreturn_struct.c) only pins that cgreturn doesn't - * crash; the result is discarded by the caller. #5 wires the - * receive side end-to-end: - * - * - `let s: foo = bar()` — N_LET call-result rhs lands AX/DX/CX - * into the slot with sized stores (MOVQ for full 8B chunks, - * MOVL/MOVB/MOVW tail by *declared* struct size — the receiver - * must NOT mirror the sender's three uniform MOVQs, else - * trailing 1..7 bytes overrun the next slot). - * - `let s: foo = foo{...}` — already wired by #4's predecessor; - * this test pins the value path. - * - `s = bar()` reassign — symmetric N_ASSIGN N_IDENT-lhs. - * - `s = foo{...}` reassign — symmetric N_STRUCTLIT shape. - * - * What this test pins: - * - cstage and wwstage emit byte-identical asm for every fixture - * (catches any drift in the receive-side store sequence). - * - END-TO-END VALUE CORRECTNESS: each fixture's main returns a - * deterministic exit code derived from the received struct's - * fields. A regression in the receive ABI (e.g., DX→+0 instead - * of +8, or an over-wide tail MOVQ overrunning the slot) shows - * up as a `got != want` exit-code mismatch, not a no-crash - * pass. - * - * Coverage: - * - 8B (one_i64): smallest struct, only AX is meaningful. - * - 16B (pair_i64): two-word AX/DX. - * - 20B (five_i32): three-word AX/DX/CX with MOVL tail — the - * headline ASYMMETRY case. A naïve MOVQ tail here would stomp - * 4 bytes past the slot. - * - 24B (trip_i64): three-word, no tail. - * - 24B mix (i32+i32+i64+i64): registerstruct-packed shape. - * - * Each shape covers two rhs forms (call-result, struct-literal) - * across two lvalue forms (let-init, reassign). - * - * Sizes >24B are OUT OF SCOPE — sret is deferred (same constraint - * as #4). Tail chunks in {3,5,6,7} are unreachable under WW - * struct alignment rules (field aligns force size%align == 0); the - * receive branches guard those out and fall through. - */ -#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[] = { - /* 8B, call-result let-init. exit = s.v (= 7). */ - { "one_i64_let_call", - "type one = struct { v: i64 };\n" - "fn mk() one = { return one { v = 7i64 }; };\n" - "fn main() i32 = {\n" - " let s: one = mk();\n" - " return s.v: i32;\n" - "};\n", - 7 }, - /* 8B, struct-literal let-init. exit = s.v (= 11). */ - { "one_i64_let_lit", - "type one = struct { v: i64 };\n" - "fn main() i32 = {\n" - " let s: one = one { v = 11i64 };\n" - " return s.v: i32;\n" - "};\n", - 11 }, - /* 8B, call-result reassign. exit = s.v (= 13). */ - { "one_i64_reassign_call", - "type one = struct { v: i64 };\n" - "fn mk() one = { return one { v = 13i64 }; };\n" - "fn main() i32 = {\n" - " let s: one = one { v = 0i64 };\n" - " s = mk();\n" - " return s.v: i32;\n" - "};\n", - 13 }, - /* 8B, struct-literal reassign. exit = s.v (= 17). */ - { "one_i64_reassign_lit", - "type one = struct { v: i64 };\n" - "fn main() i32 = {\n" - " let s: one = one { v = 0i64 };\n" - " s = one { v = 17i64 };\n" - " return s.v: i32;\n" - "};\n", - 17 }, - /* 16B, call-result let-init. exit = a + b (= 3 + 5 = 8). - * Pins that DX lands at +8 and AX at +0. */ - { "pair_i64_let_call", - "type pair = struct { a: i64, b: i64 };\n" - "fn mk() pair = { return pair { a = 3i64, b = 5i64 }; };\n" - "fn main() i32 = {\n" - " let s: pair = mk();\n" - " return (s.a + s.b): i32;\n" - "};\n", - 8 }, - /* 16B, struct-literal reassign. exit = a + b (= 4 + 6 = 10). */ - { "pair_i64_reassign_lit", - "type pair = struct { a: i64, b: i64 };\n" - "fn main() i32 = {\n" - " let s: pair = pair { a = 0i64, b = 0i64 };\n" - " s = pair { a = 4i64, b = 6i64 };\n" - " return (s.a + s.b): i32;\n" - "};\n", - 10 }, - /* 20B (five-i32), call-result let-init — the ASYMMETRY - * headline case. AX bytes 0..7 hold a,b. DX bytes 8..15 hold - * c,d. CX low 4 bytes hold e; CX upper 4 bytes are pad zero. - * The receiver MUST store CX as MOVL (4B), not MOVQ (8B); - * a MOVQ would overrun into the next local slot. Sum check: - * 1+2+3+4+5 = 15. */ - { "five_i32_let_call", - "type five = struct { a: i32, b: i32, c: i32, d: i32, e: i32 };\n" - "fn mk() five = {\n" - " return five { a = 1, b = 2, c = 3, d = 4, e = 5 };\n" - "};\n" - "fn main() i32 = {\n" - " let s: five = mk();\n" - " return s.a + s.b + s.c + s.d + s.e;\n" - "};\n", - 15 }, - /* 20B (five-i32), struct-literal reassign. Same tail-MOVL - * pin as above but exercises the structlit-field-walk path. */ - { "five_i32_reassign_lit", - "type five = struct { a: i32, b: i32, c: i32, d: i32, e: i32 };\n" - "fn main() i32 = {\n" - " let s: five = five { a = 0, b = 0, c = 0, d = 0, e = 0 };\n" - " s = five { a = 2, b = 4, c = 6, d = 8, e = 10 };\n" - " return s.a + s.b + s.c + s.d + s.e;\n" - "};\n", - 30 }, - /* 24B, call-result let-init — the headline three-word ABI. - * AX/DX/CX each carry one full word, no tail. Sum check: - * 11+22+33 = 66. */ - { "trip_i64_let_call", - "type trip = struct { x: i64, y: i64, z: i64 };\n" - "fn mk() trip = { return trip { x = 11i64, y = 22i64, z = 33i64 }; };\n" - "fn main() i32 = {\n" - " let s: trip = mk();\n" - " return (s.x + s.y + s.z): i32;\n" - "};\n", - 66 }, - /* 24B, struct-literal reassign. Same word-shape as above. */ - { "trip_i64_reassign_lit", - "type trip = struct { x: i64, y: i64, z: i64 };\n" - "fn main() i32 = {\n" - " let s: trip = trip { x = 0i64, y = 0i64, z = 0i64 };\n" - " s = trip { x = 14i64, y = 28i64, z = 42i64 };\n" - " return (s.x + s.y + s.z): i32;\n" - "};\n", - 84 }, - /* 24B mix (registerstruct-packed): {i32, i32, i64, i64}. - * Layout: a@+0, b@+4, c@+8, d@+16 (totsize 24). AX carries - * a+b packed, DX = c, CX = d. Sum check: 1+2+3+4 = 10. */ - { "mix_let_call", - "type mix = struct { a: i32, b: i32, c: i64, d: i64 };\n" - "fn mk() mix = { return mix { a = 1, b = 2, c = 3i64, d = 4i64 }; };\n" - "fn main() i32 = {\n" - " let s: mix = mk();\n" - " return (s.a + s.b) + (s.c + s.d): i32;\n" - "};\n", - 10 }, - /* dst.field = call() — single-dot struct-typed field via - * local struct base. Exercises the cgen.c:1996+ N_DOT.N_IDENT - * struct-field branch (cstage) and cgenexpr.ww:~4046 single- - * dot local-base site (wwstage). Sum check: 3+4+0 = 7. */ - { "field_local_call", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn mki() inner = { return inner { a = 3i64, b = 4i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 0i64 };\n" - " o.i = mki();\n" - " return (o.i.a + o.i.b + o.t): i32;\n" - "};\n", - 7 }, - /* dst.field = T{...} — same single-dot site, struct-literal - * rhs. Sum check: 11+22+5 = 38. */ - { "field_local_lit", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn main() i32 = {\n" - " let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 5i64 };\n" - " o.i = inner { a = 11i64, b = 22i64 };\n" - " return (o.i.a + o.i.b + o.t): i32;\n" - "};\n", - 38 }, - /* dst.field = call() via *struct base (auto-deref through - * pointer). Exercises cgenexpr.ww:~3752 single-dot ptr-base - * site (wwstage). Sum check: 30+40+100 = 170. */ - { "field_ptr_call", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn mki() inner = { return inner { a = 30i64, b = 40i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 100i64 };\n" - " let p: *outer = &o;\n" - " p.i = mki();\n" - " return (o.i.a + o.i.b + o.t): i32;\n" - "};\n", - 170 }, - /* dst.field = T{...} via *struct base, struct-literal rhs. - * Sum check: 7+8+50 = 65. */ - { "field_ptr_lit", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn main() i32 = {\n" - " let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 50i64 };\n" - " let p: *outer = &o;\n" - " p.i = inner { a = 7i64, b = 8i64 };\n" - " return (o.i.a + o.i.b + o.t): i32;\n" - "};\n", - 65 }, - /* dst.field = call() via top-level let-global base. - * Exercises cgenexpr.ww:~4352 single-dot global-base site - * (wwstage) and the corresponding cstage is_global path. - * `let g: T;` (no init) avoids the module-name-mangle path - * that initialised globals hit (cf. task #17). Sum check: - * 70+80+100 = 250 (≤ 255, fits in process exit code). */ - { "field_global_call", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "let g: outer;\n" - "fn mki() inner = { return inner { a = 70i64, b = 80i64 }; };\n" - "fn main() i32 = {\n" - " g.t = 100i64;\n" - " g.i = mki();\n" - " return (g.i.a + g.i.b + g.t): i32;\n" - "};\n", - 250 }, - /* Chained `s.f.g = call()` — depth-2 dot lhs. Exercises the - * cstage chained walker (cgen.c:~2626) and wwstage walker - * (cgenexpr.ww:~4381). The initialiser uses explicit field - * writes (not a nested struct-literal), since cgen's nested- - * STRUCTLIT-as-field-value path is a separate gap. Bare - * `let o: outer;` zero-fills the slot. Sums sized to fit a - * process exit code (8 bits): 5+6+10+20 = 41. */ - { "field_chain_call", - "type inner = struct { a: i64, b: i64 };\n" - "type middle = struct { in: inner, t: i64 };\n" - "type outer = struct { m: middle, x: i64 };\n" - "fn mki() inner = { return inner { a = 5i64, b = 6i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer;\n" - " o.m.t = 10i64;\n" - " o.x = 20i64;\n" - " o.m.in = mki();\n" - " return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n" - "};\n", - 41 }, - /* Chained `s.f.g = T{...}` — depth-2 struct-literal rhs. - * Sum check: 11+22+10+20 = 63. */ - { "field_chain_lit", - "type inner = struct { a: i64, b: i64 };\n" - "type middle = struct { in: inner, t: i64 };\n" - "type outer = struct { m: middle, x: i64 };\n" - "fn main() i32 = {\n" - " let o: outer;\n" - " o.m.t = 10i64;\n" - " o.x = 20i64;\n" - " o.m.in = inner { a = 11i64, b = 22i64 };\n" - " return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n" - "};\n", - 63 }, -}; - -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/wcas_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcas_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcas_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Catches receive-side codegen drift between the - * stages, which 995_self_rebuild covers globally but doesn't surface - * as a focused-fixture failure. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcas_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcas_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcas_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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; - - /* Compile+run for each (driver, row). */ - for (int d = 0; drivers[d].name; d++) { - if (drivers[d].gated_on_existence - && access(drivers[d].path, X_OK) != 0) { - fprintf(stderr, "cgassign_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, - "cgassign_struct[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - /* Asm byte-identity diff, only when both stages exist. */ - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "cgassign_struct: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("cgassign_struct: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/702_dot_explicit_deref.c b/test/wcc/702_dot_explicit_deref.c deleted file mode 100644 index c1a012c4..00000000 --- a/test/wcc/702_dot_explicit_deref.c +++ /dev/null @@ -1,292 +0,0 @@ -/* - * 702_dot_explicit_deref — silent miscompile of `(*p).f` (task #16). - * - * Pre-existing landmine surfaced by worker-cgnassign during #5 - * (whole-struct N_ASSIGN). Both cstage cgen.c N_ASSIGN N_DOT and - * wwstage cgenexpr.ww cgassign single-dot dispatch gated on - * `n->lhs->lhs->kind == N_IDENT`. The parser produces N_UN(STAR, - * IDENT(p)) for `(*p).f`, so the store fell off the dispatch and - * silently emitted nothing. Read-side N_DOT had the same gap — - * the pointer-auto-deref load branch required N_IDENT and fell - * through to cgexpr on the N_UN, which derefs the pointer as a - * scalar (load 8B from p into AX, then the field part is dropped). - * - * Not exercised by current selfhost source (auto-deref `p.f` was - * used throughout) so 995_self_rebuild didn't catch it. Latent for - * any future user code that types out the explicit deref. - * - * The fix in both stages is a base/lhs retarget: when the parser - * shape is N_UN(STAR, IDENT(p)), substitute the inner IDENT so - * the existing via_ptr / pointer-auto-deref branch fires the same - * as `p.f`. v1 scope is bare-IDENT inner only; `(*expr).f` with a - * non-IDENT pointer expression (`(*arr[i]).f`, `(*g.p).f`) still - * drops silently, tracked as a follow-up — needs cgexpr(inner) → - * reg + RHS-spill scheduling, which is a different design. - * - * Each row pins: - * - cstage value correctness (process exit code). - * - wwstage value correctness (when ww_ww exists). - * - cstage vs wwstage byte-identical .s output (catches drift). - */ -#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[] = { - /* Write side, scalar i64 field via explicit deref. - * Pre-fix: cgassign single-dot dispatch fell off (base.kind - * was N_UN, not N_IDENT) and emitted no store. f stayed 0. */ - { "write_i64", - "type pt = struct { x: i64, y: i64 };\n" - "fn main() i32 = {\n" - " let s: pt = pt { x = 0i64, y = 0i64 };\n" - " let p: *pt = &s;\n" - " (*p).x = 7i64;\n" - " (*p).y = 35i64;\n" - " return (s.x + s.y): i32;\n" - "};\n", - 42 }, - /* Write side, narrow i32 field. Pins fldstoreop sizing on - * the via_ptr path — MOVL not MOVQ (which would stomp 4 bytes - * past the field). */ - { "write_i32", - "type box = struct { a: i32, b: i32, c: i32 };\n" - "fn main() i32 = {\n" - " let s: box = box { a = 0, b = 0, c = 0 };\n" - " let p: *box = &s;\n" - " (*p).a = 5;\n" - " (*p).b = 11;\n" - " (*p).c = 19;\n" - " return s.a + s.b + s.c;\n" - "};\n", - 35 }, - /* Write side, str field. Pins the str-typed field branch in - * the via_ptr path (cgexpr → AX=ptr/BX=len, store both halves - * at +0/+8 — not just AX). Initialiser uses `let v: box;` - * (zero-fill) instead of a struct literal because N_LET - * N_STRUCTLIT silently drops the .len half of a str-typed - * field — a separate pre-existing bug, out of scope here. */ - { "write_str", - "type box = struct { s: str, n: i64 };\n" - "fn main() i32 = {\n" - " let v: box;\n" - " let p: *box = &v;\n" - " (*p).s = \"hi\";\n" - " (*p).n = 40i64;\n" - " return (v.s.len: i64 + v.n): i32;\n" - "};\n", - 42 }, - /* Read side, scalar i64 field via explicit deref. - * Pre-fix: case N_DOT's pointer-auto-deref branch required - * N_IDENT and fell through to `cgexpr(c, n->lhs, ...)` — - * derefed the pointer as if loading a scalar, so AX held the - * first qword of the struct (s.x = 50). Field offset for .y - * (+8) was dropped → returned 50 instead of 80. */ - { "read_i64", - "type pt = struct { x: i64, y: i64 };\n" - "fn main() i32 = {\n" - " let s: pt = pt { x = 50i64, y = 80i64 };\n" - " let p: *pt = &s;\n" - " return (*p).y: i32;\n" - "};\n", - 80 }, - /* Read side, narrow i32 field. Pins fldloadop sizing — MOVL - * with sign-extend, not raw MOVQ. */ - { "read_i32", - "type box = struct { a: i32, b: i32, c: i32 };\n" - "fn main() i32 = {\n" - " let s: box = box { a = 1, b = 2, c = 3 };\n" - " let p: *box = &s;\n" - " return (*p).a + (*p).b + (*p).c;\n" - "};\n", - 6 }, - /* Read side, str field. Pins the str-typed field load path — - * AX=ptr, BX=len. .len is the receive-side check. Initialiser - * uses write-by-field (same str-in-structlit-init bug avoided - * as in write_str). */ - { "read_str_len", - "type box = struct { s: str };\n" - "fn main() i32 = {\n" - " let v: box;\n" - " v.s = \"hello\";\n" - " let p: *box = &v;\n" - " return (*p).s.len: i32;\n" - "};\n", - 5 }, - /* Mixed: write via (*p).f then read via (*p).f. End-to-end - * round-trip pins both sides agreeing on the same offset and - * width. */ - { "roundtrip", - "type pt = struct { x: i32, y: i32, z: i32 };\n" - "fn main() i32 = {\n" - " let s: pt = pt { x = 0, y = 0, z = 0 };\n" - " let p: *pt = &s;\n" - " (*p).x = 4;\n" - " (*p).y = 7;\n" - " (*p).z = 13;\n" - " return (*p).x + (*p).y + (*p).z;\n" - "};\n", - 24 }, -}; - -/* run_driver — compile r->src via the given driver and exec; return - * the process exit code. Mirror of 701's helper. */ -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/wded_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wded_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wded_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Mirror of 701. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wded_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wded_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wded_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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_explicit_deref: 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_explicit_deref[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "dot_explicit_deref: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("dot_explicit_deref: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/703_nested_structlit.c b/test/wcc/703_nested_structlit.c deleted file mode 100644 index 33c2310b..00000000 --- a/test/wcc/703_nested_structlit.c +++ /dev/null @@ -1,285 +0,0 @@ -/* - * 703_nested_structlit — silent zero of nested STRUCTLIT fields - * (task #17). - * - * Pre-existing landmine surfaced by worker-cgnassign during #5. - * For a struct literal whose field value is itself an N_STRUCTLIT - * of a struct-typed field, the outer field-walk did - * `cgexpr(field_value); store-AX-sized` — cgexpr has no whole- - * struct-in-register convention, so for a nested literal it lands - * AX = first qword and the trailing bytes silently stay zero (or - * stack garbage in the hostile case). The same shape applies at - * three sites in each stage: - * - N_LET N_STRUCTLIT — `let o: outer = outer { i = inner{...} }` - * - N_ASSIGN N_IDENT-lhs N_STRUCTLIT — `o = outer { i = inner{...} }` - * - N_RETURN N_STRUCTLIT — `return outer { i = inner{...} }` - * - * Workaround used in 701_cgassign_struct rows that need nested - * shape: `let o: outer; o.f = ...` (write-by-field). Avoids the - * bug; doesn't fix it. - * - * Fix: a shared helper `cg_structlit_fill_bp` (cstage) / - * `cgstructlitfillbp` (wwstage) factors the field-walk, recursing - * when a field's value is itself an N_STRUCTLIT for a struct-typed - * field. Bp-relative addressing only; the N_ASSIGN N_DOT-lhs - * structlit walks (via_ptr / global) keep their inline field-walk - * and still drop nested-STRUCTLIT silently — separate follow-up - * task ("cgen: nested STRUCTLIT in N_ASSIGN N_DOT structlit"). - * - * Each row pins: - * - cstage value correctness (process exit code). - * - wwstage value correctness (when ww_ww exists). - * - cstage vs wwstage byte-identical .s output (catches drift). - */ -#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_LET nested structlit, all i64 fields. Pre-fix: only o.i.a - * (first qword) was stored; o.i.b silently stayed 0. Want: - * 7 + 8 + 12 = 27. */ - { "let_nested_i64", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn main() i32 = {\n" - " let o: outer = outer { i = inner { a = 7i64, b = 8i64 }, t = 12i64 };\n" - " return (o.i.a + o.i.b + o.t): i32;\n" - "};\n", - 27 }, - /* N_LET nested structlit, multi-level (3-deep). Pre-fix: - * only o.m.in.a was stored; b and t and outer.x silently 0. - * Want: 1+2+3+4 = 10. */ - { "let_nested_3deep", - "type leaf = struct { a: i64, b: i64 };\n" - "type middle = struct { in: leaf, t: i64 };\n" - "type outer = struct { m: middle, x: i64 };\n" - "fn main() i32 = {\n" - " let o: outer = outer {\n" - " m = middle {\n" - " in = leaf { a = 1i64, b = 2i64 },\n" - " t = 3i64\n" - " },\n" - " x = 4i64\n" - " };\n" - " return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n" - "};\n", - 10 }, - /* N_LET nested structlit, narrow i32 fields. Pins sized-store - * dispatch in the helper — MOVL not MOVQ. Want: 4+5+6+7 = 22. */ - { "let_nested_i32", - "type inner = struct { a: i32, b: i32 };\n" - "type outer = struct { i: inner, x: i32, y: i32 };\n" - "fn main() i32 = {\n" - " let o: outer = outer { i = inner { a = 4, b = 5 }, x = 6, y = 7 };\n" - " return o.i.a + o.i.b + o.x + o.y;\n" - "};\n", - 22 }, - /* N_ASSIGN N_IDENT-lhs nested structlit. Pre-fix: o.i.b - * stayed 0 after the reassign (only first qword stored). - * Want: 9+11+30 = 50. */ - { "assign_ident_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn main() i32 = {\n" - " let o: outer;\n" - " o = outer { i = inner { a = 9i64, b = 11i64 }, t = 30i64 };\n" - " return (o.i.a + o.i.b + o.t): i32;\n" - "};\n", - 50 }, - /* N_RETURN nested structlit. Pre-fix: only b's first qword - * (its .a) landed in the receive slot; b's .b silently 0. - * Want: 13+17+25 = 55. */ - { "return_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "fn mko() outer = {\n" - " return outer { i = inner { a = 13i64, b = 17i64 }, t = 25i64 };\n" - "};\n" - "fn main() i32 = {\n" - " let r: outer = mko();\n" - " return (r.i.a + r.i.b + r.t): i32;\n" - "};\n", - 55 }, - /* Mixed: inner struct surrounded by other scalar fields, with - * the inner appearing in the MIDDLE of the outer (non-zero - * outer foff for the recursion target). Pins that the helper - * threads `bpoff + outer_foff` correctly. All fields are i64 so - * the wwstage/cstage struct-layout alignment divergence (task - * #15, mixed i32/struct fields) doesn't bite — switch to i32 - * once #15 lands. Sum: 100+1+2+50 = 153. */ - { "let_nested_middle", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { pre: i64, i: inner, post: i64 };\n" - "fn main() i32 = {\n" - " let o: outer = outer {\n" - " pre = 100i64,\n" - " i = inner { a = 1i64, b = 2i64 },\n" - " post = 50i64\n" - " };\n" - " return (o.pre + o.i.a + o.i.b + o.post): i32;\n" - "};\n", - 153 }, -}; - -/* run_driver — compile r->src via the given driver and exec; return - * the process exit code. Mirror of 701/702. */ -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/wcns_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcns_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcns_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Mirror of 701/702. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcns_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcns_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcns_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "nested_structlit: 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_structlit[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "nested_structlit: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("nested_structlit: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/704_dot_structlit.c b/test/wcc/704_dot_structlit.c deleted file mode 100644 index 569a0ddc..00000000 --- a/test/wcc/704_dot_structlit.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * 704_dot_structlit — silent zero of nested STRUCTLIT field in - * cgassign N_DOT-lhs (task #18). - * - * Sister bug to #17. #17 fixed the BP-relative sites (N_LET, - * N_ASSIGN N_IDENT-lhs, N_RETURN) via the cg_structlit_fill_bp / - * cgstructlitfillbp helper. The N_ASSIGN N_DOT-lhs structlit walks - * were left inline because their addressing has extra BX-reload - * concerns (the dst addr is a *struct local or a struct global, - * loaded into BX, and cgexpr clobbers BX between fields). All four - * dot-flavors still emitted `cgexpr(field_value); store-AX-sized` - * for the rhs structlit's fields — and for a struct-typed field - * whose value is itself an N_STRUCTLIT, that lands only the first - * qword while the trailing bytes silently stay zero. - * - * Affected dot-flavors (each with the same silent-zero shape): - * 1. single-dot local-base `o.f = outer { i = inner{...}, ... }` - * 2. single-dot ptr-base `p.f = outer { i = inner{...}, ... }` - * 3. single-dot global-base `g.f = outer { i = inner{...}, ... }` - * 4. chained-dot local `o.x.y = outer { i = inner{...}, ... }` - * 5. chained-dot ptrroot `p.x.y = outer { i = inner{...}, ... }` - * 6. chained-dot global `g.x.y = outer { i = inner{...}, ... }` - * - * Fix (#18): the helper is extended into cg_structlit_fill / - * cgstructlitfill that takes a destination context (mode, srcoff, - * name, disp). The old BP-rel wrapper is preserved byte-identically. - * The four dot-flavor sites in each stage now delegate to the - * helper, which recurses on nested struct-typed N_STRUCTLIT values - * at `disp + foff` and reloads BX before each store for the non-BP - * modes. 995_self_rebuild byte-identity is the load-bearing proof - * that the no-nested case is byte-identical to the pre-#18 inline - * walks. - * - * Each row pins: - * - cstage value correctness (process exit code). - * - wwstage value correctness (when ww_ww exists). - * - cstage vs wwstage byte-identical .s output (catches drift). - */ -#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 local-base: `h.f = outer { i = inner{...}, ... }`. - * Pre-fix: h.f.i.a landed (first qword via cgexpr) but h.f.i.b - * silently stayed 0. Want: 7+8+12 = 27. */ - { "dot_local_lit_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "type holder = struct { f: outer };\n" - "fn main() i32 = {\n" - " let h: holder;\n" - " h.f = outer { i = inner { a = 7i64, b = 8i64 }, t = 12i64 };\n" - " return (h.f.i.a + h.f.i.b + h.f.t): i32;\n" - "};\n", - 27 }, - /* single-dot local-base, 3-deep recursion (outer.m.in). Pins - * that the disp accumulator threads `foff + middle_foff + - * leaf_foff` correctly. Want: 1+2+3+4+9 = 19. */ - { "dot_local_lit_3deep", - "type leaf = struct { a: i64, b: i64 };\n" - "type middle = struct { in: leaf, t: i64 };\n" - "type outer = struct { m: middle, x: i64 };\n" - "type holder = struct { f: outer, pad: i64 };\n" - "fn main() i32 = {\n" - " let h: holder;\n" - " h.f = outer {\n" - " m = middle {\n" - " in = leaf { a = 1i64, b = 2i64 },\n" - " t = 3i64\n" - " },\n" - " x = 4i64\n" - " };\n" - " h.pad = 9i64;\n" - " return (h.f.m.in.a + h.f.m.in.b + h.f.m.t + h.f.x + h.pad): i32;\n" - "};\n", - 19 }, - /* single-dot ptr-base (auto-deref): `p.f = outer { i = inner{...}, ... }`. - * Exercises DST_PTR_LOCAL mode — helper reloads BX from - * srcoff(BP) before zero-fill and before every store. Want: - * 9+11+30 = 50. */ - { "dot_ptr_lit_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "type holder = struct { f: outer };\n" - "fn main() i32 = {\n" - " let h: holder;\n" - " let p: *holder = &h;\n" - " p.f = outer { i = inner { a = 9i64, b = 11i64 }, t = 30i64 };\n" - " return (p.f.i.a + p.f.i.b + p.f.t): i32;\n" - "};\n", - 50 }, - /* single-dot global-base: `g.f = outer { i = inner{...}, ... }`. - * Exercises DST_GLOBAL mode — helper reloads BX via LEAQ - * name(SB) before zero-fill and before every store. `let g: T;` - * (no init) per the task #17 module-name-mangle workaround - * pattern. Want: 13+17+25 = 55. */ - { "dot_global_lit_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "type holder = struct { f: outer };\n" - "let g: holder;\n" - "fn main() i32 = {\n" - " g.f = outer { i = inner { a = 13i64, b = 17i64 }, t = 25i64 };\n" - " return (g.f.i.a + g.f.i.b + g.f.t): i32;\n" - "};\n", - 55 }, - /* chained-dot local (depth-2 lhs): `h.mid.f = outer { i = - * inner{...}, ... }`. Exercises the chained-DOT walker with the - * non-via_cx (local-through-chain) path — disp = base_disp + - * total_off, no BX reload. Want: 4+5+13 = 22. */ - { "dot_chain_lit_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "type holder = struct { f: outer };\n" - "type h2 = struct { mid: holder, pad: i64 };\n" - "fn main() i32 = {\n" - " let h: h2;\n" - " h.mid.f = outer { i = inner { a = 4i64, b = 5i64 }, t = 13i64 };\n" - " return (h.mid.f.i.a + h.mid.f.i.b + h.mid.f.t): i32;\n" - "};\n", - 22 }, - /* chained-dot ptrroot (depth-2 lhs through *struct root): - * `p.mid.f = outer { i = inner{...}, ... }`. Exercises the - * chained-DOT walker with ptrroot=1 — helper mode=1, reloads BX - * from rootoff(BP). Want: 6+7+14 = 27. */ - { "dot_chain_ptr_lit_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "type holder = struct { f: outer };\n" - "type h2 = struct { mid: holder, pad: i64 };\n" - "fn main() i32 = {\n" - " let h: h2;\n" - " let p: *h2 = &h;\n" - " p.mid.f = outer { i = inner { a = 6i64, b = 7i64 }, t = 14i64 };\n" - " return (p.mid.f.i.a + p.mid.f.i.b + p.mid.f.t): i32;\n" - "};\n", - 27 }, - /* chained-dot global (depth-2 lhs through struct global): - * `gg.mid.f = outer { i = inner{...}, ... }`. Exercises the - * chained-DOT walker with isglobal=1 — helper mode=2, reloads - * BX via LEAQ gg(SB). Want: 10+20+31 = 61. */ - { "dot_chain_global_lit_nested", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { i: inner, t: i64 };\n" - "type holder = struct { f: outer };\n" - "type h2 = struct { mid: holder, pad: i64 };\n" - "let gg: h2;\n" - "fn main() i32 = {\n" - " gg.mid.f = outer { i = inner { a = 10i64, b = 20i64 }, t = 31i64 };\n" - " return (gg.mid.f.i.a + gg.mid.f.i.b + gg.mid.f.t): i32;\n" - "};\n", - 61 }, - /* single-dot ptr-base 3-deep — pins disp threading through the - * helper's recursion AND through DST_PTR_LOCAL reloads. Want: - * 1+2+3+4 = 10. */ - { "dot_ptr_lit_3deep", - "type leaf = struct { a: i64, b: i64 };\n" - "type middle = struct { in: leaf, t: i64 };\n" - "type outer = struct { m: middle, x: i64 };\n" - "type holder = struct { f: outer };\n" - "fn main() i32 = {\n" - " let h: holder;\n" - " let p: *holder = &h;\n" - " p.f = outer {\n" - " m = middle {\n" - " in = leaf { a = 1i64, b = 2i64 },\n" - " t = 3i64\n" - " },\n" - " x = 4i64\n" - " };\n" - " return (p.f.m.in.a + p.f.m.in.b + p.f.m.t + p.f.x): i32;\n" - "};\n", - 10 }, -}; - -/* run_driver — compile r->src via the given driver and exec; return - * the process exit code. Mirror of 703. */ -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/wcds_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcds_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcds_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Mirror of 703. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcds_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcds_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcds_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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_structlit: 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_structlit[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "dot_structlit: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("dot_structlit: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/705_nested_call_rhs.c b/test/wcc/705_nested_call_rhs.c deleted file mode 100644 index fc00a504..00000000 --- a/test/wcc/705_nested_call_rhs.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * 705_nested_call_rhs — silent zero of nested struct-typed CALL - * value in the structlit-fill helper (task #20). - * - * Sister bug to #17 / #18. #17 introduced the - * `cg_structlit_fill[_bp]` / `cgstructlitfill[bp]` helper to handle - * nested N_STRUCTLIT field values at BP-rel + dot-lhs sites. #18 - * extended it to the four N_ASSIGN N_DOT-lhs flavors (single-dot - * local/ptr/global, chained-dot through *struct/global). Both fixes - * targeted nested N_STRUCTLIT only. - * - * #20 covers the THIRD silent miscompile: a struct-typed field - * whose VALUE is itself an N_CALL (call returning a struct ≤24B per - * #4's AX/DX/CX cgreturn ABI). Pre-#20 the cgexpr-then-AX-store - * fallthrough inside the helper landed AX = first qword only and - * silently dropped the trailing bytes (DX/CX never made it to the - * destination). - * - * ```ww - * let o: outer = outer { m = mki(), x = 20i64 }; - * // Pre-#20: o.m's first 8 bytes = AX from mki(); rest silently 0. - * ``` - * - * Fix: a new per-field branch in the helper (between the nested- - * STRUCTLIT recursion and the scalar cgexpr-then-AX-store) detects - * `fu->kind == TY_STRUCT && f->lhs->kind == N_CALL` and emits the - * full AX/DX/CX → MOVQ x full + MOVL/MOVW/MOVB tail sequence per - * #4's receive shape. Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` mirrors - * #4 — >24B and fsz%8 ∈ {3,5,6,7} fall through (sret / shift-store - * not yet wired; tracked as a follow-up). - * - * Coverage: - * - tail dispatch: 8/16/24 (tail==0), 12 (MOVL tail==4), - * 10 (MOVW tail==2 — load-bearing), 9 (MOVB tail==1). - * - dst modes: BP-rel (N_LET initializer), PTR_LOCAL (`p.f = ...` - * where p: *holder). Global + chained dst already covered by - * #18's per-mode BX-reload tests; the call branch reuses the - * same reload cadence so a subset suffices. - * - depth: one shallow (call directly under outer literal), one - * 3-deep (call in a literal in a literal under outer) to pin - * `disp + foff` threading through the helper's recursion. - * - * Each row pins: - * - cstage value correctness (process exit code). - * - wwstage value correctness (when ww_ww exists). - * - cstage vs wwstage byte-identical .s output (catches drift). - * The MOVW-tail==2 row is the load-bearing one — that's where - * a divergence between the two stages' new branches is most - * plausible. - */ -#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[] = { - /* BP-rel, inner struct 8B (one i64 field). fsz=8, full=1, - * tail=0 — AX only, no tail. Pre-#20: o.m.a still landed - * (it WAS in AX). Bug masked at this size. Want: 7+20 = 27. */ - { "bp_call_8b", - "type inner = struct { a: i64 };\n" - "type outer = struct { m: inner, x: i64 };\n" - "fn mki() inner = { return inner { a = 7i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer { m = mki(), x = 20i64 };\n" - " return (o.m.a + o.x): i32;\n" - "};\n", - 27 }, - /* BP-rel, inner struct 16B (two i64 fields). fsz=16, full=2, - * tail=0 — AX + DX, no tail. Pre-#20: o.m.b silently 0. - * Want: 7+8+20 = 35. */ - { "bp_call_16b", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { m: inner, x: i64 };\n" - "fn mki() inner = { return inner { a = 7i64, b = 8i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer { m = mki(), x = 20i64 };\n" - " return (o.m.a + o.m.b + o.x): i32;\n" - "};\n", - 35 }, - /* BP-rel, inner struct 24B (three i64 fields, the headline - * #4 ABI shape). fsz=24, full=3, tail=0 — AX + DX + CX. - * Pre-#20: o.m.b and o.m.c silently 0. Want: 1+2+3+20 = 26. */ - { "bp_call_24b", - "type inner = struct { a: i64, b: i64, c: i64 };\n" - "type outer = struct { m: inner, x: i64 };\n" - "fn mki() inner = { return inner { a = 1i64, b = 2i64, c = 3i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer { m = mki(), x = 20i64 };\n" - " return (o.m.a + o.m.b + o.m.c + o.x): i32;\n" - "};\n", - 26 }, - /* BP-rel, inner 12B (three i32 fields, maxalign=4 → no - * trailing pad). fsz=12, full=1, tail=4 — AX MOVQ + DX MOVL. - * Pins the MOVL-tail dispatch. - * - * The `x: i64` outer field forces outer.maxalign=8 so cstage - * and wwstage agree on outer.totsize (24). Without that, - * wwstage rounds outer.totsize up to 8 (task #15 pre-existing - * struct-sizing divergence) and the BP offsets in the asm - * diverge across stages. Sidestep, not a fix for #15. Want: - * 4+5+6+20 = 35. */ - { "bp_call_12b_movl_tail", - "type inner = struct { a: i32, b: i32, c: i32 };\n" - "type outer = struct { m: inner, x: i64 };\n" - "fn mki() inner = { return inner { a = 4, b = 5, c = 6 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer { m = mki(), x = 20i64 };\n" - " return (o.m.a: i64 + o.m.b: i64 + o.m.c: i64 + o.x): i32;\n" - "};\n", - 35 }, - /* BP-rel, inner 10B (five i16 fields, maxalign=2 → no - * trailing pad). fsz=10, full=1, tail=2 — AX MOVQ + DX MOVW. - * Rob's load-bearing row: pins MOVW emission on both stages. - * A divergence (cstage emits MOVL/MOVQ tail, wwstage emits - * MOVW) would fail the asm-diff. Same `x: i64` outer-maxalign - * sidestep as the 12B row. Want: 2+3+5+7+11+30 = 58. - * - * Note: the producer (mki's cgreturn) still uses the {1→MOVB, - * 4→MOVL, else MOVQ} field-store dispatch from #17 (task #13). - * For i16 fields this stomps 8 bytes per write, but write- - * order at monotonically increasing field offsets means each - * i16's low 2 bytes stay intact (later MOVQs only clobber - * higher offsets). Receive side reads only the low 2 bytes - * via MOVW tail — so the round-trip value is correct. */ - { "bp_call_10b_movw_tail", - "type inner = struct { a: i16, b: i16, c: i16, d: i16, e: i16 };\n" - "type outer = struct { m: inner, x: i64 };\n" - "fn mki() inner = {\n" - " return inner { a = 2i16, b = 3i16, c = 5i16, d = 7i16, e = 11i16 };\n" - "};\n" - "fn main() i32 = {\n" - " let o: outer = outer { m = mki(), x = 30i64 };\n" - " return (o.m.a: i64 + o.m.b: i64 + o.m.c: i64 + o.m.d: i64\n" - " + o.m.e: i64 + o.x): i32;\n" - "};\n", - 58 }, - /* BP-rel, inner 9B (nine i8 fields, maxalign=1 → no - * trailing pad). fsz=9, full=1, tail=1 — AX MOVQ + DX MOVB. - * Pins the MOVB-tail dispatch. Same `x: i64` outer-maxalign - * sidestep. Want: 1+2+3+4+5+6+7+8+9+40 = 85. */ - { "bp_call_9b_movb_tail", - "type inner = struct {\n" - " a: i8, b: i8, c: i8, d: i8, e: i8,\n" - " f: i8, g: i8, h: i8, i: i8\n" - "};\n" - "type outer = struct { m: inner, x: i64 };\n" - "fn mki() inner = {\n" - " return inner {\n" - " a = 1i8, b = 2i8, c = 3i8, d = 4i8, e = 5i8,\n" - " f = 6i8, g = 7i8, h = 8i8, i = 9i8\n" - " };\n" - "};\n" - "fn main() i32 = {\n" - " let o: outer = outer { m = mki(), x = 40i64 };\n" - " return (o.m.a: i64 + o.m.b: i64 + o.m.c: i64 + o.m.d: i64\n" - " + o.m.e: i64 + o.m.f: i64 + o.m.g: i64 + o.m.h: i64\n" - " + o.m.i: i64 + o.x): i32;\n" - "};\n", - 85 }, - /* PTR_LOCAL: `p.f = outer { m = mki(), x = ... }` where - * p: *holder. Exercises mode=DST_PTR_LOCAL — helper reloads - * BX from srcoff(BP) before the AX/DX/CX stores (cgexpr - * clobbers BX during the call). 16B inner pins the BX reload - * + 2-MOVQ store sequence. Want: 13+17+50 = 80. */ - { "ptrlocal_call_16b", - "type inner = struct { a: i64, b: i64 };\n" - "type outer = struct { m: inner, x: i64 };\n" - "type holder = struct { f: outer };\n" - "fn mki() inner = { return inner { a = 13i64, b = 17i64 }; };\n" - "fn main() i32 = {\n" - " let h: holder;\n" - " let p: *holder = &h;\n" - " p.f = outer { m = mki(), x = 50i64 };\n" - " return (p.f.m.a + p.f.m.b + p.f.x): i32;\n" - "};\n", - 80 }, - /* BP-rel 3-deep: call buried two levels under the outer - * literal. Pins that the `disp + foff` accumulator threads - * correctly through the helper's recursion into the - * call-rhs branch. Inner_in is 16B; middle wraps it + - * an i64; outer wraps middle + an i64. Want: 9+11+30+50 = 100. */ - { "bp_call_3deep_16b", - "type leaf = struct { a: i64, b: i64 };\n" - "type middle = struct { in: leaf, t: i64 };\n" - "type outer = struct { m: middle, x: i64 };\n" - "fn mki() leaf = { return leaf { a = 9i64, b = 11i64 }; };\n" - "fn main() i32 = {\n" - " let o: outer = outer {\n" - " m = middle { in = mki(), t = 30i64 },\n" - " x = 50i64\n" - " };\n" - " return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n" - "};\n", - 100 }, -}; - -/* run_driver — compile r->src via the given driver and exec; return - * the process exit code. Mirror of 703/704. */ -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/wcnc_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcnc_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcnc_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Mirror of 703/704. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcnc_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcnc_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcnc_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "nested_call_rhs: 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_call_rhs[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "nested_call_rhs: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("nested_call_rhs: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/707_cgreturn_variant_zero.c b/test/wcc/707_cgreturn_variant_zero.c deleted file mode 100644 index a7970e25..00000000 --- a/test/wcc/707_cgreturn_variant_zero.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * 707_cgreturn_variant_zero — tagged-return ABI variant-widen zero-pad - * for unused AX/DX/CX/R8 words (task #18) plus aliased-tagged variant- - * index lookup (task #20) plus float-variant payload-from-X0 pack - * (#157, rows 10-12): the same scalar-variant arm packed stale AX into - * the payload word for an f64/f32 value (which lives in X0), not the - * float bits — fixed by an X0->stack->DX spill bridge. - * - * Pre-fix (#18): cgreturn's `!istagged && !isstruct` variant-widen arm - * only filled the registers a given variant actually uses (scalar → DX; - * str → DX, CX; slice → DX, CX, R8). The unused AX/CX/R8 words were - * left holding whatever the caller's last write parked there. The - * receive side (cg_widen_tagged_store call-source arm) writes - * AX/DX/CX/R8 into the dst slot unconditionally, sized by the slot - * total — so stale registers landed at slot+16 / slot+24 and silently - * corrupted the tagged-union slot. - * - * The corruption only surfaced when the caller had primed CX/R8 - * shortly before the call. Array / slice indexing emits - * `MOVQ $stride, CX; IMULQ CX, AX` — the canonical primer. Happens - * once per index, including in for-loop bodies. Hence the original - * "for-loop miscompile" framing; the bug is in fact context-free, - * but an index expression in any straight-line code triggers it - * just as well. - * - * Fix (#18): in cgreturn's variant-widen arm, after the variant- - * specific register shuffle and before the tag-into-AX, emit - * `MOVQ $0, CX` / `MOVQ $0, R8` for variants that don't naturally - * fill those words, conditional on the dst tagged-union slot size. - * Symmetric across cstage cgen.c and wwstage cgenstmt.ww. - * - * Pre-fix (#20): wwstage's `taggedvariantindex` checked `tagged.kind - * == N_TTAGGED` directly. For aliased return types - * (`type ft = (i64|str|bool); fn f() ft = ...`) c.fnret is N_TNAME, - * so the lookup punted to -1 → caller mapped to 0, silently emitting - * `MOVQ $0, AX` for every non-leading variant. Cstage was already - * correct (check.c resolves N_TNAME → underlying upfront). - * - * Fix (#20): resolve `tagged` via resolvetagged() (alias + TBANG - * unwrap) at entry to taggedvariantindex. Wwstage-only. - * - * What this test pins: - * - Every scalar-variant arm of a 24B tagged-union return zeroes - * slot+16 (CX) regardless of caller priming. - * - The str variant of a 24B slot still propagates .len correctly - * into slot+16 (the fix's `if (rsz > 24)` guard skips the R8 - * zero for the 24B case, leaving CX/.len intact). - * - Cross-shape probes: straight-line array-index call, in-loop - * body call, nested-loop inner-body call. - * - Aliased tagged-return + N_IDENT source emits the declared- - * position variant tag (#20). Rows 7-9 pin tag=0 / tag=1 / - * tag=2 explicitly so a future variant reorder doesn't - * silently land back on the coincidentally-correct tag=0. - * - Cstage and wwstage emit byte-identical asm for every row, - * post #18 + #20 fixes. The bootstrap byte-identity invariant - * (995_self_rebuild) is the global guard; per-row diffs here - * surface focused regressions in cgreturn / taggedvariantindex. - * - * Test rows use `[N]i64` array indexing as the CX primer (well- - * supported on both stages) to dodge a pre-existing cstage/wwstage - * divergence in slice-creation reg scheduling. - */ -#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. Straight-line array-index then scalar-variant 24B-tagged - * call. Array indexing emits MOVQ $8, CX; IMULQ CX, AX — so CX - * carries 8 into the call. Pre-fix: slot+16 = 8. Post-fix: 0. */ - { "scalar_24B_after_arridx", - "type ft = (i64 | str | bool);\n" - "fn aski64(v: i64) ft = { return v; };\n" - "fn main() i32 = {\n" - " let arr: [3]i64 = [10i64, 20i64, 30i64];\n" - " let i: i32 = 1;\n" - " let v: i64 = arr[i];\n" - " let a: ft = aski64(v);\n" - " let p: u64 = (&a): u64;\n" - " let p2: *i64 = (p + 16u64): *i64;\n" - " let w: i64 = *p2;\n" - " return w: i32;\n" - "};\n", - 0 }, - /* 2. Bool variant of the same 24B slot. i64-array-index primer - * (well-supported shape) -> derive bool -> call -> probe slot+16. - * Pre-fix: stride leak. Post-fix: 0. */ - { "bool_24B_after_arridx", - "type ft = (i64 | str | bool);\n" - "fn askbool(b: bool) ft = { return b; };\n" - "fn main() i32 = {\n" - " let arr: [3]i64 = [1i64, 0i64, 1i64];\n" - " let i: i32 = 0;\n" - " let v: i64 = arr[i];\n" - " let b: bool = v != 0i64;\n" - " let a: ft = askbool(b);\n" - " let p: u64 = (&a): u64;\n" - " let p2: *i64 = (p + 16u64): *i64;\n" - " let w: i64 = *p2;\n" - " return w: i32;\n" - "};\n", - 0 }, - /* 3. Str variant of a 24B slot — sender already fills CX with - * the len, so slot+16 was correct pre-fix too. Sanity row: must - * keep working; the fix's `if (rsz > 24)` guard skips the R8 - * zero for the 24B case, leaving CX (and slot+16) at len. The - * i64-array index primes CX; the str literal feeds askstr. */ - { "str_24B_after_arridx", - "type ft = (i64 | str | bool);\n" - "fn askstr(s: str) ft = { return s; };\n" - "fn main() i32 = {\n" - " let arr: [3]i64 = [10i64, 20i64, 30i64];\n" - " let i: i32 = 1;\n" - " let _v: i64 = arr[i];\n" - " let a: ft = askstr(\"world\");\n" - " let p: u64 = (&a): u64;\n" - " let p2: *i64 = (p + 16u64): *i64;\n" - " let w: i64 = *p2;\n" - " return w: i32;\n" - "};\n", - 5 }, - /* 4. In-loop body: scalar variant call. Same shape as row 1, - * but inside a `for (i < 3)` body where IMULQ primes CX - * on every iteration. Pre-fix: slot+16 leaks 8 every iter; - * sum-of-leaks > 0. Post-fix: 0. */ - { "scalar_24B_in_loop", - "type ft = (i64 | str | bool);\n" - "fn aski64(v: i64) ft = { return v; };\n" - "fn main() i32 = {\n" - " let arr: [3]i64 = [10i64, 20i64, 30i64];\n" - " let i: i32 = 0;\n" - " let bad: i32 = 0;\n" - " for (i < 3) {\n" - " let v: i64 = arr[i];\n" - " let a: ft = aski64(v);\n" - " let p: u64 = (&a): u64;\n" - " let p2: *i64 = (p + 16u64): *i64;\n" - " let w: i64 = *p2;\n" - " if (w != 0i64) { bad += 1; };\n" - " i += 1;\n" - " };\n" - " return bad;\n" - "};\n", - 0 }, - /* 5. Nested loop, inner-body scalar-variant call. Pins that the - * fix doesn't depend on loop depth. Each inner iter primes CX. */ - { "scalar_24B_in_nested_loop", - "type ft = (i64 | str | bool);\n" - "fn aski64(v: i64) ft = { return v; };\n" - "fn main() i32 = {\n" - " let arr: [2]i64 = [10i64, 20i64];\n" - " let i: i32 = 0;\n" - " let bad: i32 = 0;\n" - " for (i < 2) {\n" - " let j: i32 = 0;\n" - " for (j < 2) {\n" - " let v: i64 = arr[j];\n" - " let a: ft = aski64(v);\n" - " let p: u64 = (&a): u64;\n" - " let p2: *i64 = (p + 16u64): *i64;\n" - " let w: i64 = *p2;\n" - " if (w != 0i64) { bad += 1; };\n" - " j += 1;\n" - " };\n" - " i += 1;\n" - " };\n" - " return bad;\n" - "};\n", - 0 }, - /* 6. Mixed loop: alternating str / i64 variants — pins that the - * scalar-variant zero doesn't regress the str-variant CX fill - * across iterations. iter-0 stores str (slot+16 = 5), iter-1 - * stores i64 (slot+16 = 0). bad counts slot+16 != expected. */ - { "mixed_variants_in_loop", - "type ft = (i64 | str | bool);\n" - "fn askstr(s: str) ft = { return s; };\n" - "fn aski64(v: i64) ft = { return v; };\n" - "fn main() i32 = {\n" - " let arr: [2]i64 = [10i64, 20i64];\n" - " let i: i32 = 0;\n" - " let bad: i32 = 0;\n" - " for (i < 2) {\n" - " let _v: i64 = arr[i];\n" - " let a: ft;\n" - " let expect: i64 = 0i64;\n" - " if (i == 0) { a = askstr(\"world\"); expect = 5i64; };\n" - " if (i == 1) { a = aski64(99i64); expect = 0i64; };\n" - " let p: u64 = (&a): u64;\n" - " let p2: *i64 = (p + 16u64): *i64;\n" - " let w: i64 = *p2;\n" - " if (w != expect) { bad += 1; };\n" - " i += 1;\n" - " };\n" - " return bad;\n" - "};\n", - 0 }, - /* 7-9: task #20 variant-index pins. Aliased tagged return plus - * N_IDENT source on each variant arm — probes slot+0 (the tag - * word). Pre-#20 wwstage's taggedvariantindex saw c.fnret as - * N_TNAME("ft") and short-circuited to -1 → tag = 0 for every - * row. Post-#20 the resolvetagged unwrap at fn entry surfaces - * the underlying N_TTAGGED so the declared-position index is - * emitted. Pinning all three positions (0/1/2) catches a future - * variant-reorder regression that would otherwise hide behind - * row 7's coincidentally-correct tag=0. */ - { "aliased_tagged_aski64_idx0", - "type ft = (i64 | str | bool);\n" - "fn aski64(v: i64) ft = { return v; };\n" - "fn main() i32 = {\n" - " let v: i64 = 7i64;\n" - " let a: ft = aski64(v);\n" - " let p: u64 = (&a): u64;\n" - " let pt: *i64 = p: *i64;\n" - " return (*pt): i32;\n" - "};\n", - 0 }, - { "aliased_tagged_askstr_idx1", - "type ft = (i64 | str | bool);\n" - "fn askstr(s: str) ft = { return s; };\n" - "fn main() i32 = {\n" - " let s: str = \"world\";\n" - " let a: ft = askstr(s);\n" - " let p: u64 = (&a): u64;\n" - " let pt: *i64 = p: *i64;\n" - " return (*pt): i32;\n" - "};\n", - 1 }, - { "aliased_tagged_askbool_idx2", - "type ft = (i64 | str | bool);\n" - "fn askbool(b: bool) ft = { return b; };\n" - "fn main() i32 = {\n" - " let b: bool = true;\n" - " let a: ft = askbool(b);\n" - " let p: u64 = (&a): u64;\n" - " let pt: *i64 = p: *i64;\n" - " return (*pt): i32;\n" - "};\n", - 2 }, - /* 10-12: #157 — float variant of the same `!istagged && !isstruct` - * return arm. Pre-fix it did `MOVQ AX, DX` even when the value was - * a float in X0 (no MOVQ-xmm->gp form), so the payload word held - * stale AX, not the float bits. Fix spills X0 through a stack slot - * (zero-slot-first so the f32 MOVSS low-4 write leaves a determin- - * istic high-4). Probe slot+8 as the float directly (bit-exact - * f64/f32 equality) — isolates the return-pack from the match- - * receive. The f32 row avoids f32 ARGS (pre-existing #143 f32-arg - * MOVSD/MOVSS divergence is unrelated; f32 here comes from a global - * with an int arg, so the byte-id check pins THIS fix). */ - { "f64_variant_return_payload", - "type ft = (f64 | i32);\n" - "fn addf(a: f64, b: f64) ft = { return a + b; };\n" - "fn main() i32 = {\n" - " let a: ft = addf(1.5, 2.0);\n" - " let p: u64 = (&a): u64;\n" - " let pf: *f64 = (p + 8u64): *f64;\n" - " let fv: f64 = *pf;\n" - " if (fv == 3.5) { return 1; };\n" - " return 0;\n" - "};\n", - 1 }, - { "f32_variant_return_payload", - "let g: f32 = 1.5f32;\n" - "type ft = (f32 | i32);\n" - "fn mkf(k: i32) ft = { if (k > 0) { return g + 2.0f32; }; return k; };\n" - "fn main() i32 = {\n" - " let a: ft = mkf(1);\n" - " let p: u64 = (&a): u64;\n" - " let pf: *f32 = (p + 8u64): *f32;\n" - " let fv: f32 = *pf;\n" - " if (fv == 3.5f32) { return 1; };\n" - " return 0;\n" - "};\n", - 1 }, - { "f64_variant_return_multivariant", - "type ft = (f64 | i32 | bool);\n" - "fn mulf(a: f64, b: f64) ft = { return a * b; };\n" - "fn main() i32 = {\n" - " let a: ft = mulf(2.5, 4.0);\n" - " let p: u64 = (&a): u64;\n" - " let pf: *f64 = (p + 8u64): *f64;\n" - " let fv: f64 = *pf;\n" - " if (fv == 10.0) { return 1; };\n" - " return 0;\n" - "};\n", - 1 }, -}; - -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/wcrv_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcrv_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcrv_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's w6c_ww - * and diff. Post #18 + #20 fixes the per-row asm matches byte-for-byte - * across stages; the bootstrap byte-identity (995_self_rebuild) covers - * the broader cross-stage drift surface globally. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcrv_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcrv_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcrv_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "cgreturn_variant_zero: 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, - "cgreturn_variant_zero[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - /* Asm byte-identity diff, only when both stages exist. */ - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "cgreturn_variant_zero: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("cgreturn_variant_zero: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/710_cast_enum_movl.c b/test/wcc/710_cast_enum_movl.c deleted file mode 100644 index 80b97540..00000000 --- a/test/wcc/710_cast_enum_movl.c +++ /dev/null @@ -1,487 +0,0 @@ -/* - * 710_cast_enum_movl — cstage and wwstage agree byte-for-byte on the - * N_CAST narrow-clamp under the principled identity-width identity- - * sign predicate (task #33). Extended from the original #25 fixture - * which mirrored wwstage's N_TENUM lacuna as a single-site `tu->kind - * == TY_ENUM` gate in cstage. - * - * Predicate (both stages): - * skip the narrow-clamp on an int→int cast iff - * src.width == dst.width && src.signed == dst.signed - * where (width, signedness) resolve through TY_NAMED / TY_ENUM - * alias chains in cstage and N_TBANG / N_TENUM / N_TNAME-alias - * chains in wwstage. Bool keeps its dedicated ANDQ $255 contract. - * - * History: #25 (b5632b1) shipped a single-site gate in cstage — - * `dst_is_enum → skip` — that made cstage byte-for-byte identical - * to wwstage on a u32→enum-u32 cast. It also inadvertently kept a - * silent miscompile alive: u32→enum-u8 and i64→enum-i32 also took - * the dst-is-enum exit, so the narrow-clamp didn't fire on a - * genuinely-width-narrowing cast and the upper bits of the source - * value leaked into any register-chained downstream use (the slot - * store happens to mask via MOVB/MOVL of the dst width, so program - * semantics looked right unless the result was consumed by a - * register-chained outer cast / arithmetic). - * - * Surfaced by worker-stat during #10: when kstat.mode was first - * typed as raw `u32`, `out.mode = k.mode` parsed as a u32→enum-u32 - * cast via `fs.mode`, and the cstage→wwstage asm divergence broke - * 993_ww_ww + 995_self_rebuild on the first selfhost pass. The - * workaround that was in tree (lib/os/os.ww kstat.mode: mode) has - * already been retired by #25's single-site fix; #33 generalises - * the gate. - * - * row | shape | gate - * --------------------+--------------------------------------+---------- - * u32_to_enum_u32 | `let y: m = x: m;` with m=enum u32. | exit=7 - * | Identity (4B/unsigned). Both stages | + byte-id - * | skip — no clamp. | - * enum_u32_to_u32 | reverse: `let z: u32 = y: u32;`. | exit=7 - * | Also identity (4B/unsigned, walker | + byte-id - * | now resolves `mymode` through | - * | aliaslookup to u32). Both skip — | - * | flips from #25's clamp-emit. | - * u32_to_enum_u8 | dst is enum u8. Width narrows 4→1, | exit=7 - * | so identity is false. Both stages | + byte-id - * | now emit ANDQ $0xFF — flips from | - * | #25's skip. Fixes the silent leak | - * | (see u32_to_enum_u8_truncate below). | - * i64_to_enum_i32 | signed-narrow: dst is enum i32. | exit=7 - * | Width narrows 8→4 → identity false. | + byte-id - * | Both stages emit MOVSXD AX, AX — | - * | flips from #25's skip. Fixes the | - * | silent leak (see | - * | i64_to_enum_i32_truncate below). | - * struct_field_rt | mirror of lib/os fillfilestat: a u32 | exit=7 - * | struct field copied into an enum-u32 | + byte-id - * | field by chained N_DOT. Identity | - * | (4B/unsigned). Both skip. | - * u32_u32_identity | `let y: u32 = x: u32;` with src=u32. | exit=7 - * | Trivial identity. Both stages skip; | + byte-id - * | pre-#33 they emitted a redundant | - * | MOVL AX, AX. | - * i32_i32_identity | same shape, src/dst i32. Pre-#33 | exit=7 - * | both emitted MOVSXD AX, AX. Now | + byte-id - * | skip. | - * u8_u8_identity | u8 → u8. Pre-#33 ANDQ $0xFF. Now | exit=7 - * | skip. | + byte-id - * i8_i8_identity | i8 → i8. Pre-#33 MOVSBQ AX, AX. | exit=7 - * | Now skip. | + byte-id - * u16_u16_identity | u16 → u16. Pre-#33 ANDQ $0xFFFF. | exit=7 - * | Now skip. | + byte-id - * i16_i16_identity | i16 → i16. Pre-#33 MOVSWQ AX, AX. | exit=7 - * | Now skip. | + byte-id - * u32_to_i32_signchg | width equal, signedness differs. | exit=7 - * | Identity is FALSE → narrow-clamp | + byte-id - * | MUST fire. Both stages emit MOVSXD | - * | (dst is signed-narrow). Pin against | - * | future refactors that mis-broaden | - * | the skip. | - * u32_to_enum_u8_trnc | exit-code-validating silent- | exit=0 - * | miscompile fix. x=0xFFFFu32 cast to | + byte-id - * | enum-u8, then to u32, then divided | - * | by 0x100. Post-#33 the inner cast | - * | clamps to 0xFF, divide yields 0; | - * | pre-#33 the upper bits leaked | - * | (AX=0xFFFF), divide yielded 0xFF. | - * i64_to_enum_i32_trnc| same shape on i64 → enum-i32. | exit=0 - * | x=0x100000000i64 cast to enum-i32, | + byte-id - * | then to i64, divided by 0x100000000. | - * | Post-#33 MOVSXD takes low 32 bits | - * | (0), divide yields 0; pre-#33 the | - * | high 32 bits leaked, divide | - * | yielded 1. | - * - * Cstage exit-code rows confirm the binary still runs correctly - * post-#33. The asm-byte-id rows pin the symmetric-emit contract. - * The `*_trnc` rows are the regression-pinning ones for the - * silent-miscompile fix that #25's dst-kind-only skip left in - * place. ww3!=ww4 byte-id (995_self_rebuild) covers a broader - * surface but doesn't isolate this corner. - * - * Note: removing the defensive MOVL exposes any upstream cgen path - * that leaves garbage in upper RAX when producing a sub-word value. - * If a future test goes red post-#33, the contract is violated - * somewhere — fix the upstream producer, do NOT reinstate the - * defensive clamp. - */ -#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. Headline #25 case: u32 → enum-u32. Pre-fix cstage emitted - * `MOVL AX, AX` after the slot load; wwstage skipped. The cast - * is a no-op at runtime so program semantics is unchanged - * either way — exit code stays 7 regardless. The asm-byte-id - * row is what catches the regression. */ - { "u32_to_enum_u32", - "type mymode = enum u32 { A = 1u32, B = 2u32 };\n" - "fn main() i32 = {\n" - "\tlet x: u32 = 7u32;\n" - "\tlet y: mymode = x: mymode;\n" - "\treturn (y: u32): i32;\n" - "};\n", - 7 }, - - /* 2. Reverse direction: enum-u32 → u32. Post-#33 both stages - * walk `mymode` through aliaslookup to u32, see (src u32, dst - * u32, both unsigned), and skip the narrow-clamp under the - * identity-width identity-sign predicate. Flips from #25's - * clamp-emit. Exit code unchanged at 7. */ - { "enum_u32_to_u32", - "type mymode = enum u32 { A = 1u32 };\n" - "fn main() i32 = {\n" - "\tlet y: mymode = 7u32: mymode;\n" - "\tlet z: u32 = y: u32;\n" - "\treturn z: i32;\n" - "};\n", - 7 }, - - /* 3. Different enum width: u32 → enum-u8. Post-#33 both stages - * emit ANDQ $0xFF because identity is false (src 4B, dst 1B). - * Flips from #25's dst-is-enum skip. The slot write masks via - * MOVB so program semantics with `7` reads back as 7 either - * way; the silent-miscompile case (upper bits leaking into - * register-chained downstream use) is pinned by - * u32_to_enum_u8_trnc below. */ - { "u32_to_enum_u8", - "type small = enum u8 { A = 1u8 };\n" - "fn main() i32 = {\n" - "\tlet x: u32 = 7u32;\n" - "\tlet y: small = x: small;\n" - "\treturn (y: u32): i32;\n" - "};\n", - 7 }, - - /* 4. Signed-narrow path: i64 → enum-i32. Post-#33 both stages - * emit MOVSXD AX, AX (identity false: src 8B, dst 4B). Flips - * from #25's skip. Slot is read with MOVSXD downstream so the - * sign-extension is recovered on use; silent leak through a - * register-chained outer cast is pinned by - * i64_to_enum_i32_trnc below. */ - { "i64_to_enum_i32", - "type sflag = enum i32 { A = 1i32 };\n" - "fn main() i32 = {\n" - "\tlet x: i64 = 7i64;\n" - "\tlet y: sflag = x: sflag;\n" - "\treturn (y: i32);\n" - "};\n", - 7 }, - - /* 5. Mirror of lib/os fillfilestat: struct field of one type - * copied into an enum-typed field of another struct via - * chained N_DOT. Identity (4B/unsigned on both sides) → both - * stages skip the clamp. Pre-#25 this blew up 993_ww_ww + - * 995_self_rebuild on the first selfhost pass. */ - { "struct_field_rt", - "type mymode = enum u32 { A = 1u32 };\n" - "type src = struct { mode: u32 };\n" - "type dst = struct { mode: mymode };\n" - "fn main() i32 = {\n" - "\tlet a: src = src { mode = 7u32 };\n" - "\tlet b: dst;\n" - "\tb.mode = a.mode: mymode;\n" - "\treturn (b.mode: u32): i32;\n" - "};\n", - 7 }, - - /* 6-11. Identity-width identity-sign rows. Pre-#33 the cast - * always emitted a clamp for sub-8B dst (MOVL/ANDQ/MOVSBQ/ - * MOVSWQ/MOVSXD depending on width and signedness); post-#33 - * all six skip because src and dst share the underlying - * primitive. Asm byte-id pins the contract. */ - { "u32_u32_identity", - "fn main() i32 = {\n" - "\tlet x: u32 = 7u32;\n" - "\tlet y: u32 = x: u32;\n" - "\treturn y: i32;\n" - "};\n", - 7 }, - { "i32_i32_identity", - "fn main() i32 = {\n" - "\tlet x: i32 = 7i32;\n" - "\tlet y: i32 = x: i32;\n" - "\treturn y;\n" - "};\n", - 7 }, - { "u8_u8_identity", - "fn main() i32 = {\n" - "\tlet x: u8 = 7u8;\n" - "\tlet y: u8 = x: u8;\n" - "\treturn (y: u32): i32;\n" - "};\n", - 7 }, - { "i8_i8_identity", - "fn main() i32 = {\n" - "\tlet x: i8 = 7i8;\n" - "\tlet y: i8 = x: i8;\n" - "\treturn (y: i32);\n" - "};\n", - 7 }, - { "u16_u16_identity", - "fn main() i32 = {\n" - "\tlet x: u16 = 7u16;\n" - "\tlet y: u16 = x: u16;\n" - "\treturn (y: u32): i32;\n" - "};\n", - 7 }, - { "i16_i16_identity", - "fn main() i32 = {\n" - "\tlet x: i16 = 7i16;\n" - "\tlet y: i16 = x: i16;\n" - "\treturn (y: i32);\n" - "};\n", - 7 }, - - /* 12. Width-equal sign-change: u32 → i32. Identity is FALSE - * (signedness differs) so the clamp MUST still emit (MOVSXD - * because dst is signed-narrow). Asm byte-id pins this - * against future refactors that mis-broaden the identity - * skip. Exit code 7 is just the value round-tripping. */ - { "u32_to_i32_signchg", - "fn main() i32 = {\n" - "\tlet x: u32 = 7u32;\n" - "\tlet y: i32 = x: i32;\n" - "\treturn y;\n" - "};\n", - 7 }, - - /* 13. Silent-miscompile fix, u32 → enum-u8. Pre-#33 the - * b5632b1 dst-is-enum skip left the upper bits of the u32 - * source in AX. With register-chained downstream use (no slot - * spill between the inner cast and the outer expression), the - * leak survives. Probe: start with x=0xFFFFu32, cast to - * enum-u8 (should clamp to 0xFF), cast to u32, divide by - * 0x100. Post-#33 the inner clamp leaves AX=0xFF and the - * divide yields 0; pre-#33 AX stayed 0xFFFF and the divide - * yielded 0xFF. Exit code distinguishes (0 vs 255). */ - { "u32_to_enum_u8_trnc", - "type small = enum u8 { A = 1u8 };\n" - "fn main() i32 = {\n" - "\tlet x: u32 = 0xFFFFu32;\n" - "\tlet r: u32 = ((x: small): u32) / 0x100u32;\n" - "\treturn r: i32;\n" - "};\n", - 0 }, - - /* 14. Silent-miscompile fix, i64 → enum-i32. Same shape on - * the signed-narrow path. x=0x100000000i64 (bit 32 set, low - * 32 bits zero). Post-#33 the MOVSXD takes the low 32 bits - * (0), AX=0, divide by 0x100000000 yields 0. Pre-#33 the - * clamp was skipped, AX stayed 0x100000000, divide yielded - * 1. Exit code distinguishes (0 vs 1). */ - { "i64_to_enum_i32_trnc", - "type sflag = enum i32 { A = 1i32 };\n" - "fn main() i32 = {\n" - "\tlet x: i64 = 0x100000000i64;\n" - "\tlet r: i64 = ((x: sflag): i64) / 0x100000000i64;\n" - "\treturn r: i32;\n" - "};\n", - 0 }, - - /* 15. Pseudo-field defensive-clamp pin. Source is `s.len`, a - * str header pseudo-field — neither stage's source-type - * resolver recognises it (cstage's `castsrcprim` gates the - * N_DOT branch on `bu->kind == TY_STRUCT`; wwstage's - * `exprprimresolved` routes through `dotfieldtnode` which - * returns nil for non-struct base). Both fall back to sz=0, - * identity is false, the narrow-clamp emits (MOVSXD here - * because dst is signed-narrow i32). Pinning byte-id on this - * row catches a future refactor that wires pseudo-field - * inference asymmetrically into one stage — the kind of drift - * that would silently break 995_self_rebuild without naming - * the corner. Exit code 7 = round-trip of the literal len. */ - { "pseudo_field_clamp", - "fn main() i32 = {\n" - "\tlet s: str = \"abcdefg\";\n" - "\tlet n: i32 = s.len: i32;\n" - "\treturn n;\n" - "};\n", - 7 }, - - /* 16. Bool source clamp pin. Source is a bool local, dst is i8. - * Width matches (1B) but bool is excluded from the int-prim - * contract on both stages (cstage's `type_isint(TY_BOOL)` is - * false; wwstage's `typenodeprimresolved` has an explicit - * `streq(nm, "bool") → return` early-out). So identity is - * never true on a bool source: the narrow-clamp emits - * (MOVSBQ AX, AX because dst is i8, signed-narrow). Without - * the bool early-out in wwstage, `primsize("bool")=1` and - * `typenameisunsigned("bool")=false` made wwstage see - * (sz=1, unsigned=false) and fire identity on bool→i8 while - * cstage emitted MOVSBQ — silent asm asymmetry that no other - * row exercises. Mirrors the `*_trnc` rows' pattern: the row - * pins the clamp emit, not just the exit code. */ - { "bool_to_i8_clamp", - "fn main() i32 = {\n" - "\tlet b: bool = true;\n" - "\tlet y: i8 = b: i8;\n" - "\treturn (y: i32);\n" - "};\n", - 1 }, -}; - -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/cem_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/cem_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/cem_%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 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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. This is the regression-pinning row for #25; an - * exit-code-only comparison wouldn't catch a redundant MOVL drift - * because the program semantics is unchanged. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/cem_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/cem_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/cem_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "cast_enum_movl: 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, - "cast_enum_movl[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - /* Asm byte-identity diff, only when wwstage is built. This is - * the row that pins the #25 fix. */ - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "cast_enum_movl: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("cast_enum_movl: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/716_match_spill_pointer_payload.c b/test/wcc/716_match_spill_pointer_payload.c deleted file mode 100644 index f72da3f1..00000000 --- a/test/wcc/716_match_spill_pointer_payload.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 716_match_spill_pointer_payload — wwstage @match_spill scratch slot - * sized to the scrutinee's tagged-union slot, not a hardcoded 24B - * default. Surfaced during task #23 (lib/os path *u8 → str migration) - * where `kpath` originally returned `(*u8 | oserror)`. - * - * Pre-fix (#9): cgmatch's non-ident scrutinee spill defaulted spillsz - * to 24 and only grew it; cgendecl scanlocals mirrored with a hardcoded - * `total += 24`. For a 1-word-payload tagged union (`(*u8 | oserror)` - * with `type oserror = !i64`) the actual slot is 16B (tag + one 8B - * word) — cstage allocated $48 / 16B slot / 2-word ABI (AX=tag, DX), - * wwstage allocated $64 / 24B slot / 3-word ABI (AX=tag, DX, CX). - * Frame-size drift broke bootstrap byte-identity the moment any caller - * matched on such a return. - * - * Fix (#9, wwstage-only per rule 10): factor scrutinee-type resolution - * into matchscrutt; factor slot-size computation into matchspillsz - * (default 16, mirroring cmd/w6c/cgen.c cgmatch's `slot_size = (su-> - * kind == TY_TAGGED) ? su->size : 16`). cgmatch gates CX write on - * `spillsz > 16` (R8 gate `> 24` was already correct). scanlocals - * uses the same helpers so scan + emit stay lockstep. - * - * What this test pins: - * - Asm byte-identity between cstage and wwstage for the canonical - * `(*u8 | oserror)` shape (aliased !i64) and the raw `(*u8 | i64)` - * shape — both must produce $48 frame, 16B spill slot, 2-word - * return ABI. - * - Runtime: both arms (pointer / error) round-trip the payload - * correctly through the narrower spill. - * - 24B-slot regression guard: `(str | i64)` (max payload 16B, slot - * 24B) still byte-identical post-fix — the gate must keep the CX - * write for slot_size > 16. - * - * NOT covered: raw `(*u8 | !i64)` (no alias) — wwstage's variant-index - * resolution for the raw N_TBANG-in-union shape has a separate pre- - * existing tag-emit divergence from cstage (tag=0 vs tag=1) that is - * orthogonal to the spill-slot sizing fixed by #9. Documented at the - * probe .ai/probe_tagged_return_pointer_payload.ww. - */ -#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. Canonical `(*u8 | oserror)` (aliased !i64). Pointer arm - * returns the first byte through a str-literal `.ptr`. Avoiding - * a top-level `let buf: [16]u8;` sidesteps an orthogonal wwstage - * DATAW-emit divergence for module-scope uninit `[N]u8`. */ - { "ptr_payload_aliased_bang_ok", - "type oserror = !i64;\n" - "fn kpath(p: str) (*u8 | oserror) = {\n" - " if (p.len < 0) { return -36i64: oserror; };\n" - " let s: str = \"A\";\n" - " return s.ptr;\n" - "};\n" - "fn main() i32 = {\n" - " let q: str = \"x\";\n" - " match (kpath(q)) {\n" - " case let e: oserror => return (e: i64): i32;\n" - " case let p: *u8 => return p[0]: i32;\n" - " };\n" - " return 0;\n" - "};\n", - 65 }, - /* 2. Same shape, error arm: pre-fix the 24B spill clobbered CX - * (irrelevant tag-only path) and bumped the frame; this row - * pins the error arm round-trips -36 + 100 = 64. */ - { "ptr_payload_aliased_bang_err", - "type oserror = !i64;\n" - "fn kpath(p: str) (*u8 | oserror) = {\n" - " if (p.len >= 0) { return -36i64: oserror; };\n" - " let s: str = \"A\";\n" - " return s.ptr;\n" - "};\n" - "fn main() i32 = {\n" - " let q: str = \"x\";\n" - " match (kpath(q)) {\n" - " case let e: oserror => return ((e: i64): i32) + 100;\n" - " case let p: *u8 => return p[0]: i32;\n" - " };\n" - " return 0;\n" - "};\n", - 64 }, - /* 3. Raw `(*u8 | i64)` — no alias, no `!`. Hypothesis (b) probe: - * the spill-slot fix holds regardless of whether the payload is - * wrapped in a type alias. */ - { "ptr_payload_raw_i64_ok", - "fn kpath(p: str) (*u8 | i64) = {\n" - " if (p.len < 0) { return -36i64; };\n" - " let s: str = \"Z\";\n" - " return s.ptr;\n" - "};\n" - "fn main() i32 = {\n" - " let q: str = \"x\";\n" - " match (kpath(q)) {\n" - " case let e: i64 => return e: i32;\n" - " case let p: *u8 => return p[0]: i32;\n" - " };\n" - " return 0;\n" - "};\n", - 90 }, - /* 4. 24B-slot regression guard. `(str | i64)` slot = 8 tag + - * 16 str = 24. The CX write at slot+16 is required (carries - * .len for the str arm). Pre-fix this row passed; post-fix the - * `spillsz > 16` gate must keep CX. Returns s.len for "hello" - * = 5. */ - { "str_payload_24B_keeps_cx", - "fn pick(b: bool) (str | i64) = {\n" - " if (b) { return \"hello\"; };\n" - " return 7i64;\n" - "};\n" - "fn main() i32 = {\n" - " match (pick(true)) {\n" - " case let e: i64 => return (e: i32) + 200;\n" - " case let s: str => return s.len: i32;\n" - " };\n" - " return 0;\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/wcmsp_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcmsp_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcmsp_%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; -} - -/* asm_byte_identical — pin frame size + spill layout by diffing the - * w6c vs w6c_ww text output. The whole point of #9 is that wwstage's - * frame stops bloating for 1-word-payload variants, so the bytes - * must match (modulo orthogonal divergences — none on these rows). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcmsp_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcmsp_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcmsp_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "match_spill_pointer_payload: 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_spill_pointer_payload[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "match_spill_pointer_payload: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("match_spill_pointer_payload: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/717_struct_byval_param.c b/test/wcc/717_struct_byval_param.c deleted file mode 100644 index 07762d62..00000000 --- a/test/wcc/717_struct_byval_param.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 717_struct_byval_param — wwstage param-spill ABI for user-defined - * by-value struct parameters of size ≤ 16B. Sister to #9 (which - * fixed an OVER-allocation in the match-scrutinee spill path); - * this is the UNDER-allocation in the cgfn param-spill path. - * - * Pre-fix (#11): wwstage cgfnparams dispatched on TK_ELLIPSIS, - * float, tagged, slice, str — then a catch-all 8B scalar arm. - * User-defined struct params (TY_STRUCT after collectstructs) - * had no branch and fell through to the 8B arm. For - * `fn cmp(a: inst, b: inst)` with `inst = struct { sec: i64, - * nsec: i64 }` cstage allocated `TEXT cmp,$32` and spilled all - * four argregs DI/SI/DX/CX; wwstage allocated `TEXT cmp,$16` - * and spilled only DI/SI — the second-half value words of each - * arg (DX/CX) were never stored, and `b.sec`/`b.nsec` reads - * trailed into the saved-BP word. Built-in `str` (also 16B, - * ptr+len) routed correctly through the isstrtype arm, so the - * bug was a TY_STRUCT type-dispatch miss, not a missing path. - * - * Fix (#11, wwstage-only per rule 10): add structparamsize in - * cgenutil.ww (mirror cstage cgen.c struct_arg_size: returns the - * struct's totsize for sizes in (0, 16], else 0). Add a struct - * arm in cgfnparams between the str arm and the 8B catch-all, - * matching cstage's struct_eb = (size > 8) ? 2 : 1 — including - * the partial-fit stitch arm (idx=5, nw=2: one reg, one stack - * tail). Mirror the same isstr branch in cgfn's pre-scan so the - * frame reservation and emit stay lockstep. - * - * What this test pins: - * - Asm byte-identity between cstage and wwstage for 2×16B - * struct params, 1×16B struct + 1×8B struct (mixed - * eightbyte counts), 1×16B struct + 1×str (regression guard - * for the already-working str path), and 6 i64 + 1 16B - * struct (partial-fit stitch case). - * - Runtime end-to-end: each row's main returns a deterministic - * exit code derived from the received struct's fields. A - * regression in the spill ABI (e.g. CX→slot+8 missing) shows - * up as a `got != want` exit-code mismatch. - * - * NOT covered: - * - Structs > 16B by-value param: cstage's struct_arg_size - * itself gates on size <= 16, so a 24B-struct param also - * under-allocates in cstage (falls through to the 8B arm). - * Both stages agree on the wrong behaviour, so it's bug- - * compatible byte-identical; that's a separate task. - * - Single-field i32-or-smaller struct: still 8B totsize after - * wwstage's registerstruct rounds up, equivalent to a scalar - * i64 in the spill path. The 1-field i64 row exercises the - * nw=1 branch. - */ -#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. Canonical 2×16B struct shape (the original probe). The - * comparator reads b.sec — that's the second-arg slot+0, which - * pre-fix sat unwritten and aliased the caller's saved BP. */ - { "two_16B_struct_cmp", - "type inst = struct { sec: i64, nsec: i64 };\n" - "fn cmp(a: inst, b: inst) i32 = {\n" - " if (a.sec < b.sec) { return -1; };\n" - " if (a.sec > b.sec) { return 1; };\n" - " if (a.nsec < b.nsec) { return -1; };\n" - " if (a.nsec > b.nsec) { return 1; };\n" - " return 0;\n" - "};\n" - "fn main() i32 = {\n" - " let x: inst = inst { sec = 5i64, nsec = 200i64 };\n" - " let y: inst = inst { sec = 5i64, nsec = 100i64 };\n" - " let r: i32 = cmp(x, y);\n" - " if (r > 0) { return 7; };\n" - " return 0;\n" - "};\n", - 7 }, - /* 2. 16B struct + 8B struct (mixed eightbyte counts). a uses - * DI/SI (2 eb), b uses DX (1 eb). Returns a.sec + a.nsec + b.v - * = 10 + 20 + 30 = 60. */ - { "mixed_16B_8B_struct", - "type two = struct { sec: i64, nsec: i64 };\n" - "type one = struct { v: i64 };\n" - "fn sum(a: two, b: one) i32 = {\n" - " return (a.sec + a.nsec + b.v): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let x: two = two { sec = 10i64, nsec = 20i64 };\n" - " let y: one = one { v = 30i64 };\n" - " return sum(x, y);\n" - "};\n", - 60 }, - /* 3. 16B struct + 16B str (regression guard). The pre-fix - * wwstage already handled str via the isstrtype arm; this row - * pins that the new struct arm doesn't shadow it. struct in - * DI/SI, str in DX/CX. Returns a.sec + s.len = 42 + 3 = 45. */ - { "struct_plus_str", - "type two = struct { sec: i64, nsec: i64 };\n" - "fn pick(a: two, s: str) i32 = {\n" - " return (a.sec + (s.len: i64)): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let x: two = two { sec = 42i64, nsec = 0i64 };\n" - " return pick(x, \"abc\");\n" - "};\n", - 45 }, - /* 4. Partial-fit stitch: 5 i64 args consume DI/SI/DX/CX/R8; - * the 16B struct hits idx=5 with regs_left=1, nw=2 — one - * half (struct word 0) lands in R9, the second half spills - * onto the caller's stack at +16(BP). Wwstage's struct arm - * stitches both into a contiguous local slot. Returns - * sum(s0..s4) + x.a + x.b = 1+2+3+4+5+11+22 = 48. */ - { "partial_fit_5i64_plus_16B", - "type pair = struct { a: i64, b: i64 };\n" - "fn manyfn(s0: i64, s1: i64, s2: i64, s3: i64, s4: i64,\n" - " x: pair) i32 = {\n" - " return (s0 + s1 + s2 + s3 + s4 + x.a + x.b): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let p: pair = pair { a = 11i64, b = 22i64 };\n" - " return manyfn(1i64, 2i64, 3i64, 4i64, 5i64, p);\n" - "};\n", - 48 }, -}; - -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/wcsbp_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcsbp_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcsbp_%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; -} - -/* asm_byte_identical — pin the spill prologue + frame size by - * diffing the w6c vs w6c_ww text output. The whole point of #11 - * is that wwstage's frame stops under-bloating for user-defined - * struct params, so the bytes must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcsbp_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcsbp_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcsbp_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "struct_byval_param: 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_byval_param[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "struct_byval_param: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("struct_byval_param: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/757_letbind_void_bang_void.c b/test/wcc/757_letbind_void_bang_void.c deleted file mode 100644 index ce970a0d..00000000 --- a/test/wcc/757_letbind_void_bang_void.c +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 757_letbind_void_bang_void — wwstage slotsize recognises `void` as - * zero-sized so `(void | !void)` (or any aliased equivalent) sizes its - * tagged slot at tag + 0 = 8B rather than tag + 8 = 16B. Surfaced by - * task #7's `fromutf8` (lib/strings/strings.ww), which let-binds the - * `(void | utf8.invalid)` result of `utf8.validate` before matching. - * - * Pre-fix (#48 part-A): wwstage's slotsize (selfhost/cmd/wcc/cgenutil.ww) - * lacked a `void`-as-0B case — primsize("void")==0 failed the `> 0` - * guard and fell through alias/struct lookup to the catch-all `return 8`. - * Aliased `!void` recursed into the same TNAME("void") branch with the - * same fallthrough. The N_TTAGGED branch then computed maxsz=8, slot = - * tag + pad(8) = 16. cglet routes the let-init through cgwidentaggedstore - * which sizes its AX→+0 / DX→+8 ABI receive by slot size — so wwstage - * emitted a phantom `MOVQ DX, off+8` spill, picking up the callee's - * stale DX (validate's bare `return;` doesn't zero it). Frame size - * diverged by 16B; every downstream offset shifted; bootstrap byte- - * identity broke as soon as any caller let-bound a `(void | !void)`. - * - * Cstage was already correct: cmd/wcc/type.c:46 sets `ty_void.size = 0`, - * cmd/wcc/check.c:432 reads it via `maxsz = 0; vsz = 0; size = 8 + 0`. - * - * Fix (#48, wwstage-only per rule 10): two narrow additions to - * `slotsize` in selfhost/cmd/wcc/cgenutil.ww — - * 1. N_TBANG case at the top: recurse on .lhs, mirroring cstage - * resolve_type N_TBANG which copies the inner type's size. - * 2. N_TNAME branch: `streq(nm, "void") => 0` before primsize, so - * the zero-size lands without the prim-pad-to-8 contract firing. - * - * What this test pins: - * - Asm byte-identity between cstage and wwstage for `(void | invalid)` - * let-bind through match, where invalid = !void (utf8.invalid shape). - * - Runtime: both arms return their expected exit code. - * - Frame size matches between stages (no $48 vs $32 drift in `foo`). - */ -#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. The canonical fromutf8 shape: let-bind `(void | invalid)`, - * match on it, return through both arms. validate always returns - * the void variant here. */ - { "letbind_void_arm", - "type invalid = !void;\n" - "fn validate() (void | invalid) = { return; };\n" - "fn main() i32 = {\n" - " let v: (void | invalid) = validate();\n" - " match (v) {\n" - " case void => return 7;\n" - " case let e: invalid => return 9;\n" - " };\n" - " return 0;\n" - "};\n", - 7 }, - /* 2. invalid arm — validate returns the !void variant. Pre-fix - * the phantom payload-spill picked up validate's stale DX; post- - * fix only the tag word lands in the slot. */ - { "letbind_invalid_arm", - "type invalid = !void;\n" - "fn validate() (void | invalid) = {\n" - " let e: invalid; return e;\n" - "};\n" - "fn main() i32 = {\n" - " let v: (void | invalid) = validate();\n" - " match (v) {\n" - " case void => return 7;\n" - " case let e: invalid => return 9;\n" - " };\n" - " return 0;\n" - "};\n", - 9 }, - /* 3. Natural fromutf8 form: success-arm lifts a plain str into - * `(str | invalid)`. Pre-fix the let-bind's bogus 16B slot - * pushed every downstream offset 8B further from BP; the lift - * itself (str → tagged-return ABI) was already correct, but - * frame-id drift broke byte-identity. */ - { "fromutf8_natural_match", - "type invalid = !void;\n" - "fn validate(s: str) (void | invalid) = { return; };\n" - "fn fromutf8(s: str) (str | invalid) = {\n" - " let v: (void | invalid) = validate(s);\n" - " match (v) {\n" - " case void => return s;\n" - " case let e: invalid => return e;\n" - " };\n" - " let e: invalid; return e;\n" - "};\n" - "fn main() i32 = {\n" - " match (fromutf8(\"hi\")) {\n" - " case let s: str => return s.len: i32;\n" - " case invalid => return -1;\n" - " };\n" - " return 0;\n" - "};\n", - 2 }, -}; - -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/wclbvv_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wclbvv_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wclbvv_%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; -} - -/* asm_byte_identical — pin frame size + spill layout by diffing the - * w6c vs w6c_ww text output. #48 part-A's whole point is that - * wwstage's slot stops bloating for the (void | !void) shape, so the - * bytes must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wclbvv_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wclbvv_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wclbvv_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "letbind_void_bang_void: 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, - "letbind_void_bang_void[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "letbind_void_bang_void: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("letbind_void_bang_void: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/759_check_enum_fold.c b/test/wcc/759_check_enum_fold.c deleted file mode 100644 index 5ed3a4d4..00000000 --- a/test/wcc/759_check_enum_fold.c +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 759_check_enum_fold — table-driven sentinel for #22 / A.6.2.1a. - * - * check-side enum fold (selfhost/cmd/wcc/check.ww enumvalfold) now - * mirrors cstage cmd/wcc/check.c:210-284 eval_enum_value: literals - * (INTLIT/RUNELIT), unary +/-/~, binary arithmetic (+ - * / %), - * bitwise (& | ^), shifts (<< >>), and sibling backref via - * N_IDENT. The N_DOT fold mutates the AST to N_INTLIT in place, so - * a wrong fold value leaks into cgen as the wrong literal and the - * exit code reflects it. Each row is a self-contained ww program - * whose `return EnumT.MEMBER: i32` carries the expected fold. - * - * Pre-#22 the check-side fold bailed on every shape but bare - * N_INTLIT and auto-increment; cgen's enumevalmember - * (selfhost/cmd/wcc/cgen.ww:158-227) absorbed the slack at codegen - * time. Post-#22 check.ww does the fold itself so A.6.2.1e's - * post-checker e.type_ assertion no longer fires on `EnumT.MEMBER` - * references whose body is a backref or arithmetic expression. - * - * Each row runs through the cstage `ww` driver and through `ww_ww` - * when present; the asm-byte-id diff between w6c and w6c_ww pins - * the symmetric-emit contract (CLAUDE.md rule 10) so a future - * drift between the two enumvalfold copies fails loud. - */ -#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 N_INTLIT — regression: the literal-only path was the - * pre-#22 fold contract and must still resolve. */ - { "intlit", - "type e = enum i32 { A = 7 };\n" - "fn main() i32 = { return e.A: i32; };\n", - 7 }, - - /* N_RUNELIT — rune literals carry their codepoint in n.uval - * exactly like N_INTLIT (cmd/wcc/check.c:191). */ - { "runelit", - "type e = enum i32 { A = 'A' };\n" - "fn main() i32 = { return e.A: i32; };\n", - 65 }, - - /* Bare sibling backref (no op): B = A. Tests the N_IDENT arm - * without a wrapping N_BIN/N_UN. */ - { "sibling_backref", - "type e = enum i32 { A = 7, B = A };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - /* N_UN TK_MINUS over literal, fed through a sibling backref. The - * NEG member exercises N_UN; the R member exercises the IDENT - * lookup that must resolve NEG's already-folded value. */ - { "unary_minus", - "type e = enum i32 { NEG = -7, R = NEG + 14 };\n" - "fn main() i32 = { return e.R: i32; };\n", - 7 }, - - /* Nested unary: ~(-8) folds to 7. Tests N_UN recursion (TK_TILDE - * over TK_MINUS over N_INTLIT). */ - { "unary_tilde_nested", - "type e = enum i32 { A = ~(-8) };\n" - "fn main() i32 = { return e.A: i32; };\n", - 7 }, - - /* N_UN TK_PLUS — noop wrapper; completes the unary whitelist. */ - { "unary_plus", - "type e = enum i32 { A = +7 };\n" - "fn main() i32 = { return e.A: i32; };\n", - 7 }, - - /* N_BIN TK_PLUS with sibling backref — the headline RW=R|W - * shape's relative. */ - { "bin_plus", - "type e = enum i32 { A = 3, B = A + 4 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - { "bin_minus", - "type e = enum i32 { A = 10, B = A - 3 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - { "bin_star", - "type e = enum i32 { A = 2, B = A * 4 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 8 }, - - { "bin_slash", - "type e = enum i32 { A = 14, B = A / 2 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - { "bin_percent", - "type e = enum i32 { A = 17, B = A % 10 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - { "bin_amp", - "type e = enum i32 { A = 15, B = A & 7 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - /* N_BIN TK_PIPE — overlaps with 700_e2e's `RW = R | W` row, kept - * here for symmetry with the rest of the binop set. */ - { "bin_pipe", - "type e = enum i32 { A = 4, B = A | 3 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - { "bin_caret", - "type e = enum i32 { A = 5, B = A ^ 2 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - { "bin_lshift", - "type e = enum i32 { A = 1, B = A << 3 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 8 }, - - { "bin_rshift", - "type e = enum i32 { A = 28, B = A >> 2 };\n" - "fn main() i32 = { return e.B: i32; };\n", - 7 }, - - /* Chained sibling backref — pins the O(N²) walker contract. - * Each member's lhs ident lookup re-walks the prefix; if the - * `until` bound were >= instead of > (off-by-one) the lookup - * of D would walk past C and the fold would diverge or loop. */ - { "chained_backref", - "type e = enum i32 { A = 1, B = A + 1, C = B + 1, D = C + 4 };\n" - "fn main() i32 = { return e.D: i32; };\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/cef_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/cef_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/cef_%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 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; -} - -/* asm_byte_identical — diff w6c against w6c_ww for the same source. - * Pins the symmetric-emit contract: a future drift between cstage's - * eval_enum_value and wwstage's enumvalfold would show up here as a - * byte diff even if both stages produce semantically-correct - * constants. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/cef_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/cef_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/cef_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "check_enum_fold: 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, - "check_enum_fold[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - /* Asm byte-identity diff, only when wwstage is built. */ - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "check_enum_fold: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("check_enum_fold: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/761_inferred_struct_arg_push.c b/test/wcc/761_inferred_struct_arg_push.c deleted file mode 100644 index 1bfd427a..00000000 --- a/test/wcc/761_inferred_struct_arg_push.c +++ /dev/null @@ -1,351 +0,0 @@ -/* - * 761_inferred_struct_arg_push — regression lock for project #16, the - * inferred-let-struct-local → call-arg push width. Closed incidentally - * by ea1579a (SK_USE-gated N_DOT call result), 7198937 (asserttyped bail - * rearm), 39f9267 + 66a91c8 (#169b structabisize convergence cascade). - * - * Each closing commit removed one path that could have left the local's - * type-AST unstamped — together they guarantee cglet's `tn = n.lhs ?? - * inferletcalltype(...)` and the localadd'd `lc.tnode` always carry a - * resolvable N_TNAME for an inferred struct-typed let. pushargsrev's - * N_IDENT struct arm (selfhost/cmd/wcc/cgenutil.ww:459) then reaches - * structparamsize > 0 and emits the maxalign-rounded 1-or-2 word push, - * byte-id with cstage's `args[i]->type` → struct_arg_size path - * (cmd/w6c/cgen.c:427, :5452). - * - * The 16-shape impl-16 probe sweep (worktree-impl-16) confirmed - * byte-identity across every reasonable trigger. This file consolidates - * the load-bearing rows so the symmetric behavior cannot regress without - * a green-to-red flip on these probes. - * - * WHAT THIS LOCKS - * - cs==ww .s byte-identity for inferred-let + struct-arg-push - * across struct shapes: - * * {i64,i64} — canonical 16B, maxalign 8 - * * {u32,i64} — decf32-shape (ken-flagged - * ftos.ww:411 refactor target), 16B with align-8 tail - * * {i64,i32} — narrow-tail (#169 maxalign-8 - * tail-padded ABI) - * * {i32,i32,i32} — maxalign 4, slot-padded 16B - * but ABI 12B (cstage struct_arg_size returns 12, both - * stages still emit 2-word push since stsz>8) - * - cs==ww runtime exit codes for the same shapes - * - Inferral paths covered: - * * `let p = make(...);` (direct N_CALL infer) - * * `let q = make(...); let p = q;` (N_IDENT-rhs infer chain) - * * `let p = make_big(...);` for >24B sret (no register-ABI - * push, but exercises let_isarray=false sret-recv → ident - * push path) - * - Branched callee (per #105 lesson — single-return masks - * reg-routing via coincidence): both `make` and the consumer - * branch on a flag so the GP register routing is exercised - * through both arms. - * - * WHAT THIS DOES NOT COVER (separate filed bugs — do NOT bundle) - * - #173: `let p = call_returning_tagged()!` drops CX/word1 on - * receive (wwstage) and elides the CALL entirely (cstage). Live - * miscompile, separate impl. - * - #176: `let x = w.p` where p is a struct field — wwstage emits - * 1-word memcpy (under-copy of nested-struct field). - * - #175: `let p = arr[0]` — both stages buggy in different ways. - * - #177: aliased struct return (`type alias_p = point; - * fn make() alias_p`) — cstage emits 1-word push (OPPOSITE of - * the #16 stated symptom polarity). - * - #174: `let p = call(): T;` cast in rhs — cstage drops the - * call. Both-stages broken. - * - * GATE POLARITY: this file must stay GREEN. A red here means one of - * the closing commits regressed. Bisect against the 4 cited commits. - */ -#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. Canonical {i64,i64} — `let p = make(0);` inferred, then - * `sum(p)` passes p as the struct arg. Branched callee on flag. - * Returns p.a + p.b = 10 + 20 = 30. */ - { "i64_i64_infer_direct", - "type point = struct { a: i64, b: i64 };\n" - "fn make(flag: i32) point = {\n" - " if (flag == 0) { return point { a = 10i64, b = 20i64 }; };\n" - " return point { a = 100i64, b = 200i64 };\n" - "};\n" - "fn sum(p: point, which: i32) i32 = {\n" - " if (which == 0) { return (p.a + p.b): i32; };\n" - " return (p.a - p.b): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let p = make(0);\n" - " return sum(p, 0);\n" - "};\n", - 30 }, - /* 2. {u32,i64} — decf32-shape. The narrow-mantissa-first then - * widening-exponent layout is the strconv ftos.ww:411 decf32 - * (ken-flagged as one-refactor-away). Returns - * (mantissa: i64) + exponent = 7 + 13 = 20. */ - { "u32_i64_decf_infer", - "type decf = struct { mantissa: u32, exponent: i64 };\n" - "fn make(flag: i32) decf = {\n" - " if (flag == 0) { return decf { mantissa = 7u32, exponent = 13i64 }; };\n" - " return decf { mantissa = 99u32, exponent = -1i64 };\n" - "};\n" - "fn use_(d: decf, which: i32) i32 = {\n" - " if (which == 0) { return ((d.mantissa: i64) + d.exponent): i32; };\n" - " return ((d.mantissa: i64) - d.exponent): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let d = make(0);\n" - " return use_(d, 0);\n" - "};\n", - 20 }, - /* 3. {i64,i32} narrow-tail — natural 12B, maxalign 8, ABI 16B - * via #169 structabisize round-up. Branched callee on flag. - * Returns p.a + (p.b: i64) = 50 + 7 = 57. */ - { "i64_i32_narrow_tail_infer", - "type ntail = struct { a: i64, b: i32 };\n" - "fn make(flag: i32) ntail = {\n" - " if (flag == 0) { return ntail { a = 50i64, b = 7i32 }; };\n" - " return ntail { a = 500i64, b = 70i32 };\n" - "};\n" - "fn use_(p: ntail, which: i32) i32 = {\n" - " if (which == 0) { return (p.a + (p.b: i64)): i32; };\n" - " return (p.a - (p.b: i64)): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let p = make(0);\n" - " return use_(p, 0);\n" - "};\n", - 57 }, - /* 4. {i32,i32,i32} — maxalign 4, ABI 12B (cstage); wwstage si - * .totsize 16. Both stages still push 2 words since stsz > 8; - * the 2-word push is THE bug #16 watched for. Returns - * p.a + p.b + p.c = 1 + 2 + 3 = 6. */ - { "i32_x3_maxalign4_infer", - "type three = struct { a: i32, b: i32, c: i32 };\n" - "fn make(flag: i32) three = {\n" - " if (flag == 0) { return three { a = 1i32, b = 2i32, c = 3i32 }; };\n" - " return three { a = 10i32, b = 20i32, c = 30i32 };\n" - "};\n" - "fn use_(p: three, which: i32) i32 = {\n" - " if (which == 0) { return p.a + p.b + p.c; };\n" - " return p.a * p.b * p.c;\n" - "};\n" - "fn main() i32 = {\n" - " let p = make(0);\n" - " return use_(p, 0);\n" - "};\n", - 6 }, - /* 5. Ident-rhs inference chain — `let p = q;` where q itself - * was inferred from a call. Pre-bail-rearm the checker's - * `n.lhs = src` walk had to propagate q's resolved type through - * exprtype(N_IDENT); a stale path would leave p's lc.tnode nil - * and structparamsize → 0 → 1-word push. Returns - * p.a + p.b = 11 + 22 = 33. */ - { "ident_rhs_chain_infer", - "type point = struct { a: i64, b: i64 };\n" - "fn make(flag: i32) point = {\n" - " if (flag == 0) { return point { a = 11i64, b = 22i64 }; };\n" - " return point { a = 110i64, b = 220i64 };\n" - "};\n" - "fn sum(p: point, which: i32) i32 = {\n" - " if (which == 0) { return (p.a + p.b): i32; };\n" - " return (p.a - p.b): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let q = make(0);\n" - " let p = q;\n" - " return sum(p, 0);\n" - "};\n", - 33 }, - /* 6. >24B sret RECEIVE — exercises the cglet sret-dest wiring - * (selfhost/cmd/wcc/cgenstmt.ww:1231 callsretsize branch). The - * let's own slot is the hidden RDI dest pointer; the callee - * writes through it. The struct is then passed BY POINTER to - * the consumer (NOT by value) — the by-value >24B param ABI is - * orthogonal to #16's call-arg-push scope and has its own - * filed gaps. Returns - * p.a + p.b + p.c + p.d = 1 + 2 + 3 + 4 = 10. */ - { "big_sret_infer_then_ptr_arg", - "type big = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn make(flag: i32) big = {\n" - " if (flag == 0) { return big { a = 1i64, b = 2i64, c = 3i64, d = 4i64 }; };\n" - " return big { a = 10i64, b = 20i64, c = 30i64, d = 40i64 };\n" - "};\n" - "fn use_(p: *big, which: i32) i32 = {\n" - " if (which == 0) { return (p.a + p.b + p.c + p.d): i32; };\n" - " return (p.a * p.b * p.c * p.d): i32;\n" - "};\n" - "fn main() i32 = {\n" - " let p = make(0);\n" - " return use_(&p, 0);\n" - "};\n", - 10 }, -}; - -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/wcisp_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcisp_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcisp_%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. 760); test/run does not - * bound individual binaries, so an unguarded hang from a future - * cgen regression would stall make test instead of redding here. */ - 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; -} - -/* asm_byte_identical — diff w6c vs w6c_ww text output. The closing - * commits (ea1579a + 7198937 + 39f9267 + 66a91c8) ensure both stages - * emit the same struct-arg push width for every shape; a red here - * means one of those reverted (#16 surfaces). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcisp_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcisp_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcisp_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "timeout 180 %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, "timeout 180 %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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "inferred_struct_arg_push: 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, - "inferred_struct_arg_push[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "inferred_struct_arg_push: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("inferred_struct_arg_push: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/762_struct_abi_size.c b/test/wcc/762_struct_abi_size.c deleted file mode 100644 index ed415e33..00000000 --- a/test/wcc/762_struct_abi_size.c +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 762_struct_abi_size — defensive lock for project #78. cstage once - * folded size(struct{i32,i32,i32}) to 12 while wwstage folded it to - * 16; the two checkers walked different layout formulas. After - * d4e500f (#169) introduced structabisize and the cgen ABI sites - * converged onto a single (off + maxalign - 1) & ~(maxalign - 1) - * formula on both sides, every layout-edge shape should agree — - * BOTH at the size(T) fold (cstage check.c:760 / wwstage check.ww - * astsize N_TSTRUCT) and at the register-RECV/RETURN ABI walker - * (cgen.c struct_arg_size / structabisize). This probe pins both. - * - * The fold and the ABI walker share the formula but live in two - * different sources: check.c:760 vs check.ww:958 (the language - * builtin) and cmd/w6c/cgen.c struct_arg_size vs - * selfhost/cmd/wcc/cgenutil.ww structabisize (the cgen ABI). Either - * can drift independently — locking only the runtime exit code via - * size(T) misses an ABI-walker regression that does not change the - * literal fold; locking only the .s byte-id misses a fold regression - * the cgen would happily honour. Both gates run per row. - * - * Expected size is COMPUTED from a (sz, aln) field table per row, - * not hardcoded — a stage-internal formula bump (rule 13) lands the - * change in compute_expected() and every row tracks. The probe must - * red the moment any source-of-truth drifts from the documented - * Hare-style layout: align(off, fa); off += fs; size = align(off, - * maxalign). - * - * Shape coverage targets the maxalign edges where the bug class - * lived: - * 1. {3xi32} — #78 canonical, maxalign 4, natural 12, - * ABI 12 (no round-up) - * 2. {i32,i32,i64} — maxalign 8, narrow lead, ABI 16 - * 3. {i64,i32,i32} — maxalign 8 with sub-8 tail, ABI 16 - * (the #169 round-up case) - * 4. {3xi16} — maxalign 2, natural 6, ABI 6 - * 5. {i16,i64} — maxalign 8 with mid-record alignment - * padding (f2 must skip off 2→8) - * 6. {3xi64} — maxalign 8, all 8-wide, natural==ABI=24 - * - * GATE POLARITY: this file must stay GREEN. A red here means either - * d4e500f (#169 structabisize) reverted, or one of the two layout - * walkers drifted from the formula. - */ -#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 field { int sz; int aln; }; - -struct row { - const char *label; - const char *src; /* must `return size(T): i32` */ - int nfields; - struct field fields[8]; -}; - -/* Hare / cstage check.c:701-760 / wwstage check.ww astsize N_TSTRUCT - * formula. Per CLAUDE.md rule 13 the expected size for each row is - * derived here, not literalled into the row table. */ -static int -compute_expected(const struct field *f, int n) -{ - int off = 0, maxalign = 1; - for (int i = 0; i < n; i++) { - int a = f[i].aln; - if (a > maxalign) maxalign = a; - off = (off + a - 1) & ~(a - 1); - off += f[i].sz; - } - return (off + maxalign - 1) & ~(maxalign - 1); -} - -static const struct row rows[] = { - /* 1. {3xi32} — #78 canonical. cstage check.c:760 returned 12, - * wwstage formerly walked a slot-padded ladder yielding 16. The - * fold now uses astsize's (off + maxal - 1) & ~(maxal - 1) on - * both sides; expect 12. */ - { "i32_x3_maxalign4", - "type t = struct { a: i32, b: i32, c: i32 };\n" - "export fn main() i32 = { return size(t): i32; };\n", - 3, {{4,4},{4,4},{4,4}} }, - /* 2. {i32,i32,i64} — maxalign 8, off lands at 8 before the i64, - * total 16. */ - { "i32_i32_i64_lead_narrow", - "type t = struct { a: i32, b: i32, c: i64 };\n" - "export fn main() i32 = { return size(t): i32; };\n", - 3, {{4,4},{4,4},{8,8}} }, - /* 3. {i64,i32,i32} — natural extent 16, maxalign 8: the post- - * field-3 round-up is a no-op here, but the #169 commit added - * exactly this round-up so the structabisize walker matches - * cstage on the maxalign-8 sub-8-tail shape (cf. {i64,i32}). */ - { "i64_i32_i32_sub8_tail", - "type t = struct { a: i64, b: i32, c: i32 };\n" - "export fn main() i32 = { return size(t): i32; };\n", - 3, {{8,8},{4,4},{4,4}} }, - /* 4. {3xi16} — maxalign 2, natural 6. Guards against a future - * "round to 8" shortcut creeping back into either walker. */ - { "i16_x3_maxalign2", - "type t = struct { a: i16, b: i16, c: i16 };\n" - "export fn main() i32 = { return size(t): i32; };\n", - 3, {{2,2},{2,2},{2,2}} }, - /* 5. {i16,i64} — exercises a non-trivial mid-record align step: - * after f1 off=2, f2 must align off 2→8 before placing the i64. - * A missing `off = align(off, fa)` would land f2 at off=2 and - * size to 10 (or 16 only via the final maxalign round); either - * way it would diverge from the formula. {i32,i32,i64} (rows 2) - * already aligns at 8 by the natural off, so the align-step is - * a no-op there. */ - { "i16_i64_mid_pad", - "type t = struct { a: i16, b: i64 };\n" - "export fn main() i32 = { return size(t): i32; };\n", - 2, {{2,2},{8,8}} }, - /* 6. {3xi64} — natural==ABI, the maxalign-rounded formula must - * not double-round. */ - { "i64_x3_natural_24", - "type t = struct { a: i64, b: i64, c: i64 };\n" - "export fn main() i32 = { return size(t): i32; };\n", - 3, {{8,8},{8,8},{8,8}} }, -}; - -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/wcabi_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcabi_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcabi_%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. 760, 761); 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; -} - -/* asm_byte_identical — diff w6c vs w6c_ww text output. The cgen ABI - * walker (structabisize since #169 / d4e500f) feeds the struct- - * return + struct-arg push widths; a divergence on these shapes - * surfaces as a .s difference even when the size(T) fold still - * matches (the two walkers are independent SSoTs). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcabi_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcabi_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcabi_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "timeout 180 %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, "timeout 180 %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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "struct_abi_size: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < n; i++) { - int want = compute_expected(rows[i].fields, - rows[i].nfields); - int got = run_driver(drivers[d].path, &rows[i], i); - total++; - if (got != want) { - fprintf(stderr, - "struct_abi_size[%s][%s]: size=%d want=%d\n", - drivers[d].name, rows[i].label, - got, want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "struct_abi_size: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("struct_abi_size: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/766_star_fn_deref_call.c b/test/wcc/766_star_fn_deref_call.c deleted file mode 100644 index 0ecdfba4..00000000 --- a/test/wcc/766_star_fn_deref_call.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 766_star_fn_deref_call — root-cause lock for project #181, and the - * live verification of the full deref-call c-cluster (#180 + #185 + - * #181 working together end-to-end). - * - * Pre-#181 wwstage's checker bailed asserttyped on the N_CALL whose - * callee was N_UN TK_STAR over a *fn — selfhost/cmd/wcc/check.ww - * exprtype's N_CALL arm only resolved IDENT/DOT-named callees and - * early-returned nil for any other shape, leaving e.type_ unstamped - * so the post-checker invariant gate fired. With cgen post-#180+#185 - * already correct for the deref-call lowering, the safe fix was to - * lift the asserttyped bail and route the callee through the - * fn-VALUE fallback (autodereference + dealias to TY_FN, mirroring - * harec check_autodereference at ref/harec/src/check.c:1566). - * - * Fix: selfhost/cmd/wcc/check.ww exprtype N_CALL arm — replace the - * `if (nm.len == 0) return nil` early-bail with `if (nm.len > 0) - * { name-lookup }`, so non-named callees fall through to the - * existing fn-VALUE branch (peel TPTR, dealias to TFN, stamp the - * result type). cstage check.c already worked because cexpr - * recurses on the callee — TK_STAR's unop arm returns t->sub which - * is TY_FN directly, no name path needed. - * - * Gate flip from 765: 765 was cstage-only because wwstage bailed - * before reaching cgen. Post-#181 every row gates BOTH stages — - * cstage runtime, wwstage runtime, AND cs.s == ww.s byte-id. This - * is the runtime coverage 765 deferred plus the symmetry gate that - * proves both stages emit identical asm for the deref-call shape. - * - * Coverage (same 5 rows as 765, now with STAGE_CS|STAGE_WW): - * 1. minimal — `let f = &add1; (*f)(7) == 8` - * 2. branched_callee — pick aa or bb by runtime cond - * 3. alias_chain — `let f = &fn; let g = f; (*g)(7)` - * 4. fn_with_args — multiple args, scalar + ptr mix - * 5. fn_tuple_return — (i32, i32) return shape; multi-reg - * return ABI survives the deref-call - * - * Gates per row: - * a. cstage builds + runs, exit == expected (already worked - * post-#180+#185; kept for c-cluster regression coverage). - * b. wwstage builds + runs, exit == expected — the #181 lift - * gate; pre-fix this row's asserttyped bailed before cgen. - * c. cstage .s == wwstage .s byte-identical — rule-10 symmetry, - * proves the checker change doesn't perturb the codegen. - * - * GATE POLARITY: must stay GREEN. A red here means either the - * checker N_CALL fn-VALUE route regressed, the cgen deref-call - * lowering regressed (#180/#185), or stage symmetry drifted. - */ -#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; -} - -#define STAGE_CS 1 -#define STAGE_WW 2 - -struct row { - const char *label; - const char *src; - int expected_exit; - int stage_mask; -}; - -static const struct row rows[] = { - { "minimal", - "fn add1(x: i32) i32 = { return x + 1; };\n" - "export fn main() i32 = {\n" - " let f: *fn(x: i32) i32 = &add1;\n" - " return (*f)(7);\n" - "};\n", - 8, - STAGE_CS | STAGE_WW }, - /* Branched-callee: the if-arm forces a runtime choice between - * two distinct fn addresses, defeating any constant-aliasing - * mask of the deref miscompile (#105 lesson). */ - { "branched_callee", - "fn aa(x: i32) i32 = { return x; };\n" - "fn bb(x: i32) i32 = { return x + 100; };\n" - "export fn main() i32 = {\n" - " let pick: i32 = 1;\n" - " let f: *fn(x: i32) i32 = &aa;\n" - " if (pick != 0) { f = &bb; };\n" - " return (*f)(7);\n" - "};\n", - 107, - STAGE_CS | STAGE_WW }, - { "alias_chain", - "fn add1(x: i32) i32 = { return x + 1; };\n" - "export fn main() i32 = {\n" - " let f: *fn(x: i32) i32 = &add1;\n" - " let g: *fn(x: i32) i32 = f;\n" - " return (*g)(7);\n" - "};\n", - 8, - STAGE_CS | STAGE_WW }, - { "fn_with_args", - "fn many(a: i32, b: i32, p: *i32) i32 = { return a + b + *p; };\n" - "export fn main() i32 = {\n" - " let z: i32 = 5;\n" - " let f: *fn(a: i32, b: i32, p: *i32) i32 = &many;\n" - " return (*f)(3, 7, &z);\n" - "};\n", - 15, - STAGE_CS | STAGE_WW }, - /* Tuple-return: exercises the multi-register return ABI through - * the deref-call. (i32, i32) keeps it simple while still covering - * the multi-reg path. */ - { "fn_tuple_return", - "fn pair(x: i32) (i32, i32) = { return (x, x + 1); };\n" - "export fn main() i32 = {\n" - " let f: *fn(x: i32) (i32, i32) = &pair;\n" - " let a, b = (*f)(7);\n" - " return a + b;\n" - "};\n", - 15, - STAGE_CS | STAGE_WW }, -}; - -static int -write_source(const char *src_path, const char *src) -{ - FILE *f = fopen(src_path, "wb"); - if (!f) return -1; - wwtest_fputs(src, f); - fclose(f); - return 0; -} - -static void -cleanup_tmp(const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", 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); - /* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - char cmd[1024]; - /* #93 sep layout: emit asm to .sepwork/__root.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -/* run_row — build + run; returns the binary's exit code (-1 on - * build failure). */ -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[256], base[64], outbin[512]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wcsfdc_%d_d_%d", getpid(), seq); - snprintf(src, sizeof src, "%s/main766.ww", tmpdir); - snprintf(base, sizeof base, "main766"); - mkdir(tmpdir, 0755); - if (write_source(src, r->src) != 0) { - cleanup_tmp(tmpdir, base); - return -1; - } - int rc = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } - cleanup_tmp(tmpdir, base); - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (filed bug per CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/wcsfdc_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/wcsfdc_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main766"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/main766.ww", tdc); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/main766.ww", tdw); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, base); - 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 cdrv[1024], wdrv[1024]; - 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "star_fn_deref_call[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "star_fn_deref_call[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "star_fn_deref_call[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - - if (!wwpresent) - fprintf(stderr, "star_fn_deref_call: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "star_fn_deref_call: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("star_fn_deref_call: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/767_match_variant_dispatch.c b/test/wcc/767_match_variant_dispatch.c deleted file mode 100644 index 85ace00c..00000000 --- a/test/wcc/767_match_variant_dispatch.c +++ /dev/null @@ -1,344 +0,0 @@ -/* - * 767_match_variant_dispatch — project #179: wwstage cgmatch's - * non-nullable arm gated its flat-tag dispatch on `pat.kind == - * N_TNAME` (with an `N_TSLICE` else-branch for task #19's untyped - * elem fallback). N_TPTR / N_TFN / N_TPTR(N_TFN) case-patterns fell - * through both arms, leaving the local `r` at -1 → `want = 0`, so - * every variant past 0 silently collapsed to tag 0 — the kind of - * miscompile that runtime-passes only when the value happens to be - * variant 0 (zero-coincidence). - * - * Cstage cg_tag_for_variant (cmd/w6c/cgen.c:624) walks Type directly - * and is kind-agnostic; it has never had this gap. Drew's settled - * principle: match dispatch is resolved-type-only (harec - * check.c:2527 stores `_case->type = ctype`). Project #66 Phase-N - * already flipped the typeeq compare; #179 was the one site that - * still keyed on AST kind. Memory: project_tinfo_lossy_nominal. - * - * Fix: selfhost/cmd/wcc/cgenexpr.ww cgmatch non-nullable arm — - * route through `flatvariantidxt(scrutt.type_, pat.type_)` directly, - * guarded only by `istaggedtype(scrutt)` + `typeisslice(pattype)` - * for the slice axis. No new helper; existing flatvariantidxt / - * flatslicevariantidx wire up unchanged. - * - * Coverage (5 rows): - * 1. nullable_ptr_fn — `(*fn(i32) i32 | void)`, branched - * store (#105 lesson: two distinct - * fns picked at runtime), match via - * indirect call. KEN'S VERIFY GATE: - * row 1 byte-id proves wwstage's - * nullable-fold ordering matches - * cstage cg_tag_for_variant — fail - * here flags a separate-signoff fold. - * Goes through the nullable disc path - * (line 1484-1502, untouched by #179). - * 2. ptr_variants_past_zero — `(*i32 | *i64)` storing &i64. - * Pre-fix wwstage emits CMPQ $0 for - * the *i64 arm; post-fix CMPQ $1. - * 3. fn_ptr_variants_via_local — `(*fn(i32) i32 | *fn(i64) i64)` - * built from a local. Direct N_TPTR - * over N_TFN case-pattern is the new- - * shape exercise; store path goes via - * ident (not `&fn` inline) to dodge - * the fn-variant store-side sibling - * bug (see below). - * 4. stored_variant1_roundtrip — `(*i32 | *i64)` with branched - * runtime variant choice (pick = 1 - * forces variant 1; defeats const- - * fold to variant 0). Mirrors #105. - * 5. aliased_ptr_variants — `type pa = *i32; type pb = *i64; - * (pa | pb)`. typeeq through TY_NAMED - * wrappers — the alias-aware path that - * the surface-name compare couldn't - * key on. OR-pattern was the original - * row 5, dropped because the wwstage - * parser doesn't accept `case T1 | T2`. - * - * Per-row gates: cstage runtime exit, wwstage runtime exit, - * cs.s == ww.s byte-identical (rule-10 stage symmetry). - * - * Sibling bug surfaced (not fixed here, filed inline): widening - * `&fn` INLINE into a `(*fn(...) | *fn(...))` slot computes the - * wrong tag in wwstage — taggedvariantindext / typeeq on the - * synthetic fn-ptr tinfo built from N_UN TK_AMP IDENT-of-FN doesn't - * match the variant tinfo, so tag collapses to 0. Storing via an - * intermediate ident-typed local (row 3 shape) routes through a - * different store path and tags correctly. cstage cg_widen_tagged_ - * store hits the same node with the same Type and tags correctly, - * so the divergence is wwstage-only on the inline-amp shape. Out - * of scope for #179 (which is the match-DISPATCH side, not the - * widen-STORE side). - * - * GATE POLARITY: must stay GREEN. A red here means the cgmatch - * non-nullable dispatch regressed, the nullable arm got perturbed, - * or stage symmetry drifted on the new typeeq route. - */ -#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; -} - -#define STAGE_CS 1 -#define STAGE_WW 2 - -struct row { - const char *label; - const char *src; - int expected_exit; - int stage_mask; -}; - -static const struct row rows[] = { - /* KEN'S VERIFY GATE: nullable disc path must byte-id even - * though #179 didn't touch it. A divergence here flags a - * separate flatvariantidxt nullable-fold ordering issue. */ - { "nullable_ptr_fn", - "fn ai(x: i32) i32 = { return x + 1; };\n" - "fn bi(x: i32) i32 = { return x + 100; };\n" - "fn indi(f: *fn(x: i32) i32, x: i32) i32 = { return (*f)(x); };\n" - "export fn main() i32 = {\n" - " let pick: i32 = 1;\n" - " let v: (*fn(x: i32) i32 | void) = &ai;\n" - " if (pick != 0) { v = &bi; };\n" - " let r: i32 = match (v) {\n" - " case void => yield 99: i32;\n" - " case let f: *fn(x: i32) i32 => yield indi(f, 7);\n" - " };\n" - " return r;\n" - "};\n", - 107, - STAGE_CS | STAGE_WW }, - { "ptr_variants_past_zero", - "export fn main() i32 = {\n" - " let a: i64 = 42;\n" - " let v: (*i32 | *i64) = &a;\n" - " let r: i32 = match (v) {\n" - " case *i32 => yield 11: i32;\n" - " case *i64 => yield 22: i32;\n" - " };\n" - " return r;\n" - "};\n", - 22, - STAGE_CS | STAGE_WW }, - { "fn_ptr_variants_via_local", - "fn ai(x: i32) i32 = { return x + 1; };\n" - "fn bi(x: i64) i64 = { return x + 1; };\n" - "fn indl(f: *fn(x: i64) i64, x: i64) i64 = { return (*f)(x); };\n" - "export fn main() i32 = {\n" - " let g: *fn(x: i64) i64 = &bi;\n" - " let v: (*fn(x: i32) i32 | *fn(x: i64) i64) = g;\n" - " let r: i32 = match (v) {\n" - " case let p: *fn(x: i32) i32 => yield 1: i32;\n" - " case let q: *fn(x: i64) i64 => yield indl(q, 9): i32;\n" - " };\n" - " return r;\n" - "};\n", - 10, - STAGE_CS | STAGE_WW }, - { "stored_variant1_roundtrip", - "export fn main() i32 = {\n" - " let a: i32 = 5;\n" - " let b: i64 = 9;\n" - " let pick: i32 = 1;\n" - " let v: (*i32 | *i64) = &a;\n" - " if (pick != 0) { v = &b; };\n" - " let r: i32 = match (v) {\n" - " case *i32 => yield 11: i32;\n" - " case *i64 => yield 22: i32;\n" - " };\n" - " return r;\n" - "};\n", - 22, - STAGE_CS | STAGE_WW }, - { "aliased_ptr_variants", - "type pa = *i32;\n" - "type pb = *i64;\n" - "export fn main() i32 = {\n" - " let a: i64 = 100;\n" - " let g: pb = &a;\n" - " let v: (pa | pb) = g;\n" - " let r: i32 = match (v) {\n" - " case pa => yield 11: i32;\n" - " case pb => yield 22: i32;\n" - " };\n" - " return r;\n" - "};\n", - 22, - STAGE_CS | STAGE_WW }, -}; - -static int -write_source(const char *src_path, const char *src) -{ - FILE *f = fopen(src_path, "wb"); - if (!f) return -1; - wwtest_fputs(src, f); - fclose(f); - return 0; -} - -static void -cleanup_tmp(const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", 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); - /* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - char cmd[1024]; - /* #93 sep layout: emit asm to .sepwork/__root.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[256], base[64], outbin[512]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/mvd_%d_d_%d", getpid(), seq); - snprintf(src, sizeof src, "%s/main767.ww", tmpdir); - snprintf(base, sizeof base, "main767"); - mkdir(tmpdir, 0755); - if (write_source(src, r->src) != 0) { - cleanup_tmp(tmpdir, base); - return -1; - } - int rc = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } - cleanup_tmp(tmpdir, base); - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (filed bug per CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/mvd_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/mvd_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main767"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/main767.ww", tdc); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/main767.ww", tdw); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, base); - 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 cdrv[1024], wdrv[1024]; - 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "match_variant_dispatch[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "match_variant_dispatch[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "match_variant_dispatch[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - - if (!wwpresent) - fprintf(stderr, "match_variant_dispatch: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "match_variant_dispatch: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("match_variant_dispatch: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/769_dot_aliased_ptr.c b/test/wcc/769_dot_aliased_ptr.c deleted file mode 100644 index 4433c422..00000000 --- a/test/wcc/769_dot_aliased_ptr.c +++ /dev/null @@ -1,352 +0,0 @@ -/* - * 769_dot_aliased_ptr — project #191: wwstage cgdot N_DOT on an - * aliased-pointer receiver. `type vt = struct{...}; type vs = *vt; - * fn f(s: vs) { s.field }` — wwstage's cgdot read the receiver's - * tnode kind WITHOUT first peeling N_TNAME alias chains, so lkind - * stayed N_TNAME (not the underlying N_TPTR), structlookupchain - * missed (`vs` isn't a struct alias), and the lookup fell through - * to the SB-global fallback. The emitted asm was - * MOVQ field(SB), AX - * which links as an undefined symbol — runtime never reached. - * - * Cstage (cmd/w6c/cgen.c:7001-7004) uses type_chase_named to walk - * TY_NAMED.under to the underlying TY_PTR / TY_STRUCT before the - * kind-gated arms fire. Mirror: a loop via `aliaslookup(c, name)` - * at the top of cgdot's lc != nil branch, stopping at struct - * aliases so the existing direct-struct N_TNAME arm below stays - * byte-id with pre-fix #22 callers. - * - * Fix: selfhost/cmd/wcc/cgenexpr.ww cgdot, after `let tn = lc.tnode;` - * insert a peel loop before the lkind decision. LOOP not single- - * peel — Phase-N builds N_TNAME chains (memory - * project_tinfo_lossy_nominal), so depth-2+ aliases require - * iteration. Inner peel on the pointee is unnecessary because the - * existing `structlookupchain(c, inner)` already walks N_TNAME - * chains via aliaslookup (cgenutil.ww:1266-1276); row 4 below - * proves the inner-chain depth-3 path stays green without an - * explicit inner peel. - * - * Coverage (4 rows): - * 1. fn_param_read — `fn f(s: vs) { s.field }`. Single- - * alias receiver. Pre-fix: link - * failure on `field(SB)`. Post-fix: - * deref + offset load. The impl-e1 - * fold-blocker exact shape. - * 2. let_binding_read — `let s: vs = (&v): vs; s.field`. - * Single-alias via let-init; the - * explicit cast routes around the - * cstage checker assignability gap - * for chain-depth-1 (see row 3). - * 3. double_alias_read — `type pvt = *vt; type vs = pvt;` - * with `let s: vs = (&v): vs`. - * KEN'S LOOP-PEEL VERIFY GATE: - * chain depth 2 on the receiver, so - * a single-peel implementation would - * stop at N_TNAME("pvt") and miss - * via structlookupchain (which only - * bottoms out when aliaslookup - * returns an N_TNAME that - * structlookup hits — here the next - * hop is N_TPTR, breaking its - * inner-loop guard). The cast in - * the let-init dodges the cstage - * checker's "*vt not assignable to - * vs" through double aliases (see - * SIBLING below). - * 4. pointee_alias_chain — `type vti = vt; type vti2 = vti; - * type vs = *vti2;`. Receiver itself - * is single-aliased; the chain - * depth lives inside the pointee. - * DREW'S INNER-PEEL VERIFY GATE: - * proves structlookupchain's - * aliaslookup loop is sufficient - * without an explicit inner peel. - * If this row reds, the structural - * no-inner-peel claim above is - * wrong and the fix needs an - * inner-side peel too. - * - * Per-row gates: cstage runtime exit, wwstage runtime exit, - * cs.s == ww.s byte-identical (rule-10 stage symmetry). - * - * SIBLING BUGS surfaced during impl — NOT fixed here, filed for - * separate single-class commits (rule 11 split, rule 7 no - * workarounds, task spec "If you find sibling bugs in cgdot: file - * inline"): - * - * (A) cgassign N_DOT TK_ASSIGN on aliased-ptr receiver SILENTLY - * DROPS THE STORE. `s.field = 7` where s: vs = *vt emits - * nothing — wwstage MOVs the rhs into AX and discards. - * Cstage emits the correct `MOVQ off(BP), BX; MOVL AX, (BX)`. - * Same pre-peel pattern as cgdot, at cgenexpr.ww:5160-5168 - * (the `if (lkind == nkind.N_TPTR)` arm of the cgassign - * base-IDENT branch). Out of scope for #191 (read-side - * cgdot only); siblings to file as a follow-up class along - * with cgassign compound (+=, -=, etc.) — same shape, same - * drop. Repro: - * type vt = struct { field: i32 }; - * type vs = *vt; - * fn setit(s: vs) void = { s.field = 7; }; - * - * (B) Chained N_DOT spine (`p.i.x` where p: vs = *outer, outer - * has field i: inner, inner has field x) on aliased-ptr - * receiver also link-fails. The chained spine has dotlhs == - * N_DOT (not N_IDENT), so #191's branch doesn't fire; the - * chained-spine codepath has its own peel gap. Repro: - * type inner = struct { x: i32 }; - * type outer = struct { i: inner }; - * type vs = *outer; - * fn callit(p: vs) i32 = { return p.i.x; }; - * - * (C) Cstage checker rejects single-deep-alias assignability: - * `let s: vs = &v;` where vs = pvt = *vt errors with - * `init *vt not assignable to declared vs`. Workaround in - * row 3: `(&v): vs` explicit cast. The checker walks single - * aliases but not chains of depth >= 2. Not a wwstage cgen - * bug; cited as the reason row 3's let-init carries an - * explicit cast. - * - * GATE POLARITY: must stay GREEN. A red here means the cgdot - * outer-peel regressed (row 1/2), the loop-peel depth handling - * regressed (row 3), or the inner-chain structlookupchain path - * drifted (row 4). - */ -#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 expected_exit; - int stage_mask; -}; - -static const struct row rows[] = { - { "fn_param_read", - "package main;\n" - "type vt = struct { field: i32 };\n" - "type vs = *vt;\n" - "fn callit(s: vs) i32 = { return s.field; };\n" - "export fn main() i32 = {\n" - " let v: vt; v.field = 42;\n" - " return callit(&v);\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "let_binding_read", - "package main;\n" - "type vt = struct { field: i32 };\n" - "type vs = *vt;\n" - "export fn main() i32 = {\n" - " let v: vt; v.field = 42;\n" - " let s: vs = (&v): vs;\n" - " return s.field;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "double_alias_read", - "package main;\n" - "type vt = struct { field: i32 };\n" - "type pvt = *vt;\n" - "type vs = pvt;\n" - "export fn main() i32 = {\n" - " let v: vt; v.field = 42;\n" - " let s: vs = (&v): vs;\n" - " return s.field;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "pointee_alias_chain", - "package main;\n" - "type vt = struct { field: i32 };\n" - "type vti = vt;\n" - "type vti2 = vti;\n" - "type vs = *vti2;\n" - "fn callit(s: vs) i32 = { return s.field; };\n" - "export fn main() i32 = {\n" - " let v: vti2; v.field = 42;\n" - " return callit(&v);\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, -}; - -static int -write_source(const char *src_path, const char *src) -{ - FILE *f = fopen(src_path, "wb"); - if (!f) return -1; - fputs(src, f); - fclose(f); - return 0; -} - -static void -cleanup_tmp(const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", 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); - /* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - char cmd[1024]; - /* #93 sep layout: emit asm to .sepwork/__root.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[256], base[64], outbin[512]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/dap_%d_d_%d", getpid(), seq); - snprintf(src, sizeof src, "%s/main769.ww", tmpdir); - snprintf(base, sizeof base, "main769"); - mkdir(tmpdir, 0755); - if (write_source(src, r->src) != 0) { - cleanup_tmp(tmpdir, base); - return -1; - } - int rc = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } - cleanup_tmp(tmpdir, base); - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/dap_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/dap_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main769"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/main769.ww", tdc); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/main769.ww", tdw); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, base); - 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 cdrv[1024], wdrv[1024]; - 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "dot_aliased_ptr[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "dot_aliased_ptr[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "dot_aliased_ptr[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - - if (!wwpresent) - fprintf(stderr, "dot_aliased_ptr: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "dot_aliased_ptr: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("dot_aliased_ptr: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/770_return_tagged_forward.c b/test/wcc/770_return_tagged_forward.c deleted file mode 100644 index 3653aa79..00000000 --- a/test/wcc/770_return_tagged_forward.c +++ /dev/null @@ -1,383 +0,0 @@ -/* - * 770_return_tagged_forward — project #201: wwstage cgreturn forwarding - * of a matching tagged-union call result. cstage cgen.c:8007-8014 - * detects passthrough via Type-based `istagged && (vu == rt || - * type_eq(vt, cg_ret_type))`, so any callee shape (IDENT, DOT, deref- - * call) that returns the same tagged type triggers a direct AX/DX/CX/R8 - * forward. Wwstage's cgreturn was instead detecting forwardtagged by - * walking the CALLEE NAME — only N_IDENT and N_DOT callees were - * inspected; an N_UN(TK_STAR) deref-call fell through to the variant- - * tag synthesis path, which clobbered AX→DX and zeroed CX/R8/AX before - * RET, wiping the just-returned tagged-ABI words. - * - * Trigger (impl-e1-resume STOP / 994_w6c_ww byte-id red): - * fn st_read(s: vstream, ...) (size | io.eof | io.error) = - * { return (*r)(s, buf); }; - * - * Fix: selfhost/cmd/wcc/cgenstmt.ww cgreturn — replace the IDENT/DOT - * name-keyed lookup with a TYPE-BASED predicate on the checker-stamped - * tinfo: peel TY_NAMED from both rhs.type_ and c.fnret.type_, and - * forward when the peeled rhs tinfo is TY_TAGGED and pointer-identical - * to the peeled fnret tinfo. Identity is sufficient because - * tinfofornode memoizes per typedecl (check.ww:1566-1568 "every TNAME - * resolving to the same decl yields the SAME tinfo pointer"); the - * structural fallback in cstage's type_eq is gated by #178 (no tinfo- - * level structural-eq helper today, and typeeqast's TY_TAGGED arm - * conservatively returns false). - * - * Coverage (6 rows): - * 1. forward_named_match — IDENT callee, both fns return the same - * NAMED tagged alias. Pre-fix: forwarded - * via the old name-keyed path. Post-fix: - * same forward via the new TYPE path. - * Asm regression gate. - * 2. widen_subset — IDENT callee returns a concrete variant - * (i32), fnret is the tagged union. Must - * KEEP the widen-shuffle (AX→DX, zero - * CX/R8/AX). Asserts the new predicate - * correctly rejects non-matching shapes. - * 3. deref_call_match — `(*r)()` callee, both fns return the - * same NAMED tagged alias. THE BUG row. - * Pre-fix wwstage: spurious tag-synth - * shuffle after CALL AX. Post-fix: - * direct CALL AX + RET. - * 4. scalar_return — fnret is i32 (not tagged); cgreturn - * skips the istaggedtype gate entirely. - * Sanity that the fix doesn't perturb - * the unrelated scalar path. - * 5. nested_call_forward — outer call wraps inner scalar call, - * both fns return the same NAMED tagged - * alias. Forward fires on the outer - * N_CALL despite the nested arg. - * 6. cross_module_forward — caller is in `main`, callee+type live - * in an imported module. drew-add: gates - * cross-module TY_NAMED identity (every - * reference to `mtag770.result` resolves - * to the SAME NAMED tinfo via aliassym + - * sym.type_ cache; #191 lineage). If a - * regressor splits NAMED tinfo per - * reference site, this row reds first. - * - * Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s == - * ww.s byte-identical (rule-10 stage symmetry). - * - * GATE POLARITY: must stay GREEN. A red on row 3/5/6 means the - * TYPE-based forward predicate dropped a callee shape; row 2 reds if - * the predicate fires too aggressively (non-matching rhs incorrectly - * forwarded); row 1/4 red means structural regression in adjacent - * cgreturn arms. - */ -#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; - /* Optional secondary-module source (row 6 cross_module). */ - const char *modname; - const char *modsrc; - int expected_exit; - int stage_mask; -}; - -static const struct row rows[] = { - { "forward_named_match", - "package main;\n" - "type result = (i32 | str);\n" - "fn ra(x: i32) result = { return x + 1: i32; };\n" - "fn cw(x: i32) result = { return ra(x); };\n" - "export fn main() i32 = {\n" - " let r: result = cw(40);\n" - " match (r) {\n" - " case let i: i32 => return i;\n" - " case let s: str => return 0;\n" - " };\n" - "};\n", - NULL, NULL, - 41, - STAGE_CS | STAGE_WW }, - { "widen_subset", - "package main;\n" - "type result = (i32 | str);\n" - "fn ri(x: i32) i32 = { return x + 1; };\n" - "fn cw(x: i32) result = { return ri(x); };\n" - "export fn main() i32 = {\n" - " let r: result = cw(40);\n" - " match (r) {\n" - " case let i: i32 => return i;\n" - " case let s: str => return 0;\n" - " };\n" - "};\n", - NULL, NULL, - 41, - STAGE_CS | STAGE_WW }, - { "deref_call_match", - "package main;\n" - "type result = (i32 | str);\n" - "fn ra(x: i32) result = { return x + 1: i32; };\n" - "fn cw(r: *fn(x: i32) result, x: i32) result = { return (*r)(x); };\n" - "export fn main() i32 = {\n" - " let p: *fn(x: i32) result = &ra;\n" - " let v: result = cw(p, 40);\n" - " match (v) {\n" - " case let i: i32 => return i;\n" - " case let s: str => return 0;\n" - " };\n" - "};\n", - NULL, NULL, - 41, - STAGE_CS | STAGE_WW }, - { "scalar_return", - "package main;\n" - "fn ri(x: i32) i32 = { return x + 1; };\n" - "fn cw(x: i32) i32 = { return ri(x); };\n" - "export fn main() i32 = { return cw(40); };\n", - NULL, NULL, - 41, - STAGE_CS | STAGE_WW }, - { "nested_call_forward", - "package main;\n" - "type result = (i32 | str);\n" - "fn ri(x: i32) i32 = { return x + 1; };\n" - "fn wrap(x: i32) result = { return x: i32; };\n" - "fn cw(x: i32) result = { return wrap(ri(x)); };\n" - "export fn main() i32 = {\n" - " let r: result = cw(39);\n" - " match (r) {\n" - " case let i: i32 => return i;\n" - " case let s: str => return 0;\n" - " };\n" - "};\n", - NULL, NULL, - 40, - STAGE_CS | STAGE_WW }, - { "cross_module_forward", - "package main;\n" - "import mtag770;\n" - "fn cw(x: i32) mtag770.result = { return mtag770.ra(x); };\n" - "export fn main() i32 = {\n" - " let r: mtag770.result = cw(40);\n" - " match (r) {\n" - " case let i: i32 => return i;\n" - " case let s: str => return 0;\n" - " };\n" - "};\n", - "mtag770", - "package mtag770;\n" - "export type result = (i32 | str);\n" - "export fn ra(x: i32) result = { return x + 1: i32; };\n", - 41, - STAGE_CS | STAGE_WW }, -}; - -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; -} - -static int -write_sources(const struct row *r, const char *tmpdir, const char *src) -{ - if (r->modname != NULL) { - char moddir[256], modfile[512]; - snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname); - snprintf(modfile, sizeof modfile, "%s/%s.ww", - moddir, r->modname); - mkdir(moddir, 0755); - if (write_source(modfile, r->modsrc) != 0) return -1; - } - return write_source(src, r->src); -} - -static void -cleanup_tmp(const struct row *r, const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); - /* #93: the sep scratch + tmpdir-pinned pkgcache + concat scratch. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc %s/all_cs.s %s/all_ww.s", - tmpdir, base, tmpdir, tmpdir, tmpdir); if (system(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); - if (r->modname != NULL) { - char moddir[256]; - snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname); - snprintf(p, sizeof p, "%s/%s.ww", moddir, r->modname); unlink(p); - snprintf(p, sizeof p, "%s/%s.s", moddir, r->modname); unlink(p); - snprintf(p, sizeof p, "%s/%s.o", moddir, r->modname); unlink(p); - snprintf(p, sizeof p, "%s/%s.combined.ww", moddir, r->modname); unlink(p); - rmdir(moddir); - } - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - /* #93 sep layout: emit asm to .sepwork/.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - char cmd[1280]; - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[512], base[64], outbin[768]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/rtf_%d_d_%d", getpid(), seq); - snprintf(src, sizeof src, "%s/main770.ww", tmpdir); - snprintf(base, sizeof base, "main770"); - mkdir(tmpdir, 0755); - if (write_sources(r, tmpdir, src) != 0) { - cleanup_tmp(r, tmpdir, base); - return -1; - } - int rc = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } - cleanup_tmp(r, tmpdir, base); - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/rtf_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/rtf_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main770"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/main770.ww", tdc); - if (write_sources(r, tdc, src) != 0) { cleanup_tmp(r, tdc, base); cleanup_tmp(r, tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - /* #93: root + each imported pkg now compile to separate - * .sepwork/.s; concat (sorted glob, identical set both - * stages) for the full-asm byte-id compare. */ - snprintf(cs, sizeof cs, "%s/all_cs.s", tdc); - { char cc[1280]; snprintf(cc, sizeof cc, - "cat %s/%s.sepwork/*.s > %s 2>/dev/null", tdc, base, cs); - if (system(cc)) {} } - - snprintf(src, sizeof src, "%s/main770.ww", tdw); - if (write_sources(r, tdw, src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/all_ww.s", tdw); - { char cc[1280]; snprintf(cc, sizeof cc, - "cat %s/%s.sepwork/*.s > %s 2>/dev/null", tdw, base, ws); - if (system(cc)) {} } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(r, tdc, base); - cleanup_tmp(r, tdw, base); - return rc; -} - -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 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "return_tagged_forward[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "return_tagged_forward[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "return_tagged_forward[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - - if (!wwpresent) - fprintf(stderr, "return_tagged_forward: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "return_tagged_forward: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("return_tagged_forward: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/772_typeassert_nonident.c b/test/wcc/772_typeassert_nonident.c deleted file mode 100644 index eb976390..00000000 --- a/test/wcc/772_typeassert_nonident.c +++ /dev/null @@ -1,338 +0,0 @@ -/* - * 772_typeassert_nonident — project #200: wwstage cgtypeassert non-IDENT - * scrutinee bug. The pre-fix `e as T` on a non-N_IDENT scrutinee (direct - * call result, arr[i], p.field, ?, paren-wrap of any of those) left - * scrutoff=0; the tag-load fell on (BP) — the saved-BP word — and the - * payload-load on +8(BP) — the return address — instead of the spilled - * tagged-return ABI words. wwstage produced garbage (exit 220 in the - * repro vs cstage's 42). - * - * Fix (selfhost/cmd/wcc/cgenexpr.ww:368-414 cgtypeassert): mirror cstage - * cmd/w6c/cgen.c:6300-6316 N_TYPEASSERT non-IDENT arm. Add an `else` - * branch after the existing N_IDENT path: resolve the tagged type via - * matchscrutt, alloc an `@asrt_spill` slot via matchspillsz/localalloc, - * cgexpr the LHS, then spill AX→+0 (tag), DX→+8 (word0), CX→+16 - * (word1) guarded on spill > 16. Mirrors cgmatch's spill (cgenexpr.ww: - * 1422-1460). The R8→+24 path (cgmatch shape) is intentionally omitted - * to mirror cstage's cgtypeassert exactly (rule-10 byte-id); the cstage - * twin gap is filed inline as a sibling task. - * - * Coverage (7 rows): - * 1. call_as_size — `f() as size`. The repro shape: direct call - * result, basic-size payload. Pre-fix - * wwstage: garbage AX from (BP). Post-fix: - * 42. - * 2. call_as_str — `f() as str`. Non-IDENT, multi-word payload - * (AX=ptr / BX=len). Tests CX→+16 spill + - * the `isstrtype` post-spill BX load. - * 3. call_as_namedvoid — `f() as nvariant` where nvariant is a - * named-void alias. Tag-check fires; the - * payload load is a 0-byte no-op (void - * variant has size 0; no consumer reads AX). - * Drew-add. - * 4. call_as_fnptr — `f() as *fn(i32) i32`. Fn-ptr variant - * (8B word0 = pointer). Round-trips through - * spill→tag-check→MOVQ +8 → AX. - * 5. call_as_u8 — `f() as u8`. Narrow scalar. The callee - * zero/sign-extended the value to 8B on - * return; MOVQ store + MOVQ read round-trips - * it. Ken-add: confirms no narrow-truncation - * on the payload load. - * 6. call_as_i16 — `f() as i16`. Narrow signed scalar. Tests - * negative round-trip through the spill slot - * (callee sign-extended to 8B; MOVQ - * preserves). Ken-add sibling of row 5. - * 7. branched_call_as — callee returns a runtime-chosen variant - * (the success branch); `f(true) as size`. - * Tests that the spill+tag-check path - * doesn't lose the callee's selected tag. - * - * Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s == - * ww.s byte-identical (rule-10 stage symmetry). - * - * GATE POLARITY: must stay GREEN. A red on rows 1-4 or 7 means the - * spill path is broken (non-IDENT scrutinee returned to the BP/RIP - * read). Rows 5/6 red means narrow-scalar round-trip miscompiled at - * the payload load (would surface if a future SIB regresses MOVQ to a - * narrow load that drops the callee's extension). - */ -#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 expected_exit; - int stage_mask; -}; - -static const struct row rows[] = { - { "call_as_size", - "package main;\n" - "fn f(b: bool) (size | str) = {\n" - " if (b) { return 42: size; };\n" - " return \"x\";\n" - "};\n" - "export fn main() i32 = {\n" - " let n = f(true) as size;\n" - " return n: i32;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "call_as_str", - "package main;\n" - "fn f(b: bool) (size | str) = {\n" - " if (b) { return \"AB\"; };\n" - " return 0: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let s = f(true) as str;\n" - " return s.len: i32 + 40;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "call_as_namedvoid", - "package main;\n" - "type nv = void;\n" - "fn f(b: bool) (size | nv) = {\n" - " if (b) { return void: nv; };\n" - " return 0: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let _ = f(true) as nv;\n" - " return 42;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "call_as_fnptr", - /* #193: explicit deref required to call a fn-ptr (`(*p)(args)`). - * The probe is wwstage cgtypeassert non-IDENT spill; deref-call - * shape is orthogonal. */ - "package main;\n" - "fn g(x: i32) i32 = { return x + 1; };\n" - "fn f(b: bool) (*fn(x: i32) i32 | size) = {\n" - " if (b) { return &g; };\n" - " return 0: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let p = f(true) as *fn(x: i32) i32;\n" - " return (*p)(41);\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "call_as_u8", - "package main;\n" - "fn f(b: bool) (u8 | str) = {\n" - " if (b) { return 42: u8; };\n" - " return \"x\";\n" - "};\n" - "export fn main() i32 = {\n" - " let n = f(true) as u8;\n" - " return n: i32;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "call_as_i16", - "package main;\n" - "fn f(b: bool) (i16 | str) = {\n" - " if (b) { return -1: i16; };\n" - " return \"x\";\n" - "};\n" - "export fn main() i32 = {\n" - " let n = f(true) as i16;\n" - " if (n: i32 == -1) { return 42; };\n" - " return 1;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, - { "branched_call_as", - "package main;\n" - "fn f(b: bool) (size | str) = {\n" - " if (b) { return 42: size; };\n" - " return \"x\";\n" - "};\n" - "export fn main() i32 = {\n" - " let pick: bool = true;\n" - " let n = f(pick) as size;\n" - " return n: i32;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW }, -}; - -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; -} - -static void -cleanup_tmp(const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", 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); - /* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - char cmd[1024]; - /* #93 sep layout: emit asm to .sepwork/__root.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[512], base[64], outbin[768]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/tan_%d_d_%d", getpid(), seq); - snprintf(base, sizeof base, "main772"); - 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 = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } - cleanup_tmp(tmpdir, base); - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/tan_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/tan_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main772"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/%s.ww", tdc, base); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/%s.ww", tdw, base); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, base); - return rc; -} - -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 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "typeassert_nonident[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "typeassert_nonident[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "typeassert_nonident[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - - if (!wwpresent) - fprintf(stderr, "typeassert_nonident: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "typeassert_nonident: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("typeassert_nonident: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/773_isas_spread_variant.c b/test/wcc/773_isas_spread_variant.c deleted file mode 100644 index f4aa7818..00000000 --- a/test/wcc/773_isas_spread_variant.c +++ /dev/null @@ -1,319 +0,0 @@ -/* - * 773_isas_spread_variant — project #198: wwstage checker `is`/`as` - * variant lookup walked the unflattened AST u.list (casevariantin via - * typeeqast streq), so any variant introduced via a `...inner` spread - * was invisible to the checker and rejected as "is/as: not a variant - * of operand". Repro: `r: (size | io.eof | ...io.error); r is - * io.underread;` — io.underread is in io.error's params chain, which - * gets spliced into the parent union at tinfofornode L1827-1836, but - * the AST u.list still holds the single `...io.error` entry that - * streq("io.underread", "io.error") rejects. - * - * Fix: selfhost/cmd/wcc/check.ww checkisas (lines ~3441-3466). Route - * through tinfo.params via flatvariantidxt (the same Phase-N helper - * #179 cgmatch and #66 cgtagvariantidx use); fall back to casevariantin - * AST walk when tinfo isn't available (defensive — non-#198 path stays - * as-is). Mirrors cstage cmd/wcc/check.c:1662-1675 u->params + type_eq. - * Closes the cgen-drain mini-cluster (#201 → #199 → #200 → #198). - * - * Coverage (5 rows): - * 1. spread_is_inline_variant — `(size | io.eof | ...io.error)`, - * `r is io.underread`. THE BUG: pre- - * fix wwstage rejects with "is/as: - * not a variant of operand - * (io.underread)". Post-fix accepts; - * cgen tag-compare hits the flatten- - * spread index. No byte-id: layout- - * asymmetry on `...wrapper` (cstage - * flattens at resolve_type, wwstage - * computes maxsz off vt.size of the - * un-spliced alias — sibling task, - * not blocking checker correctness). - * 2. direct_cross_mod_tagged — `(size | io.eof | io.error)`, - * `r is io.error`. Direct cross-mod - * tagged variant (no spread). Regr- - * ession gate: AST-streq path always - * matched this; the new tinfo path - * must too. byte-id ON. - * 3. cross_mod_named_void — `(size | io.eof)`, `r is io.eof`. - * Cross-mod NAMED-void variant. Regr- - * ession gate for the io.eof shape - * used pervasively in the io fold. - * byte-id ON. - * 4. same_module_variant — `(i32 | str)`, `r is i32`. Bare - * same-module primitive variant. - * Lowest-bar regression gate: no - * cross-mod, no spread, no NAMED. - * byte-id ON. - * 5. spread_as_inline_payload — `(size | io.eof | ...io.error)`, - * `r as io.underread`. `as` twin of - * row 1: same checker path - * (checkisas dispatches on both - * N_TYPETEST and N_TYPEASSERT), with - * the payload-extract runtime gate. - * No byte-id (sibling, same as row 1). - * - * Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s == - * ww.s byte-identical when byte_id != 0. - * - * GATE POLARITY: must stay GREEN. Rows 1/5 red means the tinfo.params - * routing dropped a spread-flattened variant; row 2 red means the - * direct cross-mod path broke; row 3 red means the NAMED-void path - * broke; row 4 red means a same-module / same-package regression. - */ -#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 expected_exit; - int stage_mask; - int byte_id; -}; - -static const struct row rows[] = { - { "spread_is_inline_variant", - "package main;\n" - "import io;\n" - "type rsh = (size | io.eof | ...io.error);\n" - "export fn main() i32 = {\n" - " let r: rsh = 42: size;\n" - " if (r is io.underread) { return 1; };\n" - " if (r is io.eof) { return 2; };\n" - " if (r is size) { return 3; };\n" - " return 0;\n" - "};\n", - 3, - STAGE_CS | STAGE_WW, 0 }, - { "direct_cross_mod_tagged", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let r: (size | io.eof | io.error) = 42: size;\n" - " if (r is io.error) { return 1; };\n" - " if (r is io.eof) { return 2; };\n" - " if (r is size) { return 3; };\n" - " return 0;\n" - "};\n", - 3, - STAGE_CS | STAGE_WW, 1 }, - { "cross_mod_named_void", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let r: (size | io.eof) = 42: size;\n" - " if (r is io.eof) { return 1; };\n" - " if (r is size) { return 3; };\n" - " return 0;\n" - "};\n", - 3, - STAGE_CS | STAGE_WW, 1 }, - { "same_module_variant", - "package main;\n" - "export fn main() i32 = {\n" - " let r: (i32 | str) = 3: i32;\n" - " if (r is i32) { return 3; };\n" - " if (r is str) { return 1; };\n" - " return 0;\n" - "};\n", - 3, - STAGE_CS | STAGE_WW, 1 }, - { "spread_as_inline_payload", - "package main;\n" - "import io;\n" - "type rsh = (size | io.eof | ...io.error);\n" - "fn pick(b: i32) rsh = {\n" - " if (b == 1) { let u: io.underread = 7: i32: io.underread; return u; };\n" - " return 42: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let r = pick(1);\n" - " let u = r as io.underread;\n" - " return u: i32;\n" - "};\n", - 7, - STAGE_CS | STAGE_WW, 0 }, -}; - -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; -} - -static void -cleanup_tmp(const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", 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); - /* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - char cmd[1024]; - /* #93 sep layout: emit asm to .sepwork/__root.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[512], base[64], outbin[768]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/isas_%d_d_%d", getpid(), seq); - snprintf(src, sizeof src, "%s/main773.ww", tmpdir); - snprintf(base, sizeof base, "main773"); - mkdir(tmpdir, 0755); - if (write_source(src, r->src) != 0) { - cleanup_tmp(tmpdir, base); - return -1; - } - int rc = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } - cleanup_tmp(tmpdir, base); - return rc; -} - -/* Parallel tmpdirs per CLAUDE.md rule 14 — ww_ww writes intermediates - * next to the source so concurrent cstage/wwstage builds against the - * same path would race. */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/isas_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/isas_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main773"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/main773.ww", tdc); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/main773.ww", tdw); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, base); - return rc; -} - -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 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "isas_spread_variant[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "isas_spread_variant[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "isas_spread_variant[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "isas_spread_variant: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "isas_spread_variant: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("isas_spread_variant: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/774_tagged_widen_named_variant.c b/test/wcc/774_tagged_widen_named_variant.c deleted file mode 100644 index adf19ce8..00000000 --- a/test/wcc/774_tagged_widen_named_variant.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * 774_tagged_widen_named_variant — project #205: cstage type_assignable + - * wwstage isassignable tagged→tagged arm misses NAMED-variant nominal - * match. Before the fix, `let e: wrapper = u; let r: (size|eof|wrapper) = e;` - * REJECTED at cstage's checker (wwstage accepted permissively but cgen - * silently miscompiled — separate fold via #199b). The subset loop - * walked wrapper's leaves (unsupported, underread, nomem) against dst - * (size, eof, wrapper) and found none → return 0. - * - * Fix (cmd/wcc/type.c:327 + selfhost/cmd/wcc/check.ww:3087): insert a - * NAMED-variant nominal compare BEFORE the subset loop in the - * tagged→tagged arm. When src is a NAMED-tagged wrapper and dst has a - * direct NAMED-tagged variant equal to src, accept by nominal identity. - * Mirrors the concrete→tagged arm at type.c:316 (#199 α). SSoT with - * `is`/`as` non-recursive variant lookup (#198 family). Wwstage mirrors - * the structural insertion; the existing permissive `*confident = false; - * return true;` tail is preserved (deferred-tightening per #202). - * - * Cgen's tag-remap for the wrapper-as-whole case still maps src - * variants to dst tag 0 (cg_widen_tag_remap walks per-variant); the - * wrapped-slot layout for `dst.tag = variant_idx, dst.payload = src` - * is the deferred #199b future-work. Probes verify checker-accept + - * runtime exit-clean only; they do NOT inspect the resulting variant - * tag of the widen target. - * - * Coverage (5 rows): - * 1. accept_wrapper_variant — BUG-REPRO. `let e: wrapper = u; - * return e;` where return type contains wrapper as direct NAMED - * variant. MUST now ACCEPT + run. - * 2. accept_nested_wrapper — two layers of NAMED-tagged wrap: - * `inner -> outer -> (size|outer)`. Each widen exercises the - * tagged→tagged NAMED nominal compare. - * 3. accept_pure_leaf_subset — REGRESSION GATE. Tagged→tagged - * where every src variant is directly in dst (no wrapper). The - * classic subset path — must keep working. - * 4. reject_concrete_unrelated — REJECTION GATE. Concrete `a` into - * `(b|c)` where a is NOT in dst. Verifies the fix didn't widen - * the concrete→tagged arm's discipline (#199 α stays intact). - * 5. branched_callee_widen — fn returns (size|eof|wrapper) - * from one of two paths (the wrapper-widen and a flat scalar - * variant). Verifies branched runtime, not just constant-fold. - * - * Per-row gates: cstage compile + runtime, wwstage compile + runtime, - * cs.s == ww.s byte-identical (rule-10). Row 4 uses EXPECT_REJECT to - * indicate "build must fail" — driver invocation returns non-zero. - */ -#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 - -#define EXPECT_REJECT (-2) - -struct row { - const char *label; - const char *src; - int expected_exit; /* EXPECT_REJECT means build must fail */ - int stage_mask; - int byte_id; /* run cs.s == ww.s diff (works rows only) */ -}; - -static const struct row rows[] = { - { "accept_wrapper_variant", - "package main;\n" - "type unsupported = !void;\n" - "type underread = !i32;\n" - "type nomem = !void;\n" - "type wrapper = !(unsupported | underread | nomem);\n" - "type eof = void;\n" - "fn f() (size | eof | wrapper) = {\n" - " let u: unsupported;\n" - " let e: wrapper = u;\n" - " return e;\n" - "};\n" - "export fn main() i32 = { let r = f(); return 41; };\n", - 41, - STAGE_CS | STAGE_WW, 1 }, - { "accept_nested_wrapper", - "package main;\n" - "type ax = !void;\n" - "type inner = !(ax);\n" - "type outer = !(inner);\n" - "fn f() (size | outer) = {\n" - " let a: ax;\n" - " let i: inner = a;\n" - " let o: outer = i;\n" - " return o;\n" - "};\n" - "export fn main() i32 = { let r = f(); return 42; };\n", - 42, - STAGE_CS | STAGE_WW, 1 }, - /* Row 3 dst uses an all-!void variant set (8B slot, tag-only) to - * keep ssz == slot_sz at cgwidentaggedstorebp. A wider dst - * (e.g. `(ax|bx|size)`) hits a pre-existing wwstage cgen gap: the - * call-ABI branch unconditionally stores DX/CX/R8 at write_off+8 - * when slot_sz > 8, ignoring src ssz, while cstage zero-pads when - * ssz < sz (cmd/w6c/cgen.c:1788-1795 vs selfhost/cmd/wcc/cgenutil.ww - * cgwidentaggedstorebp:2680-2700). Sibling to #202 (wwstage - * cgen-side asymmetry); deferred per #199b layout family. */ - { "accept_pure_leaf_subset", - "package main;\n" - "type ax = !void;\n" - "type bx = !void;\n" - "type cx = !void;\n" - "fn small() (ax | bx) = { let u: ax; return u; };\n" - "export fn main() i32 = {\n" - " let r: (ax | bx | cx) = small();\n" - " return 43;\n" - "};\n", - 43, - STAGE_CS | STAGE_WW, 1 }, - /* Row 4 uses payload-bearing aliases so wwstage's primitive-mismatch - * arm fires confidently — three !void aliases all collapse to "void" - * via unwrapbang + resolvealias and wwstage falls through to its - * permissive tail (#202 family, deferred). The payload axis (i32 - * vs i64 vs f64) splits the underlying primitives so both stages - * reject under the concrete→tagged arm. */ - { "reject_concrete_unrelated", - "package main;\n" - "type ax = !i32;\n" - "type bx = !i64;\n" - "type cx = !f64;\n" - "export fn main() i32 = {\n" - " let u: ax;\n" - " let r: (bx | cx) = u;\n" - " return 0;\n" - "};\n", - EXPECT_REJECT, - STAGE_CS | STAGE_WW, 0 }, - { "branched_callee_widen", - "package main;\n" - "type unsupported = !void;\n" - "type wrapper = !(unsupported);\n" - "type eof = void;\n" - "fn pick(b: bool) (size | eof | wrapper) = {\n" - " if (b) {\n" - " let u: unsupported;\n" - " let e: wrapper = u;\n" - " return e;\n" - " };\n" - " return 50: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let r1 = pick(true);\n" - " let r2 = pick(false);\n" - " return 45;\n" - "};\n", - 45, - 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; -} - -static void -cleanup_tmp(const char *tmpdir, const char *base) -{ - char p[512]; - snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - 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 *src) -{ - char cmd[1280]; - /* #93 sep layout: emit asm to .sepwork/__root.s; pin - * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver); - return runwait(cmd); -} - -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char tmpdir[256], src[512], base[64], outbin[768]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wt_%d_d_%d", getpid(), seq); - snprintf(src, sizeof src, "%s/main774.ww", tmpdir); - snprintf(base, sizeof base, "main774"); - 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, src); - if (r->expected_exit == EXPECT_REJECT) { - rc = (br != 0) ? r->expected_exit : 0; - } else if (br == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - rc = runwait(outbin); - } else { - rc = -1; - } - cleanup_tmp(tmpdir, base); - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/wt_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/wt_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main774"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/main774.ww", tdc); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/main774.ww", tdw); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, base); - return rc; -} - -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 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, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "tagged_widen_named_variant[cstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - int got = run_row(wdrv, &rows[i], seq++); - if (got != rows[i].expected_exit) { - fprintf(stderr, - "tagged_widen_named_variant[wwstage run][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].expected_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "tagged_widen_named_variant[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "tagged_widen_named_variant: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "tagged_widen_named_variant: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("tagged_widen_named_variant: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/775_io_vtable_run.c b/test/wcc/775_io_vtable_run.c deleted file mode 100644 index 4d34e89e..00000000 --- a/test/wcc/775_io_vtable_run.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * 775_io_vtable_run — project #94 fold-eFinal sentinel. Pins the - * single-surface lib/io/stream.ww vtable port: `vtable` (the three - * (*T|void) slots), `stream` (= *vtable), and the read/write/close - * dispatchers (ref/hare/io/stream.ha:33-68). The fold-eFinal FLIP - * retired the pre-vtable `stream` struct + `closed` tag, leaving this - * as the sole io surface. (Comment prose below predates the FLIP and - * still says `vstream`/`st_read`/`st_write`/`st_close` for the same - * `stream`/`read`/`write`/`close`.) - * - * Each row imports io, allocates a vtable, optionally points one slot at - * a user fn (via the `(&fn): *io.` cast — see SIBLINGS below), - * calls the matching dispatcher, and checks the exit constant. Both - * stages run; cs.s == ww.s byte-id on the rows that build (rule-10). - * - * row | what it pins - * --------------------------+-------------------------------------- - * reader_set_happy | reader slot set + st_read happy path. - * | Callee returns (7: size); probe asserts - * | `r is size && r as size == 7`. Pins - * | the call-arm `case let r: *reader => - * | return (*r)(s, buf)` end-to-end. - * reader_void_unsupported | reader=void. st_read takes the void-arm - * | (two direct widens, #199 α + #205) and - * | returns the errors.unsupported variant. - * | Probe verifies the call returns + exit - * | constant only — does NOT inspect r's - * | discriminant tag (deferred #199b: the - * | wrapped-slot tag-remap currently lands - * | the value at dst tag 0, not the `error` - * | variant index; see 774 header for the - * | parallel constraint). - * writer_set_happy | symmetric to reader_set_happy on - * | st_write; callee returns (buf.len:size). - * writer_void_unsupported | symmetric to reader_void_unsupported. - * closer_void_noop | closer=void. st_close returns plain - * | `void` (no widen — direct `return void` - * | from the dispatcher). Probe asserts - * | `r is void` — the only row that can - * | inspect the tag, because no nested - * | wrapper is involved. - * closer_set | closer slot set + st_close happy path. - * | Callee returns plain `void`; the - * | dispatcher forwards via `(*c)(s)` and - * | the return surfaces at the caller. - * branched_readers | two distinct vtable instances each with - * | a different reader fn. st_read on each - * | dispatches to the right callee (1:size - * | vs 2:size). Mirrors 774's branched- - * | callee row; catches a constant-fold - * | mistake in the dispatch path. - * - * DEFERRALS / RELATED (NOT addressed by these rows): - * - * - #199b layout-extension (wrapped-slot tag-remap): when widening a - * NAMED wrapper-typed value (e.g. `error`) into a tagged parent - * (e.g. `(size | eof | error)`), cgen walks src VARIANTS rather - * than treating src-as-a-whole, so the dst tag lands at 0 - * regardless of which wrapper variant was set. Affects rows 2 & 4 - * (unsupported via void-arm). Probes verify runtime exit-clean, - * NOT the variant tag — matches the 774 header's exact constraint. - * - * - NEW: checker rejects bare `&fn_name` assignment to a - * `(* | void)` field. `vt.reader = &myread;` errors - * with "cannot assign *fn(vstream, []u8) (size | eof | error) to - * (*reader | void)" — the structural `*fn(...)` value is not - * accepted as the `*reader` named variant of the tagged field. - * Both stages reject. Symmetric to #178 (typeeqast layer asymmetry) - * at the pointer-to-fn-alias layer + tagged-variant assignment. - * PROBE ROUTES AROUND via an explicit cast: `(&myread): *io.reader`. - * Once closed, the cast drops out of every Hare-faithful vtable - * initialisation. - * - * - NEW: `let p: *io.reader = &myread;` also errors with "init - * *fn(vstream, []u8) ... not assignable to declared *reader" — - * same root as above but at the let-binding layer. The Hare - * `io::vtable { reader = &my_read, ... }` shape needs both holes - * closed. - * - * GATE POLARITY: must stay GREEN. A red here means the vtable port - * regressed at the checker, the dispatcher cgen miscompiled the - * call-arm, or the field-slot zero-init lost the void tag. - */ -#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[] = { - { "reader_set_happy", - "package main;\n" - "import io;\n" - "fn myread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n" - " return 7: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " vt.reader = (&myread): *io.reader;\n" - " let v: io.stream = &vt;\n" - " let buf: [4]u8;\n" - " let bs: []u8 = buf[0:4];\n" - " let r = io.read(v, bs);\n" - " if (r is size) {\n" - " let n = r as size;\n" - " if (n == 7: size) { return 70; };\n" - " return 71;\n" - " };\n" - " return 99;\n" - "};\n", - 70, - STAGE_CS | STAGE_WW, 1 }, - { "reader_void_unsupported", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " let v: io.stream = &vt;\n" - " let buf: [4]u8;\n" - " let bs: []u8 = buf[0:4];\n" - " let _r = io.read(v, bs);\n" - " return 11;\n" - "};\n", - 11, - STAGE_CS | STAGE_WW, 1 }, - { "writer_set_happy", - "package main;\n" - "import io;\n" - "fn mywrite(s: io.stream, buf: []u8) (size | io.error) = {\n" - " return buf.len: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " vt.writer = (&mywrite): *io.writer;\n" - " let v: io.stream = &vt;\n" - " let buf: [5]u8;\n" - " let bs: []u8 = buf[0:5];\n" - " let r = io.write(v, bs);\n" - " if (r is size) {\n" - " let n = r as size;\n" - " if (n == 5: size) { return 80; };\n" - " return 81;\n" - " };\n" - " return 99;\n" - "};\n", - 80, - STAGE_CS | STAGE_WW, 1 }, - { "writer_void_unsupported", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " let v: io.stream = &vt;\n" - " let buf: [3]u8;\n" - " let bs: []u8 = buf[0:3];\n" - " let _r = io.write(v, bs);\n" - " return 22;\n" - "};\n", - 22, - STAGE_CS | STAGE_WW, 1 }, - { "closer_void_noop", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " let v: io.stream = &vt;\n" - " let r = io.close(v);\n" - " if (r is void) { return 50; };\n" - " return 99;\n" - "};\n", - 50, - STAGE_CS | STAGE_WW, 1 }, - { "closer_set", - "package main;\n" - "import io;\n" - "fn myclose(s: io.stream) (void | io.error) = {\n" - " return void;\n" - "};\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " vt.closer = (&myclose): *io.closer;\n" - " let v: io.stream = &vt;\n" - " let r = io.close(v);\n" - " if (r is void) { return 60; };\n" - " return 99;\n" - "};\n", - 60, - STAGE_CS | STAGE_WW, 1 }, - { "branched_readers", - "package main;\n" - "import io;\n" - "fn r1(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n" - " return 1: size;\n" - "};\n" - "fn r2(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n" - " return 2: size;\n" - "};\n" - "export fn main() i32 = {\n" - " let vt1: io.vtable;\n" - " let vt2: io.vtable;\n" - " vt1.reader = (&r1): *io.reader;\n" - " vt2.reader = (&r2): *io.reader;\n" - " let buf: [4]u8;\n" - " let bs: []u8 = buf[0:4];\n" - " let a = io.read(&vt1, bs);\n" - " let b = io.read(&vt2, bs);\n" - " let asz: size = 0;\n" - " let bsz: size = 0;\n" - " if (a is size) { asz = a as size; };\n" - " if (b is size) { bsz = b as size; };\n" - " if (asz == 1: size && bsz == 2: size) { return 95; };\n" - " return 99;\n" - "};\n", - 95, - 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. Sweep them all then rmdir. */ -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", tmpdir, base); unlink(p); - /* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *cwd, - const char *src) -{ - char cmd[2048]; - /* #93 sep layout: `--sep -o ` emits the asm to - * .sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the - * shared out/.pkgcache is untouched and the cache dies with the - * tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver, cwd); - 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/iov_%d_d_%d", getpid(), seq); - snprintf(base, sizeof base, "main775"); - 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; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/iov_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/iov_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main775"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/%s.ww", tdc, base); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/%s.ww", tdw, base); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, 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, - "io_vtable_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, - "io_vtable_run[ww][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) { - fprintf(stderr, - "io_vtable_run[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "io_vtable_run: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "io_vtable_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("io_vtable_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/776_memio_vstream_run.c b/test/wcc/776_memio_vstream_run.c deleted file mode 100644 index 3a1d363e..00000000 --- a/test/wcc/776_memio_vstream_run.c +++ /dev/null @@ -1,431 +0,0 @@ -/* - * 776_memio_vstream_run — project #94 fold-eFinal sentinel. Pins - * the single-surface lib/memio: a unified - * `stream` struct with `vt: io.vtable` as the first field for the - * intrusive cast, three constructors (fixed / dynamic / - * dynamicfrom) that return the `stream` BY VALUE (sret — - * field-by-field build then `return r;`, ref/hare/memio/stream.ha:46, - * 58,64), the unified read / fixedwrite / dynamicwrite / - * dynamicclose callbacks that recover the outer stream via a - * vstream→*stream pointer cast, and the collapsed single accessors - * (string / buffer / reset / borrowedread) over the common - * header. The fold-eFinal FLIP collapsed the dual memio surface into - * this one Hare-shaped surface (bare names, no suffix). - * - * Each row imports memio + io, calls one or more constructors, drives - * the io.read/io.write/io.close dispatchers, and asserts an exit - * constant. Both stages run; cs.s == ww.s byte-id on every row. - * - * row | what it pins - * --------------------------+-------------------------------------- - * fixed_read_5 | fixed + io.read of 5 bytes - * | over an 8-byte source. Asserts the - * | return is `size` and equal to 5, and - * | that out[0]/out[4] mirror the buffer. - * | Pins the full intrusive shape (build - * | stream field-by-field → return r (sret); - * | caller takes &st.vt; dispatcher recovers - * | via s: *stream). - * dynamic_write_grow | dynamic + io.write of 3 - * | bytes. Asserts the write reports 3 - * | and that the dynamicgrow path - * | allocated the backing buffer (initial - * | cap=0 forces growth on the first - * | write). Closes via io.close to - * | exercise dynamicclose's free. - * dynamicfrom_alt_rw | dynamicfrom seeded with a - * | 4-byte slice. Alternates st_read - * | (4 bytes back) → st_write (2 bytes, - * | grows past cap=4 → realloc). Asserts - * | both paths report their lengths. - * | Single ctx flavour, two dispatcher - * | paths through the same vtable. - * branched_fixed | branched callee per #105: two - * | fixed instances with - * | different buffer sizes (3 and 5), - * | runtime-selected by the exit code - * | nudge. st_read on each returns the - * | matching size. Catches a constant- - * | fold mistake in the dispatch path - * | (mirror of 775's branched_readers - * | row for the vtable-init side). - * - * IMPORT ORDER (#208 CLOSED): every row imports `memio; io; os;` — - * os LAST. This is the formerly-failing os-late ordering. Pre-#208 it - * tripped the wwstage checker on io.read/write/close's `return s.read( - * s, buf)` (a fn-pointer field call) with 3 false "return: not - * assignable (i64 → )"/"(i32 → )" — because exprtype re-bound the field - * leaf to a same-named scalar global (os.read:i64) instead of the - * field's tagged fn type. #208 fixed exprtype to resolve value-receiver - * field calls from the field type (cmd/wcc/check.c:1378-1433 twin), so - * the order no longer matters and all 4 rows now run STAGE_CS|STAGE_WW - * byte-id. The earlier `import os;`-FIRST workaround is retired here. - * - * DEFERRALS / RELATED (NOT addressed by this row): - * - * - #173 (TRY-on-tagged-return both-stages broken) blocks - * fixedwrite from returning Hare's `nomem` when full; - * memio.ww surfaces 0 instead (documented divergence). - * The probe doesn't pin Hare-equivalent nomem behaviour; - * it can graduate once #173 closes. - * - * - #195 (cgassign N_DOT TK_ASSIGN on aliased-ptr receiver): - * not bitten — the value-return constructor builds the stream in - * a local `let r: stream;` and field-assigns each slot (including - * r.vt.reader) through a plain pointer-to-local, not an aliased-ptr - * receiver. The flat ptr/len/cap fields stay flat. - * - * GATE POLARITY: must stay GREEN. A red here means lib/memio - * regressed, the sret value-return path miscompiled, the intrusive - * io.stream→*stream cast miscomputed offsets, or io.vtable's - * first-field embed lost its offset-0 invariant. - */ -#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[] = { - { "fixed_read_5", - "package main;\n" - "import memio;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let buf: [8]u8;\n" - " buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 68u8;\n" - " buf[4] = 69u8; buf[5] = 70u8; buf[6] = 71u8; buf[7] = 72u8;\n" - " let st: memio.stream = memio.fixed(buf[0:8]);\n" - " let s: io.stream = &st.vt;\n" - " let out: [5]u8;\n" - " let n: size = 0;\n" - " let rd = io.read(s, out[0:5]);\n" - " if (rd is size) { n = rd as size; };\n" - " if (n == 5: size && out[0] == 65u8 && out[4] == 69u8) { return 42; };\n" - " return 99;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW, 1 }, - { "dynamic_write_grow", - "package main;\n" - "import memio;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let st: memio.stream = memio.dynamic();\n" - " let s: io.stream = &st.vt;\n" - " let src: [3]u8;\n" - " src[0] = 88u8; src[1] = 89u8; src[2] = 90u8;\n" - " let wr = io.write(s, src[0:3]);\n" - " let n: size = 0;\n" - " if (wr is size) { n = wr as size; };\n" - " let cl = io.close(s);\n" - " if (cl is void && n == 3: size) { return 43; };\n" - " return 99;\n" - "};\n", - 43, - STAGE_CS | STAGE_WW, 1 }, - { "dynamicfrom_alt_rw", - "package main;\n" - "import memio;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let seed: [4]u8;\n" - " seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n" - " let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n" - " let s: io.stream = &st.vt;\n" - " let out: [4]u8;\n" - " let rd = io.read(s, out[0:4]);\n" - " let rn: size = 0;\n" - " if (rd is size) { rn = rd as size; };\n" - " let extra: [2]u8;\n" - " extra[0] = 99u8; extra[1] = 100u8;\n" - " let wr = io.write(s, extra[0:2]);\n" - " let wn: size = 0;\n" - " if (wr is size) { wn = wr as size; };\n" - " if (rn == 4: size && wn == 2: size && out[0] == 1u8 && out[3] == 4u8) { return 44; };\n" - " return 99;\n" - "};\n", - 44, - STAGE_CS | STAGE_WW, 1 }, - { "branched_fixed", - "package main;\n" - "import memio;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let a: [3]u8;\n" - " a[0] = 10u8; a[1] = 11u8; a[2] = 12u8;\n" - " let b: [5]u8;\n" - " b[0] = 20u8; b[1] = 21u8; b[2] = 22u8; b[3] = 23u8; b[4] = 24u8;\n" - " let sta: memio.stream = memio.fixed(a[0:3]);\n" - " let stb: memio.stream = memio.fixed(b[0:5]);\n" - " let sa: io.stream = &sta.vt;\n" - " let sb: io.stream = &stb.vt;\n" - " let outa: [3]u8;\n" - " let outb: [5]u8;\n" - " let rda = io.read(sa, outa[0:3]);\n" - " let rdb = io.read(sb, outb[0:5]);\n" - " let na: size = 0;\n" - " let nb: size = 0;\n" - " if (rda is size) { na = rda as size; };\n" - " if (rdb is size) { nb = rdb as size; };\n" - " if (na == 3: size && nb == 5: size && outa[2] == 12u8 && outb[4] == 24u8) { return 45; };\n" - " return 99;\n" - "};\n", - 45, - STAGE_CS | STAGE_WW, 1 }, - /* accessors_string_buffer_reset — fold-eFinal PREP: the collapsed - * single accessors over the common `stream` header (string / - * buffer / reset, ref/hare/memio/stream.ha:81,74,87). Writes 3 - * bytes through the value-returned fixed stream, then asserts - * string (str view), buffer ([]u8 view), and reset (rewind to - * 0) all read the flat header correctly. Pins the value-return - * field integrity (st.vt + st.ptr/len/cap/pos survive sret) AND the - * accessor collapse. cs.s == ww.s byte-id. */ - { "accessors_string_buffer_reset", - "package main;\n" - "import memio;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let buf: [16]u8;\n" - " let st: memio.stream = memio.fixed(buf[0:16]);\n" - " let s: io.stream = &st.vt;\n" - " let payload: [3]u8;\n" - " payload[0] = 65u8; payload[1] = 66u8; payload[2] = 67u8;\n" - " let wr = io.write(s, payload[0:3]);\n" - " if (!(wr is size)) { return 90; };\n" - " let v: str = memio.string(&st);\n" - " if (v.len != 3 || v[0] != 65u8 || v[2] != 67u8) { return 91; };\n" - " let bv: []u8 = memio.buffer(&st);\n" - " if (bv.len != 3 || bv[1] != 66u8) { return 92; };\n" - " memio.reset(&st);\n" - " if (memio.string(&st).len != 0) { return 93; };\n" - " return 46;\n" - "};\n", - 46, - STAGE_CS | STAGE_WW, 1 }, - /* borrowedread_view_eof — the collapsed borrowedread - * (ref/hare/memio/stream.ha:94): returns an amt-byte borrowed view - * advancing the cursor, eof when fewer remain. Reads 3 of 4 seeded - * bytes (view len 3), then a 2-byte borrowedread over the remaining - * 1 byte returns io.eof. cs.s == ww.s byte-id. */ - { "borrowedread_view_eof", - "package main;\n" - "import memio;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let seed: [4]u8;\n" - " seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n" - " let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n" - " let br = memio.borrowedread(&st, 3);\n" - " match (br) {\n" - " case let b: []u8 => { if (b.len != 3 || b[0] != 1u8 || b[2] != 3u8) { return 91; }; };\n" - " case io.eof => { return 92; };\n" - " };\n" - " let br2 = memio.borrowedread(&st, 2);\n" - " match (br2) {\n" - " case let b: []u8 => { return 93; };\n" - " case io.eof => { return 47; };\n" - " };\n" - " return 99;\n" - "};\n", - 47, - 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. Sweep all then - * rmdir. */ -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", tmpdir, base); unlink(p); - /* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *cwd, - const char *src) -{ - char cmd[2048]; - /* #93 sep layout: `--sep -o ` emits the asm to - * .sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the - * shared out/.pkgcache is untouched and the cache dies with the - * tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver, cwd); - 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/mvs_%d_d_%d", getpid(), seq); - snprintf(base, sizeof base, "main776"); - 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; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/mvs_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/mvs_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main776"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/%s.ww", tdc, base); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/%s.ww", tdw, base); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, 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, - "memio_vstream_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, - "memio_vstream_run[ww][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) { - fprintf(stderr, - "memio_vstream_run[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "memio_vstream_run: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "memio_vstream_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("memio_vstream_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/777_fmt_handle_run.c b/test/wcc/777_fmt_handle_run.c deleted file mode 100644 index 463887e7..00000000 --- a/test/wcc/777_fmt_handle_run.c +++ /dev/null @@ -1,408 +0,0 @@ -/* - * 777_fmt_handle_run — io fold-2 (#5) commit-2 sentinel. Pins the - * fprint family over an [[io.handle]] sink after the fdsink shim - * removal: fmt's fprint/fprintln/fprintf now take `io.handle = - * (io.file | io.stream)`, so a raw fd (file arm) and a memio vtable - * (stream arm) both flow into the same surface and io.write dispatches - * per arm. Supersedes the pre-#5 fd_ctx workaround (the fake-stream - * vtable around os.write) — drew's placeholder for the then-missing - * handle, removed by this commit. - * - * The file-arm rows open a per-process /tmp output file, call one - * fprint-family fn with `fd: io.file` (widens to io.handle), close, - * reopen for read, read the bytes back, and assert both (a) the exact - * byte content and (b) a unique row-tagged exit constant. The - * stream-arm row drives the SAME fprintf over a memio.fixed vtable - * (the io.stream arm) and reads back via memio.string — the two-arm - * coverage #5 graduated. - * - * 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 a SEPARATE pre-existing - * cgen cluster surfaced once fmt actually codegens under wwstage: - * - #226: io.read's error widening (cgwidentagremap) loses the - * io.eof/io.error NAMED tinfo identity under fmt-presence - * (flatvariantidxt → -1 → tag collapses to 0); cs!=ww whole-file, so - * byte-id fails even though io.read is unused here (it's emitted - * regardless). The #10/#218/tinfo-lossy-nominal family. - * - #227: spread-union widen + return-ABI, broken in BOTH stages. - * 995 self-rebuild dodges all of this because no selfhost main pulls fmt - * transitively. Byte-id graduates when #226 (+#227) land. - * - * row | what it pins - * ---------------------+-------------------------------------- - * fprintf_file_int | fprintf(fd: io.file, "v={}\n", 42) - * | over the FILE arm -> io.write -> os.write, - * | plus the {n}-placeholder parser through - * | formatfield. - * fprintln_file_multi | fprintln(fd: io.file, 7, "hi", true) — - * | three positional args, space-separator - * | + trailing newline shape over the file arm. - * fprint_file_raw | fprint(fd: io.file, "raw") — one str arg, - * | no placeholder parser (bare fprint loop). - * branched_fprintf | branched callee per #105: two distinct - * | format strings runtime-selected from a - * | single fprintf call site. - * fprintf_stream | fprintf over a memio.fixed io.stream - * | (the STREAM arm of io.handle) read back - * | via memio.string — the other handle arm. - * - * IMPORT-ORDER WORKAROUND (sibling task #208, pre-existing): every row - * places `import os;` FIRST for the same reason 776 documents (wwstage - * checker ordering-sensitivity on os.tryread/trywrite/tryopen's bare - * `return r;` over a tagged return type). - * - * 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); 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 lib/fmt regressed, - * the file->handle widen miscompiled, io.write's per-arm dispatch broke, - * the {n}-placeholder dispatch through formatfield regressed, or the - * memio stream arm lost its offset-0 vtable invariant. - */ -#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[] = { - { "fprintf_file_int", - "package main;\n" - "import os;\n" - "import fmt;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let path: str = \"/tmp/wwfmth_777_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, \"v={}\\n\", 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] != 118u8 || buf[1] != 61u8 || buf[2] != 52u8 || buf[3] != 50u8 || buf[4] != 10u8) { return 95; };\n" - " return 42;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW, 1 }, - { "fprintln_file_multi", - "package main;\n" - "import os;\n" - "import fmt;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let path: str = \"/tmp/wwfmth_777_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.fprintln(fd: io.file, 7i64, \"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] != 55u8 || buf[1] != 32u8 || buf[2] != 104u8 || buf[3] != 105u8 || buf[4] != 32u8 || buf[5] != 116u8 || buf[6] != 114u8 || buf[7] != 117u8 || buf[8] != 101u8 || buf[9] != 10u8) { return 95; };\n" - " return 43;\n" - "};\n", - 43, - STAGE_CS | STAGE_WW, 1 }, - { "fprint_file_raw", - "package main;\n" - "import os;\n" - "import fmt;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let path: str = \"/tmp/wwfmth_777_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.fprint(fd: io.file, \"raw\");\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: [8]u8;\n" - " let n: i64 = os.read(rd, &buf[0], 8u64);\n" - " os.close(rd);\n" - " if (n != 3i64) { return 93; };\n" - " if (nw != 3: size) { return 94; };\n" - " if (buf[0] != 114u8 || buf[1] != 97u8 || buf[2] != 119u8) { return 95; };\n" - " return 44;\n" - "};\n", - 44, - STAGE_CS | STAGE_WW, 1 }, - { "branched_fprintf", - "package main;\n" - "import os;\n" - "import fmt;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let path: str = \"/tmp/wwfmth_777_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 sel: i32 = 1;\n" - " let f1: str = \"A{}\";\n" - " let f2: str = \"B{}\";\n" - " let fmtstr: str = f1;\n" - " if (sel == 0) { fmtstr = f2; };\n" - " let wr = fmt.fprintf(fd: io.file, fmtstr, 9i64);\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: [8]u8;\n" - " let n: i64 = os.read(rd, &buf[0], 8u64);\n" - " os.close(rd);\n" - " if (n != 2i64) { return 93; };\n" - " if (nw != 2: size) { return 94; };\n" - " if (buf[0] != 65u8 || buf[1] != 57u8) { return 95; };\n" - " return 45;\n" - "};\n", - 45, - STAGE_CS | STAGE_WW, 1 }, - { "fprintf_stream", - "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 s: io.stream = &st.vt;\n" - " let wr = fmt.fprintf(s, \"v={}\\n\", 42i64);\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 != 5: size) { return 94; };\n" - " let view: str = memio.string(&st);\n" - " if (view.len != 5) { return 93; };\n" - " if (view[0] != 118u8 || view[1] != 61u8 || view[2] != 52u8 || view[3] != 50u8 || view[4] != 10u8) { return 95; };\n" - " return 46;\n" - "};\n", - 46, - 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. Sweep all then - * rmdir. Mirror of 776's cleanup_tmp. */ -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", tmpdir, base); unlink(p); - /* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *cwd, - const char *src) -{ - char cmd[2048]; - /* #93 sep layout: `--sep -o ` emits the asm to - * .sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the - * shared out/.pkgcache is untouched and the cache dies with the - * tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver, cwd); - 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/fh_%d_d_%d", getpid(), seq); - snprintf(base, sizeof base, "main777"); - 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; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). Mirror of 776's. Unused - * while every row is cstage-only (#226/#227; #209 is closed), kept for - * the byte-id graduation once those cgen folds land. */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/fh_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/fh_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main777"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/%s.ww", tdc, base); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/%s.ww", tdw, base); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, 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_handle_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_handle_run[ww][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) { - fprintf(stderr, - "fmt_handle_run[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "fmt_handle_run: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "fmt_handle_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("fmt_handle_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/778_bufio_vstream_run.c b/test/wcc/778_bufio_vstream_run.c deleted file mode 100644 index 267689ff..00000000 --- a/test/wcc/778_bufio_vstream_run.c +++ /dev/null @@ -1,587 +0,0 @@ -/* - * 778_bufio_vstream_run — project #94 fold-eFinal sentinel. Pins - * the single-surface lib/bufio over io.stream — the unified surface - * the eFinal FLIP collapsed to, NOT a port of Hare's missing scanner - * features (those are #217, OUT of eFinal). Covers: - * - buffered stream: `stream` (vt@offset0 for the intrusive cast), - * `init` (returns the stream BY VALUE; sret field-by-field) - * over an underlying **io.stream** src — every underlying read/ - * write/close goes through io.read/io.write/io.close (the - * vtable dispatchers), io.error forwards directly (no nomem-widen). - * Plus setflush / flush (public + internal drain) / unread / - * isbuffered (fn-ptr-equality discriminator). - * - scanner: `scanner` (value-return newscannerbuf over an - * io.stream src) + scanbyte / scanbytes (single-byte delim) / - * scanline / finish. - * - * Each row imports os + bufio + memio + io (NOT fmt — bufio doesn't - * transitively pull fmt, so it dodges the fmt-presence cgen cluster - * #226 (io.read error-remap nominal-identity) + #227 (spread-union - * widen/return-ABI) that blocks the fmt-importing tests' byte-id; both - * stages run on every row, cs.s==ww.s — bufio is NOT compiler-embedded, - * so these byte-id rows are its ONLY byte-id cover). (#209, the wwstage - * formattable match-arm CHECKER bail, is now closed.) - * - * row | what it pins - * --------------------------+-------------------------------------- - * stream_write_flush | bufio.init + io.write - * | through bwrite + bufio.flush via - * | io.close. Asserts the data - * | reaches the underlying memio buffer - * | after close drains wbuf. Full chain: - * | stream built field-by-field → - * | return r (sret) → caller takes &b.vt → - * | dispatcher recovers via *stream → - * | flush → io.write(b.src, ...). - * stream_read_unread | init read path: io.read - * | refills rbuf from src, serves first - * | N bytes. Pins bread's read path - * | through the intrusive cast. unread - * | not exposed yet (no bufio.unread - * | equivalent on the V API surface - * | until eFinal); this row exercises - * | the base read path instead. - * isbuffered_v_discriminate | isbuffered on a fresh init - * | returns true; isbuffered on a - * | plain memio.fixed (no bufio - * | wrap) returns false. Pins the - * | fn-ptr-equality discriminator - * | (ref/hare/bufio/stream.ha:179) over - * | the bread/bwrite callback symbols. - * isbuffered_v_boundary | isbuffered discriminates by vtable - * | identity: true on the bufio.init - * | wrapper stream, false on the - * | underlying (non-bufio) memio stream. - * | Pins the fn-ptr-equality discriminator - * | (ref/hare/bufio/stream.ha:179) over - * | the single-surface io.stream. - * branched_bufio_wrap | branched callee per #105: two - * | distinct underlying *io.stream's - * | runtime-selected; bufio.init - * | wraps the picked one. io.read - * | returns the matching source's - * | content. Catches a constant-fold - * | mistake in the dispatch path - * | (mirror of 775's branched_readers - * | for the bufio-wrap side). - * - * DEFERRALS / RELATED (NOT addressed by this row): - * - * - io fold-2 (handle port): drew-deferred. Hare's - * ref/hare/bufio/stream.ha:69 takes `src: io::handle`; once - * handle = (file | int) lands, init's src parameter - * collapses accordingly. #5 graduates. - * - * - #206 (bare &fn → (*alias|void)): 5 cast sites in bufio.ww - * (3 vtable wire-up + 2 in isbuffered); explicit casts KEPT - * (ken/#214 — cast-drop deferred, gated on #214). - * - * - #173 (TRY-on-tagged-return both-stages broken): flush - * constructs nomem and widens to io.error explicitly rather - * than using `io.write(...)?`; same shape memio.ww + fmt.ww - * adopt. - * - * - #207 (struct-lit multi-tagged-field copy drops past first) and - * #210 (slice-field struct-lit drop): not reachable from the - * value-return form — stream is built field-by-field in a - * local then sret-returned, no `alloc(T{...})` struct-lit. - * - * BOOTSTRAP-EMBED CHECK: bufio is NOT embedded in any selfhost - * combined.ww (grep confirmed), so the fold-eFinal bufio collapse - * does NOT require a Makefile regen (#110); only lib/bufio/ test - * paths see the surface. 990-997 byte-id gates stay green by virtue - * of bufio being test-only. - * - * GATE POLARITY: must stay GREEN. A red here means lib/bufio - * regressed, the sret value-return path miscompiled, the intrusive - * io.stream→*stream cast miscomputed offsets, the io.vtable - * first-field embed lost its offset-0 invariant, or the fn-ptr- - * equality discriminator in isbuffered stopped resolving. - */ -#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[] = { - { "stream_write_flush", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [16]u8;\n" - " let mst: memio.stream = memio.fixed(raw[0:16]);\n" - " let msrc: io.stream = &mst.vt;\n" - " let rbuf: [8]u8;\n" - " let wbuf: [8]u8;\n" - " let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:8]);\n" - " let vs: io.stream = &b.vt;\n" - " let payload: [5]u8;\n" - " payload[0] = 104u8; payload[1] = 105u8;\n" - " payload[2] = 33u8; payload[3] = 98u8; payload[4] = 121u8;\n" - " let wr = io.write(vs, payload[0:5]);\n" - " let nw: size = 0;\n" - " if (wr is size) { nw = wr as size; };\n" - " if (mst.pos != 0) { return 91; };\n" - " let cl = io.close(vs);\n" - " if (cl is io.error) { return 92; };\n" - " if (mst.pos != 5) { return 93; };\n" - " if (nw != 5: size) { return 94; };\n" - " if (raw[0] != 104u8 || raw[4] != 121u8) { return 95; };\n" - " return 46;\n" - "};\n", - 46, - STAGE_CS | STAGE_WW, 1 }, - { "stream_read_unread", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [8]u8;\n" - " raw[0] = 65u8; raw[1] = 66u8; raw[2] = 67u8; raw[3] = 68u8;\n" - " raw[4] = 69u8; raw[5] = 70u8; raw[6] = 71u8; raw[7] = 72u8;\n" - " let mst: memio.stream = memio.fixed(raw[0:8]);\n" - " let msrc: io.stream = &mst.vt;\n" - " let rbuf: [8]u8;\n" - " let wbuf: [4]u8;\n" - " let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:4]);\n" - " let vs: io.stream = &b.vt;\n" - " let out: [4]u8;\n" - " let rd1 = io.read(vs, out[0:3]);\n" - " let n1: size = 0;\n" - " if (rd1 is size) { n1 = rd1 as size; };\n" - " if (n1 != 3: size) { return 91; };\n" - " if (out[0] != 65u8 || out[2] != 67u8) { return 92; };\n" - " let rd2 = io.read(vs, out[0:4]);\n" - " let n2: size = 0;\n" - " if (rd2 is size) { n2 = rd2 as size; };\n" - " if (n2 != 4: size) { return 93; };\n" - " if (out[0] != 68u8 || out[3] != 71u8) { return 94; };\n" - " return 47;\n" - "};\n", - 47, - STAGE_CS | STAGE_WW, 1 }, - { "isbuffered_v_discriminate", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [8]u8;\n" - " let mst: memio.stream = memio.fixed(raw[0:8]);\n" - " let msrc: io.stream = &mst.vt;\n" - " let rbuf: [4]u8;\n" - " let wbuf: [4]u8;\n" - " let b: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n" - " let vsbuf: io.stream = &b.vt;\n" - " let buf2: [4]u8;\n" - " let st2: memio.stream = memio.fixed(buf2[0:4]);\n" - " let vsplain: io.stream = &st2.vt;\n" - " if (!bufio.isbuffered(vsbuf)) { return 91; };\n" - " if (bufio.isbuffered(vsplain)) { return 92; };\n" - " return 48;\n" - "};\n", - 48, - STAGE_CS | STAGE_WW, 1 }, - { "isbuffered_v_boundary", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [8]u8;\n" - " let mst: memio.stream = memio.fixed(raw[0:8]);\n" - " let msrc: io.stream = &mst.vt;\n" - " let rbuf: [4]u8;\n" - " let wbuf: [4]u8;\n" - " let bc: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n" - " let vs: io.stream = &bc.vt;\n" - " if (!bufio.isbuffered(vs)) { return 91; };\n" - " if (bufio.isbuffered(msrc)) { return 92; };\n" - " return 49;\n" - "};\n", - 49, - STAGE_CS | STAGE_WW, 1 }, - { "branched_bufio_wrap", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raA: [4]u8;\n" - " raA[0] = 10u8; raA[1] = 11u8; raA[2] = 12u8; raA[3] = 13u8;\n" - " let raB: [4]u8;\n" - " raB[0] = 20u8; raB[1] = 21u8; raB[2] = 22u8; raB[3] = 23u8;\n" - " let mstA: memio.stream = memio.fixed(raA[0:4]);\n" - " let mstB: memio.stream = memio.fixed(raB[0:4]);\n" - " let sel: i32 = 1;\n" - " let src: io.stream = &mstA.vt;\n" - " if (sel == 0) { src = &mstB.vt; };\n" - " let rbuf: [4]u8;\n" - " let wbuf: [4]u8;\n" - " let bc: bufio.stream = bufio.init(src, rbuf[0:4], wbuf[0:4]);\n" - " let vs: io.stream = &bc.vt;\n" - " let out: [4]u8;\n" - " let rd = io.read(vs, out[0:4]);\n" - " let n: size = 0;\n" - " if (rd is size) { n = rd as size; };\n" - " if (n != 4: size) { return 91; };\n" - " if (out[0] != 10u8 || out[3] != 13u8) { return 92; };\n" - " return 51;\n" - "};\n", - 51, - STAGE_CS | STAGE_WW, 1 }, - /* scanner_lines — fold-eFinal PREP: the value-return scanner over an - * io.stream src. newscannerbuf (Hare newscanner_buf) → scanline - * twice ("ab", "c") → trailing "def" with no newline is EOF_DISCARD - * → io.eof → finish. Pins the scanner reads through io.read - * (vtable dispatch) on the underlying memio stream. cs.s==ww.s. */ - { "scanner_lines", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [8]u8;\n" - " raw[0]=97u8; raw[1]=98u8; raw[2]=10u8;\n" - " raw[3]=99u8; raw[4]=10u8;\n" - " raw[5]=100u8; raw[6]=101u8; raw[7]=102u8;\n" - " let mst: memio.stream = memio.dynamicfrom(raw[0:8]);\n" - " let vs: io.stream = &mst.vt;\n" - " let win: [16]u8;\n" - " let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:16]);\n" - " let ok: i32 = 0;\n" - " match (bufio.scanline(&sc)) {\n" - " case let s: str => { if (s.len == 2 && s[0] == 97u8 && s[1] == 98u8) { ok += 1; }; };\n" - " case io.eof => { return 81; };\n" - " case let e: io.error => { return 82; };\n" - " case bufio.overflow => { return 83; };\n" - " };\n" - " match (bufio.scanline(&sc)) {\n" - " case let s: str => { if (s.len == 1 && s[0] == 99u8) { ok += 1; }; };\n" - " case io.eof => { return 84; };\n" - " case let e: io.error => { return 85; };\n" - " case bufio.overflow => { return 86; };\n" - " };\n" - " match (bufio.scanline(&sc)) {\n" - " case let s: str => { return 87; };\n" - " case io.eof => { ok += 1; };\n" - " case let e: io.error => { return 88; };\n" - " case bufio.overflow => { return 89; };\n" - " };\n" - " bufio.finish(&sc);\n" - " if (ok != 3) { return 90; };\n" - " return 52;\n" - "};\n", - 52, - STAGE_CS | STAGE_WW, 1 }, - /* scanner_byte_bytes — scanbyte pops 'A'; scanbytes(',') then - * tokenizes "B" and "CD" from "AB,CD,". Single-byte delim (the OLD - * scantok behaviour; multibyte is #217). cs.s==ww.s. */ - { "scanner_byte_bytes", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [6]u8;\n" - " raw[0]=65u8; raw[1]=66u8; raw[2]=44u8;\n" - " raw[3]=67u8; raw[4]=68u8; raw[5]=44u8;\n" - " let mst: memio.stream = memio.dynamicfrom(raw[0:6]);\n" - " let vs: io.stream = &mst.vt;\n" - " let win: [8]u8;\n" - " let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:8]);\n" - " let fb: u8 = 0u8;\n" - " match (bufio.scanbyte(&sc)) {\n" - " case let b: u8 => { fb = b; };\n" - " case io.eof => { return 81; };\n" - " case let e: io.error => { return 82; };\n" - " case bufio.overflow => { return 92; };\n" - " };\n" - " if (fb != 65u8) { return 83; };\n" - " match (bufio.scanbytes(&sc, 44u8)) {\n" - " case let bs: []u8 => { if (bs.len != 1 || bs[0] != 66u8) { return 84; }; };\n" - " case io.eof => { return 85; };\n" - " case let e: io.error => { return 86; };\n" - " case bufio.overflow => { return 87; };\n" - " };\n" - " match (bufio.scanbytes(&sc, 44u8)) {\n" - " case let bs: []u8 => { if (bs.len != 2 || bs[0] != 67u8 || bs[1] != 68u8) { return 88; }; };\n" - " case io.eof => { return 89; };\n" - " case let e: io.error => { return 90; };\n" - " case bufio.overflow => { return 91; };\n" - " };\n" - " return 53;\n" - "};\n", - 53, - STAGE_CS | STAGE_WW, 1 }, - /* stream_setflush — setflush swaps the auto-flush byte-set to ';'; - * writing "ab;" auto-flushes (contains ';') so the sink advances to - * 3 with no explicit flush. Pins setflush + the flush-scan path. - * cs.s==ww.s. */ - { "stream_setflush", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let sinkbuf: [16]u8;\n" - " let sink: memio.stream = memio.fixed(sinkbuf[0:16]);\n" - " let sinkvs: io.stream = &sink.vt;\n" - " let rb: [4]u8;\n" - " let wb: [8]u8;\n" - " let b: bufio.stream = bufio.init(sinkvs, rb[0:4], wb[0:8]);\n" - " let bvs: io.stream = &b.vt;\n" - " let semi: [1]u8;\n" - " semi[0] = 59u8;\n" - " bufio.setflush(&b, semi[0:1]);\n" - " let payload: [3]u8;\n" - " payload[0]=97u8; payload[1]=98u8; payload[2]=59u8;\n" - " let wr = io.write(bvs, payload[0:3]);\n" - " if (!(wr is size)) { return 81; };\n" - " if (sink.pos != 3) { return 82; };\n" - " if (sinkbuf[0] != 97u8 || sinkbuf[2] != 59u8) { return 83; };\n" - " return 54;\n" - "};\n", - 54, - STAGE_CS | STAGE_WW, 1 }, - /* stream_unread — read "XYZ" then unread("XY") pushes the two - * bytes back in front of the read buffer; the next read returns them - * first. Pins unread's in-place shift through *stream. - * cs.s==ww.s. */ - { "stream_unread", - "package main;\n" - "import os;\n" - "import bufio;\n" - "import memio;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let raw: [4]u8;\n" - " raw[0]=88u8; raw[1]=89u8; raw[2]=90u8;\n" - " let mst: memio.stream = memio.dynamicfrom(raw[0:3]);\n" - " let vs: io.stream = &mst.vt;\n" - " let rbuf: [4]u8;\n" - " let wbuf: [4]u8;\n" - " let b: bufio.stream = bufio.init(vs, rbuf[0:4], wbuf[0:4]);\n" - " let bvs: io.stream = &b.vt;\n" - " let out: [4]u8;\n" - " let rd = io.read(bvs, out[0:3]);\n" - " let n: size = 0;\n" - " if (rd is size) { n = rd as size; };\n" - " if (n != 3: size || out[0] != 88u8 || out[2] != 90u8) { return 81; };\n" - " let push: [2]u8;\n" - " push[0]=88u8; push[1]=89u8;\n" - " bufio.unread(&b, push[0:2]);\n" - " let out2: [2]u8;\n" - " let rd2 = io.read(bvs, out2[0:2]);\n" - " let n2: size = 0;\n" - " if (rd2 is size) { n2 = rd2 as size; };\n" - " if (n2 != 2: size || out2[0] != 88u8 || out2[1] != 89u8) { return 82; };\n" - " return 55;\n" - "};\n", - 55, - 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. Sweep all then - * rmdir. Mirror of 776's/777's cleanup_tmp. */ -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", tmpdir, base); unlink(p); - /* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *cwd, - const char *src) -{ - char cmd[2048]; - /* #93 sep layout: `--sep -o ` emits the asm to - * .sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the - * shared out/.pkgcache is untouched and the cache dies with the - * tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver, cwd); - 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/bvs_%d_d_%d", getpid(), seq); - snprintf(base, sizeof base, "main778"); - 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; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). Mirror of 776's/777's. */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/bvs_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/bvs_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main778"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/%s.ww", tdc, base); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/%s.ww", tdw, base); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, 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, - "init_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, - "init_run[ww][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) { - fprintf(stderr, - "init_run[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "init_run: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "init_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("init_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/791_io_handle_run.c b/test/wcc/791_io_handle_run.c deleted file mode 100644 index 105f1e92..00000000 --- a/test/wcc/791_io_handle_run.c +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 791_io_handle_run — project #5 commit-1 sentinel. Pins the lib/io - * handle dispatch surface: `handle = (file | stream)` and the - * read/write/close/seek/tell dispatchers (ref/hare/io/handle.ha:16-67) - * over BOTH arms: - * - * - file-arm: a real OS fd (io.file = i32) routed to - * os.{read,write,lseek,close} — the Q2 layering (lib/io imports os, - * os is the import floor). The file-arm syscall-error path returns - * the #199b errors.unsupported placeholder (see lib/io/types.ww:40), - * pinned by the file_arm_error_stub row (a never-opened fd forces - * EBADF on every file-arm dispatcher). - * - stream-arm: a memio (or hand-built) vtable routed to the private - * st_read/st_write/st_seek (ref/hare/io/stream.ha:44-77). - * - * row | what it pins - * -------------------------+-------------------------------------- - * file_arm_roundtrip | open(/tmp) → io.write "ABC" → io.seek - * | SET 0 → io.read back → io.seek END == 3 - * | → io.tell == 3 → io.close. Real fd - * | through every file-arm dispatcher; - * | SET+END+CUR(via tell) whence. exit 42. - * stream_write_read | memio.fixed: io.write 3 bytes, verify - * | the backing buffer, then read them back - * | through io.read on a fresh fixed stream. - * | exit 43. - * stream_seek_unsupported | hand-built vtable with the seeker slot - * | left void; io.seek takes st_seek's - * | void-arm and returns an io.error (not - * | an off). exit 44. (Was memio.fixed: - * | memio wires a seeker now, so its seek - * | succeeds — pinned by memiotest; this - * | row keeps the void-arm dispatch pin.) - * stream_seeker_tell | hand-built vtable WITH a seeker fn: - * | io.seek returns the seeker's off, and - * | io.tell == seek(s, 0, CUR). exit 45. - * file_arm_error_stub | never-opened fd (9999) → every file-arm - * | dispatcher's syscall returns -1 (EBADF) - * | → the #199b errors.unsupported stub - * | comes back as io.error. exit 46. - * - * Both stages run each row; cs.s == ww.s byte-id is asserted per row - * (rule-10). GATE POLARITY: must stay GREEN — a red means the handle - * union decompose, a dispatcher arm, or the seeker slot 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; - int byte_id; -}; - -static const struct row rows[] = { - { "file_arm_roundtrip", - "package main;\n" - "import io;\n" - "import os;\n" - "export fn main() i32 = {\n" - " let cflags: os.flag = os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC;\n" - " let fd: i32 = 0;\n" - " match (os.tryopen(\"/tmp/ww_io791_handle.dat\", cflags, 420i32)) {\n" - " case let e: os.oserror => return 81;\n" - " case let f: i32 => fd = f;\n" - " };\n" - " let h: io.file = fd;\n" - " let data: [3]u8;\n" - " data[0] = 65u8; data[1] = 66u8; data[2] = 67u8;\n" - " match (io.write(h, data[0:3])) {\n" - " case let n: size => { if (n != 3: size) { return 82; }; };\n" - " case let e: io.error => return 83;\n" - " };\n" - " match (io.seek(h, 0: io.off, io.whence.SET)) {\n" - " case let o: io.off => { if (o != 0: io.off) { return 84; }; };\n" - " case let e: io.error => return 85;\n" - " };\n" - " let rb: [3]u8;\n" - " match (io.read(h, rb[0:3])) {\n" - " case let n: size => { if (n != 3: size) { return 86; }; };\n" - " case let z: io.eof => return 87;\n" - " case let e: io.error => return 88;\n" - " };\n" - " if (rb[0] != 65u8 || rb[1] != 66u8 || rb[2] != 67u8) { return 89; };\n" - " match (io.seek(h, 0: io.off, io.whence.END)) {\n" - " case let o: io.off => { if (o != 3: io.off) { return 93; }; };\n" - " case let e: io.error => return 94;\n" - " };\n" - " match (io.tell(h)) {\n" - " case let o: io.off => { if (o != 3: io.off) { return 90; }; };\n" - " case let e: io.error => return 91;\n" - " };\n" - " match (io.close(h)) {\n" - " case void => void;\n" - " case let e: io.error => return 92;\n" - " };\n" - " return 42;\n" - "};\n", - 42, - STAGE_CS | STAGE_WW, 1 }, - { "stream_write_read", - "package main;\n" - "import io;\n" - "import memio;\n" - "export fn main() i32 = {\n" - " let buf: [8]u8;\n" - " let st: memio.stream = memio.fixed(buf[0:8]);\n" - " let s: io.stream = &st.vt;\n" - " let data: [3]u8;\n" - " data[0] = 88u8; data[1] = 89u8; data[2] = 90u8;\n" - " match (io.write(s, data[0:3])) {\n" - " case let n: size => { if (n != 3: size) { return 71; }; };\n" - " case let e: io.error => return 72;\n" - " };\n" - " if (buf[0] != 88u8 || buf[1] != 89u8 || buf[2] != 90u8) { return 73; };\n" - " let st2: memio.stream = memio.fixed(buf[0:8]);\n" - " let s2: io.stream = &st2.vt;\n" - " let rb: [3]u8;\n" - " match (io.read(s2, rb[0:3])) {\n" - " case let n: size => { if (n != 3: size) { return 74; }; };\n" - " case let z: io.eof => return 75;\n" - " case let e: io.error => return 76;\n" - " };\n" - " if (rb[0] != 88u8 || rb[1] != 89u8 || rb[2] != 90u8) { return 77; };\n" - " return 43;\n" - "};\n", - 43, - STAGE_CS | STAGE_WW, 1 }, - { "stream_seek_unsupported", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " let s: io.stream = &vt;\n" - " match (io.seek(s, 0: io.off, io.whence.SET)) {\n" - " case let o: io.off => return 61;\n" - " case let e: io.error => return 44;\n" - " };\n" - "};\n", - 44, - STAGE_CS | STAGE_WW, 1 }, - { "stream_seeker_tell", - "package main;\n" - "import io;\n" - "fn myseek(s: io.stream, off: io.off, w: io.whence) (io.off | io.error) = {\n" - " return off + 100: io.off;\n" - "};\n" - "export fn main() i32 = {\n" - " let vt: io.vtable;\n" - " vt.seeker = (&myseek): *io.seeker;\n" - " let s: io.stream = &vt;\n" - " match (io.seek(s, 5: io.off, io.whence.SET)) {\n" - " case let o: io.off => { if (o != 105: io.off) { return 51; }; };\n" - " case let e: io.error => return 52;\n" - " };\n" - " match (io.tell(s)) {\n" - " case let o: io.off => { if (o != 100: io.off) { return 53; }; };\n" - " case let e: io.error => return 54;\n" - " };\n" - " return 45;\n" - "};\n", - 45, - STAGE_CS | STAGE_WW, 1 }, - { "file_arm_error_stub", - "package main;\n" - "import io;\n" - "export fn main() i32 = {\n" - /* A never-opened fd: every file-arm syscall returns -1 (EBADF), - * driving the #199b errors.unsupported stub on all four - * dispatchers (write/read/seek/close). Since that stub is the - * only error-producing path reachable here, an io.error arm - * taken == the placeholder came back. */ - " let h: io.file = 9999i32;\n" - " let data: [3]u8;\n" - " data[0] = 65u8; data[1] = 66u8; data[2] = 67u8;\n" - " match (io.write(h, data[0:3])) {\n" - " case let n: size => return 61;\n" - " case let e: io.error => void;\n" - " };\n" - " match (io.read(h, data[0:3])) {\n" - " case let n: size => return 62;\n" - " case let z: io.eof => return 63;\n" - " case let e: io.error => void;\n" - " };\n" - " match (io.seek(h, 0: io.off, io.whence.SET)) {\n" - " case let o: io.off => return 64;\n" - " case let e: io.error => void;\n" - " };\n" - " match (io.close(h)) {\n" - " case void => return 65;\n" - " case let e: io.error => void;\n" - " };\n" - " return 46;\n" - "};\n", - 46, - 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. Sweep them all then rmdir. */ -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", tmpdir, base); unlink(p); - /* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); - if (system(p)) {} /* best-effort */ - rmdir(tmpdir); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *cwd, - const char *src) -{ - char cmd[2048]; - /* #93 sep layout: `--sep -o ` emits the asm to - * .sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the - * shared out/.pkgcache is untouched and the cache dies with the - * tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */ - snprintf(cmd, sizeof cmd, - "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " - "-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null", - tmpdir, src, tmpdir, driver, cwd); - 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/ioh_%d_d_%d", getpid(), seq); - snprintf(base, sizeof base, "main791"); - 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; -} - -/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so - * ww_ww writing intermediates next to the source doesn't clobber the - * cstage .s (CLAUDE.md rule 14 phase split). */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, - const struct row *r, int seq) -{ - char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; - snprintf(tdc, sizeof tdc, "/tmp/ioh_%d_c_%d", getpid(), seq); - snprintf(tdw, sizeof tdw, "/tmp/ioh_%d_w_%d", getpid(), seq); - snprintf(base, sizeof base, "main791"); - mkdir(tdc, 0755); - mkdir(tdw, 0755); - snprintf(src, sizeof src, "%s/%s.ww", tdc, base); - if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } - int rc = -1; - if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - snprintf(src, sizeof src, "%s/%s.ww", tdw, base); - if (write_source(src, r->src) != 0) goto out; - if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out; - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - cleanup_tmp(tdc, base); - cleanup_tmp(tdw, 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, - "io_handle_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, - "io_handle_run[ww][%s]: exit=%d want=%d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - if (rows[i].byte_id) { - total++; - if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) { - fprintf(stderr, - "io_handle_run[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - } - - if (!wwpresent) - fprintf(stderr, "io_handle_run: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "io_handle_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("io_handle_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/792_spread_variant_match.c b/test/wcc/792_spread_variant_match.c deleted file mode 100644 index 4ec163a9..00000000 --- a/test/wcc/792_spread_variant_match.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 792_spread_variant_match — project #209 close. Pins that the wwstage - * checker accepts a `match` over a tagged union with a `...inner` SPREAD - * variant, byte-identically with cstage (rule-10). - * - * THE BUG (wwstage-CHECKER-only, cs!=ww): a spread variant - * `type field = (...inner | str)` flattens its inner union's members - * into `field` (i64|bool|rune|str). cstage's resolve_type performs the - * flatten at type-build (cmd/wcc/check.c:651-674), so its match-arm - * validity + exhaustiveness see the flattened members. wwstage's checker - * (selfhost/cmd/wcc/check.ww checkmatchexhaust) walked the RAW AST - * `u.list`, which keeps the spread unexpanded — every member arm tripped - * "case: not a variant of scrutinee" and the phantom spread node tripped - * "match: variant not handled". So any module that matches over a spread - * union (e.g. fmt's `field = (...formattable | *mods)`) was - * wwstage-uncompilable. cstage compiled it fine. Hare flattens spreads; - * align UP to cstage (a too-strict checker, NOT a down-align). - * - * THE FIX (#209): casevariantin + a checkvariantcovered coverage helper - * recurse into a spread variant's inner-union members (resolvealias → - * N_TTAGGED → walk members), mirroring cstage's flatten at the AST - * layer; the #61a tinfo build additionally sizes the union off the - * flattened MEMBERS (not the whole inner union) so the slot matches - * cstage's. cgen already dispatches off the flattened tinfo.params - * (flatvariantidxt), so no cgen change is needed — checker-only. - * - * The PRE-FIX failure mode is a wwstage CHECKER REJECT, so the - * discriminator is `w6c_ww` on the driver-produced combined.ww - * succeeding AT ALL — pre-fix it errored out; post-fix it succeeds AND - * is byte-id with cstage's w6c. The probe stays on the param+match shape - * (a fn taking the spread union and matching it, exhaustive, no - * default): that is exactly what #209's checker fix covers and is - * byte-id. (Spread-union CONSTRUCTION/return-ABI is a separate, - * pre-existing both-stage cgen path — out of #209 scope — so it is - * deliberately NOT exercised here.) Self-contained single-file probe. - */ -#define _GNU_SOURCE -#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); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -static const char SRC[] = - "package main;\n" - "\n" - "type inner = (i64 | bool | rune);\n" - "type field = (...inner | str);\n" - "\n" - "fn classify(f: field) i64 = {\n" - " match (f) {\n" - " case let n: i64 => return n;\n" - " case let b: bool => return 1;\n" - " case let r: rune => return 2;\n" - " case let s: str => return (s.len: i64);\n" - " };\n" - "};\n" - "\n" - "export fn main() i32 = { return 0; };\n"; - -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 cdrv[2100], wdrv[2100]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - if (access(wdrv, X_OK) != 0) { - fprintf(stderr, "792: ww_ww missing — cannot run the cs==ww " - "byte-id gate (the whole point of this test)\n"); - return 1; - } - - char dir[] = "/tmp/ww792_XXXXXX"; - if (mkdtemp(dir) == NULL) { fprintf(stderr, "792: mkdtemp\n"); return 1; } - - char path[1024], cmd[4096], cs_s[1024], ws_s[1024]; - int rc = 0; - - snprintf(path, sizeof path, "%s/main.ww", dir); - FILE *f = fopen(path, "wb"); - if (!f) { fprintf(stderr, "792: write main.ww\n"); rc = 1; goto done; } - fputs(SRC, f); - fclose(f); - - /* #94: sep layout. The cstage driver's --sep build emits the root - * unit's asm to .sepwork/__root.s via its internal w6c; pin - * WW_PKGCACHE under the scratch dir so out/.pkgcache is untouched. */ - snprintf(cs_s, sizeof cs_s, "%s/main.sepwork/__root.s", dir); - snprintf(ws_s, sizeof ws_s, "%s/mainww.sepwork/__root.s", dir); - - snprintf(cmd, sizeof cmd, - "cd %s && WW_PKGCACHE=%s/pkgc_c %s build --sep -I %s -o %s/main %s/main.ww", - dir, dir, cdrv, dir, dir, dir); - if (runwait(cmd) != 0) { - fprintf(stderr, "792: cstage sep build failed\n"); - rc = 1; goto done; - } - - /* The #209 discriminator: the wwstage driver's --sep build runs the - * wwstage checker over the same root unit. Pre-fix it REJECTED the - * spread-union match arms (build fails); post-fix it accepts AND its - * w6c_ww-emitted __root.s is byte-id with the cstage __root.s. */ - snprintf(cmd, sizeof cmd, - "cd %s && WW_PKGCACHE=%s/pkgc_w %s build --sep -I %s -o %s/mainww %s/main.ww", - dir, dir, wdrv, dir, dir, dir); - if (runwait(cmd) != 0) { - fprintf(stderr, "792: wwstage sep build failed " - "(#209 spread-union match reject?)\n"); - rc = 1; goto done; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "792: cs.s/ww.s DIFFER (rule-10 byte-id " - "violation)\n"); - rc = 1; goto done; - } - - printf("spread_variant_match: ok (w6c_ww accepts spread match + " - "cs==ww byte-id)\n"); - -done: - snprintf(cmd, sizeof cmd, "rm -rf %s", dir); - (void)runwait(cmd); - return rc; -} diff --git a/test/wcc/800_append_wide_elem.c b/test/wcc/800_append_wide_elem.c deleted file mode 100644 index 15c942c7..00000000 --- a/test/wcc/800_append_wide_elem.c +++ /dev/null @@ -1,491 +0,0 @@ -/* - * 800_append_wide_elem — cstage and wwstage agree, byte-for-byte and at - * runtime, that `append(s, v)` stores the FULL element width for every - * element kind (task #34; the regex fold-2a blocker). - * - * The bug: both stages lowered the append element store as ONE sized - * mov from AX (`MOV* AX, (BX)`) — correct only for scalars <= 8B. A - * str/slice element (24B {ptr,len,cap}, cgexpr -> AX/BX/CX) kept only - * .ptr (byte-id-BLIND: both stages identical and identically wrong); a - * tagged element got the raw payload written into the tag slot (no - * boxing — the #12 pathology); a struct element kept only its first - * qword. wwstage ADDITIONALLY fed rt_ensure membsz from bare - * elemsizeof, whose 8-sentinel under-allocated AND mis-strided named - * tagged/struct elements (the #8 family; cs!=ww on the `MOVQ $N, SI` - * line and the stride IMUL). - * - * The fix (BOTH stages, converged byte-identical), keyed on the - * DECLARED slice local's element type (cstage su->sub; wwstage stamped - * tinfo via elemsizeofc — NEVER the value node, the #25/#31 esz=0 - * trap): - * - scalar 1/2/4/8: UNTOUCHED (the u8 row's asm is unchanged vs - * pre-fix master — regression-pinned by byte-id + runtime). - * - str/slice: push AX/BX/CX across rt_ensure, dst in DX (BX holds - * the element .len after the pops — the #24 register discipline), - * 3-word store at (DX)/8(DX)/16(DX). - * - tagged: grow FIRST, dst -> BX, then the #12 widen choke-point - * (cg_widen_tagged_store / cgwidentaggedstore) boxes {tag,payload} - * through @tagbase/@tagscr (via_outer mode). - * - struct: grow first; literal -> dst spilled to @appendscr + - * structlit fill (DST_PTR_LOCAL); local ident -> word-copy; ANY - * other source shape is a rule-7 loud-stop (build fails), never a - * silent scalar fall-through. - * - spread `append(s, items...)`: the source element is already a - * fully-formed T (tag included), so the wide arm grows first and - * whole-width word-copies &items[i] -> dst, recomputing both - * addresses from the slice headers after the (possibly - * reallocating) rt_ensure. - * - * row | shape | want - * ---------------------+--------------------------------------+------ - * u8_baseline | []u8, two appends, sum | 8 - * enum_alias_esz | []ek (enum i32 alias) — pins the | 5 - * | elemsizeofc swap: wwstage fed SI=$8 | - * | where cstage fed $4 (cs!=ww growth). | - * tagged_box | [](i64|bool), append 100i64, match | 100 - * | readback. Pre-fix: raw 100 landed in | - * | the tag slot, no arm matched. | - * tagged_two_append | two appends — element 0 survives the | 42 - * | realloc full-width. | - * str_len_bytes | []str, append, len + byte readback | 11 - * str_two_append | two str appends, len0*10 + len1 | 32 - * slice_elem | [][]u8, append, len + byte readback | 12 - * struct_first8 | []pt (16B), x+y (first qword — ken's | 8 - * | characterization row) | - * struct_tail_word | []pt, z at +8 (the word the 1-word | 8 - * | store dropped) | - * struct_lit_two | two struct-LITERAL appends (pins the | 7 - * | @appendscr per-fn dedup: frame + | - * | byte-id diverge if cstage allocs two | - * | scratch slots where wwstage dedups) | - * spread_str | append(ys, xs...) of []str + single | 7 - * spread_tagged | append(ys, xs...) of [](i64|bool) | 42 - * tagged_ident_src | append of ALREADY-TAGGED locals | 42 - * | (i64 + bool members) — the widener's | - * | already-tagged source path. | - * struct_eight_appends | 8 struct-lit appends of the getopt | 15 - * | option shape ({rune,str} = 32B), | - * | full-width readback of element 7 — | - * | the >6-element OOB regression pin | - * | (getopt's deleted helper fed a stale | - * | hardcoded membsz=24: 8-slot grow was | - * | 192B while writes strode 32; the | - * | builtin derives 32 from the type | - * | table, MOVQ $32, SI). | - * struct_call_loudstop | append(xs, f()) struct-from-call is | BUILD_FAIL - * | the deferred source shape — must | - * | fail LOUD on both stages (rule-7), | - * | never silently store one word. | - * spread_call_loudstop | append(ys, f()...) non-ident spread | BUILD_FAIL - * | source — pre-review this fell PAST | - * | the spread arm (cstage garbage store | - * | vs wwstage silent skip, divergent); | - * | now a rule-7 loud-stop (task #37). | - * - * Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id, - * which subsumes the frame-size canary (TEXT main,$N) — the @tagbase/ - * @tagscr/@appendscr scratch allocations must land in the same order - * and size on both stages (#25/#31 lesson). - */ -#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; -} - -/* want == BUILD_FAIL: the row must FAIL to build on both stages (the - * rule-7 loud-stop for a deferred source shape). run_driver returns -1 - * exactly when the build fails, so `got == -1` is the pass. */ -#define BUILD_FAIL (-2147483647 - 1) - -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - { "u8_baseline", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tappend(xs, 3u8);\n" - "\treturn (xs[0] + xs[1]): i32;\n" - "};\n", - 8 }, - - { "enum_alias_esz", - "package main;\n" - "type ek = enum { A = 5, B = 9 };\n" - "export fn main() i32 = {\n" - "\tlet xs: []ek = [];\n" - "\tappend(xs, ek.A);\n" - "\treturn xs[0]: i32;\n" - "};\n", - 5 }, - - { "tagged_box", - "package main;\n" - "type cell = (i64 | bool);\n" - "export fn main() i32 = {\n" - "\tlet xs: []cell = [];\n" - "\tappend(xs, 100i64);\n" - "\tlet r: i32 = 1;\n" - "\tmatch (xs[0]) {\n" - "\tcase let v: i64 => r = v: i32;\n" - "\tcase bool => r = 2;\n" - "\t};\n" - "\treturn r;\n" - "};\n", - 100 }, - - { "tagged_two_append", - "package main;\n" - "type cell = (i64 | bool);\n" - "export fn main() i32 = {\n" - "\tlet xs: []cell = [];\n" - "\tappend(xs, 40i64);\n" - "\tappend(xs, 2i64);\n" - "\tlet r: i32 = 0;\n" - "\tmatch (xs[0]) {\n" - "\tcase let v: i64 => r += v: i32;\n" - "\tcase bool => r = 99;\n" - "\t};\n" - "\tmatch (xs[1]) {\n" - "\tcase let v: i64 => r += v: i32;\n" - "\tcase bool => r = 98;\n" - "\t};\n" - "\treturn r;\n" - "};\n", - 42 }, - - { "str_len_bytes", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []str = [];\n" - "\tlet s: str = \"abcdef\";\n" - "\tappend(xs, s);\n" - "\treturn (xs[0].len: i32) + (xs[0][5]: i32) - (xs[0][0]: i32);\n" - "};\n", - 11 }, - - { "str_two_append", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []str = [];\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = \"fg\";\n" - "\tappend(xs, a);\n" - "\tappend(xs, b);\n" - "\treturn (xs[0].len * 10 + xs[1].len): i32;\n" - "};\n", - 32 }, - - { "slice_elem", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet hb: [4]u8; hb[0] = 9u8; hb[1] = 4u8;\n" - "\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 4;\n" - "\tlet ys: [][]u8 = [];\n" - "\tappend(ys, a);\n" - "\treturn (ys[0].len: i32) + (ys[0][0]: i32);\n" - "};\n", - 12 }, - - { "struct_first8", - "package main;\n" - "type pt = struct { x: i32, y: i32, z: i64 };\n" - "export fn main() i32 = {\n" - "\tlet xs: []pt = [];\n" - "\tlet p: pt = pt { x = 3, y = 5, z = 9 };\n" - "\tappend(xs, p);\n" - "\treturn (xs[0].x + xs[0].y): i32;\n" - "};\n", - 8 }, - - { "struct_tail_word", - "package main;\n" - "type pt = struct { x: i32, y: i32, z: i64 };\n" - "export fn main() i32 = {\n" - "\tlet xs: []pt = [];\n" - "\tlet p: pt = pt { x = 3, y = 5, z = 9 };\n" - "\tappend(xs, p);\n" - "\treturn (xs[0].z - 1): i32;\n" - "};\n", - 8 }, - - { "struct_lit_two", - "package main;\n" - "type pt = struct { x: i32, y: i32, z: i64 };\n" - "export fn main() i32 = {\n" - "\tlet xs: []pt = [];\n" - "\tappend(xs, pt { x = 1, y = 2, z = 5 });\n" - "\tappend(xs, pt { x = 3, y = 4, z = 7 });\n" - "\treturn (xs[0].z + xs[1].z): i32 - xs[0].x - xs[1].y;\n" - "};\n", - 7 }, - - { "spread_str", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []str = [];\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = \"fg\";\n" - "\tappend(xs, a);\n" - "\tlet ys: []str = [];\n" - "\tappend(ys, xs...);\n" - "\tappend(ys, b);\n" - "\treturn (ys[0].len + ys[1].len + ys.len): i32;\n" - "};\n", - 7 }, - - { "spread_tagged", - "package main;\n" - "type cell = (i64 | bool);\n" - "export fn main() i32 = {\n" - "\tlet xs: []cell = [];\n" - "\tappend(xs, 40i64);\n" - "\tappend(xs, 2i64);\n" - "\tlet ys: []cell = [];\n" - "\tappend(ys, xs...);\n" - "\tlet r: i32 = 0;\n" - "\tmatch (ys[0]) {\n" - "\tcase let v: i64 => r += v: i32;\n" - "\tcase bool => r = 99;\n" - "\t};\n" - "\tmatch (ys[1]) {\n" - "\tcase let v: i64 => r += v: i32;\n" - "\tcase bool => r = 98;\n" - "\t};\n" - "\treturn r;\n" - "};\n", - 42 }, - - { "tagged_ident_src", - "package main;\n" - "type cell = (i64 | bool);\n" - "export fn main() i32 = {\n" - "\tlet xs: []cell = [];\n" - "\tlet c: cell = 40i64;\n" - "\tlet b: cell = true;\n" - "\tappend(xs, c);\n" - "\tappend(xs, b);\n" - "\tlet r: i32 = 0;\n" - "\tmatch (xs[0]) {\n" - "\tcase let v: i64 => r += v: i32;\n" - "\tcase bool => r = 99;\n" - "\t};\n" - "\tmatch (xs[1]) {\n" - "\tcase i64 => r = 98;\n" - "\tcase let w: bool => { if (w) { r += 2; }; };\n" - "\t};\n" - "\treturn r;\n" - "};\n", - 42 }, - - { "struct_eight_appends", - "package main;\n" - "type option = struct { flag: rune, value: str };\n" - "export fn main() i32 = {\n" - "\tlet opts: []option = [];\n" - "\tappend(opts, option { flag = 'a', value = \"v0\" });\n" - "\tappend(opts, option { flag = 'b', value = \"v1\" });\n" - "\tappend(opts, option { flag = 'c', value = \"v2\" });\n" - "\tappend(opts, option { flag = 'd', value = \"v3\" });\n" - "\tappend(opts, option { flag = 'e', value = \"v4\" });\n" - "\tappend(opts, option { flag = 'f', value = \"v5\" });\n" - "\tappend(opts, option { flag = 'g', value = \"v6\" });\n" - "\tappend(opts, option { flag = 'h', value = \"seventh\" });\n" - "\treturn (opts[7].value.len + opts.len): i32;\n" - "};\n", - 15 }, - - { "struct_call_loudstop", - "package main;\n" - "type pt = struct { x: i32, y: i32, z: i64 };\n" - "fn mk() pt = {\n" - "\treturn pt { x = 1, y = 2, z = 3 };\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet xs: []pt = [];\n" - "\tappend(xs, mk());\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL }, - - { "spread_call_loudstop", - "package main;\n" - "fn mks() []str = {\n" - "\tlet xs: []str = [];\n" - "\tlet a: str = \"abc\";\n" - "\tappend(xs, a);\n" - "\treturn xs;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet ys: []str = [];\n" - "\tappend(ys, mks()...);\n" - "\treturn ys[0].len: i32;\n" - "};\n", - BUILD_FAIL }, -}; - -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/apwe_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/apwe_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/apwe_%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) { - if (r->want != BUILD_FAIL) - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - runwait(rmcmd); - return -1; - } - - int got = runwait(outbin); - - runwait(rmcmd); - return got; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Subsumes the frame-size canary: a scratch-order or - * esz divergence shows up on the TEXT main,$N line or the MOVQ $N, SI - * line. Pre-fix the tagged/struct rows differed (wwstage SI=$8 vs $16); - * the str rows were byte-id-blind (both wrong identically), which the - * runtime rows above catch. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/apwe_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/apwe_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/apwe_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "append_wide_elem: 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++; - int bad = rows[i].want == BUILD_FAIL - ? (got != -1) : (got != rows[i].want); - if (bad) { - fprintf(stderr, - "append_wide_elem[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].want == BUILD_FAIL) - continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "append_wide_elem: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("append_wide_elem: %d fixtures passed\n", total); - return 0; -} diff --git a/test/wcc/804_delete_elem.c b/test/wcc/804_delete_elem.c deleted file mode 100644 index 297946bd..00000000 --- a/test/wcc/804_delete_elem.c +++ /dev/null @@ -1,482 +0,0 @@ -/* - * 804_delete_elem — cstage and wwstage agree, byte-for-byte and at - * runtime, that `delete(xs[i])` removes element i from a slice: the - * tail [i+1..len) shifts down one stride, len -= 1, cap unchanged - * (the delete-half of task #35; insert() stays deferred; regex - * fold-2b's delete_thread/failed-thread/PC-dedup consumers). - * - * Lowering (BOTH stages, converged byte-identical by construction — - * there was no pre-existing reference side): push i and &hdr, then an - * ascending word-copy loop moves element j+1 onto element j until - * j >= len-1, then hdr.len -= 1. The element move is a same-type - * whole-stride byte copy — src and dst are elements of the SAME - * slice, so no boxing/coercion exists for any element kind; the rows - * below prove that across scalar (8B), narrow (4B/1B — the MOVL/MOVB - * copy tails), str (24B header), struct (16B) and tagged (56B) esz. - * esz comes off the STAMPED base type (the #34/#48 discipline), so a - * named-alias element cannot mis-stride. - * - * Hare's delete also accepts the range form delete(xs[i..j]) - * (ref/harec/src/check.c:1981-2027 EXPR_SLICE) — implemented as the - * fold-5a prereq P2; covered by 809_delete_range. - * - * row | shape | want - * ----------------+----------------------------------------+------ - * i64_first | [7,11,13], delete(xs[0]) | 44 - * i64_middle | [7,11,13], delete(xs[1]) | 40 - * i64_last | [7,11,13], delete(xs[2]) (no copy, | 38 - * | loop body never runs) | - * i32_narrow | []i32 esz=4 — the MOVL copy tail | 229 - * u16_narrow | []u16 esz=2 — the MOVW copy tail (the | 26 - * | last otherwise-unexercised tail arm) | - * u8_narrow | []u8 esz=1 — the MOVB copy tail | 28 - * cap_unchanged | manual {ptr,len=3,cap=8} header over a | 91 - * | stack backing; delete middle; cap must | - * | still read 8, tail value intact | - * delete_to_empty | 1-element slice, delete(xs[0]), len=0 | 7 - * str_elem | []str esz=24 — 3-qword header moves | 234 - * | whole | - * tagged_56b | [](s6|bool) esz=56 — 7-qword raw move, | 231 - * | tag+payload survive; match head + | - * | raw-byte tail readback (#43 caveat) | - * regex_shape | delete((*threads)[i]) behind *[]thread | - * | — the fold-2b delete_thread shape | 24 - * | (deref-of-local base, struct element) | - * delete_in_loop | drain [1,2,3,4] to empty via repeated | 112 - * | delete(xs[0]) — len bookkeeping under | - * | iteration, base-4 order accumulation | - * reject_array | delete(t[0]) on [3]i64 — "delete must | BUILD_FAIL - * | operate on a slice" | - * reject_nonindex | delete(xs) — operand must be an index- | BUILD_FAIL - * | ing or slicing expression | - * reject_arity | delete(xs[0], xs[1]) | BUILD_FAIL - * - * BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring, - * both stages) — a build that fails for any other reason (parse error, - * crash) is a vacuous reject and fails the row. - * - * Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id, - * which subsumes the frame canary (TEXT main,$N) and the del_l/del_e - * label-counter symmetry. - */ -#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; -} - -/* want == BUILD_FAIL: the row must FAIL to build on both stages AND - * emit expect_err on stderr (the checker reject set — message text - * included — is part of the contract: rule 7, never a silent - * acceptance; without the message check a row would pass vacuously on - * any unrelated build failure). */ -#define BUILD_FAIL (-2147483647 - 1) - -struct row { - const char *label; - const char *src; - int want; - const char *expect_err; /* BUILD_FAIL rows: required stderr substring */ -}; - -static const struct row rows[] = { - { "i64_first", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tdelete(xs[0]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 44, NULL }, - - { "i64_middle", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tdelete(xs[1]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 40, NULL }, - - { "i64_last", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tdelete(xs[2]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 38, NULL }, - - { "i32_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i32 = [];\n" - "\tappend(xs, 4i32);\n" - "\tappend(xs, 9i32);\n" - "\tappend(xs, 2i32);\n" - "\tdelete(xs[0]);\n" - "\treturn xs[0] + xs[1]*10 + len(xs)*100;\n" - "};\n", - 229, NULL }, - - { "u16_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u16 = [];\n" - "\tappend(xs, 4u16);\n" - "\tappend(xs, 9u16);\n" - "\tappend(xs, 2u16);\n" - "\tdelete(xs[1]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 26, NULL }, - - { "u8_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tappend(xs, 9u8);\n" - "\tappend(xs, 3u8);\n" - "\tdelete(xs[1]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 28, NULL }, - - { "cap_unchanged", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet hb: [8]u8; hb[0] = 4u8; hb[1] = 6u8; hb[2] = 9u8;\n" - "\tlet xs: []u8; xs.ptr = &hb[0]; xs.len = 3; xs.cap = 8;\n" - "\tdelete(xs[1]);\n" - "\treturn (xs.cap*10 + xs.len): i32 + xs[1]: i32;\n" - "};\n", - 91, NULL }, - - { "delete_to_empty", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tdelete(xs[0]);\n" - "\treturn len(xs) + 7;\n" - "};\n", - 7, NULL }, - - { "str_elem", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []str = [];\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = \"de\";\n" - "\tlet g: str = \"fghi\";\n" - "\tappend(xs, a);\n" - "\tappend(xs, b);\n" - "\tappend(xs, g);\n" - "\tdelete(xs[1]);\n" - "\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n" - "};\n", - 234, NULL }, - - /* 56B element: tag qword + 48B struct payload — seven whole-qword - * moves; tag AND payload must both survive the raw move (a tagged - * element at rest is just bytes; no boxing on a same-slice move). - * Readback is split: match proves tag + head (s.a), and the tail - * qword (f, element byte 48 -> absolute byte 104 of element 1) is - * read RAW via a *u8 over xs.ptr — the indexed-match payload - * cursor itself truncates past 32B (pre-existing, task #43), so a - * match on s.f would mis-fail regardless of the move. Verified - * truncation-free against a no-delete control. */ - { "tagged_56b", - "package main;\n" - "type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n" - "type cell = (s6 | bool);\n" - "export fn main() i32 = {\n" - "\tlet xs: []cell = [];\n" - "\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n" - "\tlet p1: s6 = s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 };\n" - "\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n" - "\tappend(xs, p0);\n" - "\tappend(xs, p1);\n" - "\tappend(xs, p2);\n" - "\tdelete(xs[1]);\n" - "\tlet r: i32 = 0;\n" - "\tmatch (xs[1]) {\n" - "\tcase let s: s6 => r = s.a: i32;\n" - "\tcase bool => r = 99;\n" - "\t};\n" - "\tlet bp: *u8 = xs.ptr: *u8;\n" - "\tr += bp[104]: i32;\n" - "\treturn r + len(xs)*100;\n" - "};\n", - 231, NULL }, - - /* The fold-2b consumer shape: regex.ha:547-551 delete_thread takes - * threads: *[]thread and deletes through the pointer. ww spells the - * autoderef explicitly: delete((*threads)[i]). */ - { "regex_shape", - "package main;\n" - "type thread = struct { pc: i64, matched: bool };\n" - "fn delete_thread(i: i64, threads: *[]thread) void = {\n" - "\tdelete((*threads)[i]);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet xs: []thread = [];\n" - "\tappend(xs, thread { pc = 1, matched = false });\n" - "\tappend(xs, thread { pc = 2, matched = false });\n" - "\tappend(xs, thread { pc = 3, matched = false });\n" - "\tdelete_thread(1, &xs);\n" - "\tlet r: i32 = (xs[0].pc + xs[1].pc): i32;\n" - "\treturn r + len(xs)*10;\n" - "};\n", - 24, NULL }, - - /* Pins the len bookkeeping under iteration: each pass reads the - * new head then deletes it; base-4 positional accumulation makes - * any wrong order, double-shift, or stale len visible (a stuck - * len would never terminate; the row would time out as a wrong - * exit via the harness). 1,2,3,4 -> ((1*4+2)*4+3)*4+4 = 112. */ - { "delete_in_loop", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tappend(xs, 3);\n" - "\tappend(xs, 4);\n" - "\tlet acc: i64 = 0;\n" - "\tfor (len(xs) > 0) {\n" - "\t\tacc = acc*4 + xs[0];\n" - "\t\tdelete(xs[0]);\n" - "\t};\n" - "\treturn acc: i32 + len(xs)*1000;\n" - "};\n", - 112, NULL }, - - { "reject_array", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet t: [3]i64 = [1, 2, 3];\n" - "\tdelete(t[0]);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "delete must operate on a slice" }, - - { "reject_nonindex", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tdelete(xs);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "delete: operand must be an indexing or slicing expression" }, - - { "reject_arity", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tappend(xs, 6u8);\n" - "\tdelete(xs[0], xs[1]);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "delete: takes exactly one argument" }, -}; - -/* errlog_has — the build-failure stderr must carry the row's expected - * diagnostic; any other failure (parse error, crash) is a vacuous - * reject and must not pass. */ -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; -} - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char tmpdir[64], src[128], outbin[128], errlog[160], rmcmd[160]; - char cmd[1200]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/dele_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/dele_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dele_%d_%d", tmpdir, getpid(), i); - snprintf(errlog, sizeof errlog, "%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, "%s build -o %s %s 2>%s", - driver, outbin, src, errlog); - if (runwait(cmd) != 0) { - int rc = -1; - if (r->want != BUILD_FAIL) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - } else if (r->expect_err && - !errlog_has(errlog, r->expect_err)) { - fprintf(stderr, "row[%s]: %s build failed without " - "expected diagnostic \"%s\"\n", - r->label, driver, r->expect_err); - rc = -3; /* failed, but for the wrong reason */ - } - runwait(rmcmd); - return rc; - } - - int got = runwait(outbin); - - runwait(rmcmd); - return got; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Both delete lowerings are written fresh, so this is - * the converged-by-construction gate: any drift in the shift loop, the - * del_l/del_e label sequence, or the esz word-copy shows here. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dele_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/dele_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/dele_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "delete_elem: 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++; - int bad = rows[i].want == BUILD_FAIL - ? (got != -1) : (got != rows[i].want); - if (bad) { - fprintf(stderr, - "delete_elem[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].want == BUILD_FAIL) - continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "delete_elem: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("delete_elem: %d fixtures passed\n", total); - return 0; -} diff --git a/test/wcc/806_tryprop_multisuccess.c b/test/wcc/806_tryprop_multisuccess.c deleted file mode 100644 index bd5a747f..00000000 --- a/test/wcc/806_tryprop_multisuccess.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * 806_tryprop_multisuccess — the `?` operator's interim single-success - * gate (F8, task #5) and the direct try-form is/match reject parity - * (F9, task #12). - * - * F8: ww's `?` assumes ONE success member end-to-end — the checker - * collapses the result to the first non-error variant and cgen emits a - * single tag compare — so `f()?` over (A|B|err) silently PROPAGATED - * the other success member to the caller as if it were an error - * (scratch/fold2b_probes/p11h, exit 21; graduated below as - * reject_success2). Until the honest subset-union result typing lands - * (task #14, harec check.c:2759-2835), BOTH stages loud-reject - * |success| > 1 at the checker; (T|err1|err2) — one success, many - * errors — stays legal (runtime canary: 925_tryprop_tag_remap_run). - * - * F9: cstage types `f()?` as the success variant, so the direct forms - * `f()? is T` / `match (f()?)` hit its non-tagged is/match gates and - * reject — while wwstage's scruttype (IDENT/DOT-only) silently - * ACCEPTED the same files (cs≠ww, gate-blind). Align-richer-DOWN: - * wwstage gains the same verdicts (text differs per per-stage diag - * conventions, asserted exactly per stage below). A success variant - * that is itself a named tagged union must KEEP being accepted - * (accept_nested_tagged — guards the mirror against over-rejecting). - * - * row | shape | want - * ----------------------+--------------------------------------+------ - * reject_success2 | (void|[]capture|nomem)? — p11h | BUILD_FAIL - * reject_success3 | (i32|bool|u64|nomem)? | BUILD_FAIL - * reject_try_is | f()? is i32, success = i32 | BUILD_FAIL - * reject_try_match | match (f()?), success = i32 | BUILD_FAIL - * reject_unw_success2 | (void|[]capture|nomem)! — `!` is the | BUILD_FAIL - * | same class (rob ruling, #133) | - * reject_tryunw_is | f()! is i32 — kind-agnostic sibling | BUILD_FAIL - * reject_callarg | g(f()?) — gate in call-arg position | BUILD_FAIL - * accept_two_member | (i32|nomem)? unwrap runtime | 0 - * accept_unw_multi_error| (i32|e1|e2)! unwrap runtime | 0 - * accept_void_success | (void|nomem)? statement runtime | 0 - * accept_nested_tagged | (ab|nomem)? is i32, ab = (i32|bool) | 0 - * - * BUILD_FAIL rows assert the diagnostic TEXT per stage (a build that - * fails for any other reason is a vacuous reject and fails the row). - * Accept rows also assert cstage/wwstage asm byte-id. - */ -#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; -} - -/* want == BUILD_FAIL: the row must FAIL to build on both stages AND - * emit the per-stage expected diagnostic on stderr (rule 7 — never a - * silent acceptance). */ -#define BUILD_FAIL (-2147483647 - 1) - -struct row { - const char *label; - const char *src; - int want; - const char *expect_cs; /* BUILD_FAIL: required cstage stderr substring */ - const char *expect_ww; /* BUILD_FAIL: required wwstage stderr substring */ -}; - -#define MULTISUCC_DIAG \ - "?: multi-success union unwired (task #14): bind and match instead" - -static const struct row rows[] = { - /* p11h graduated: pre-gate this BUILT and exited 21 (the []capture - * success wrongly propagated and read back as nomem). */ - { "reject_success2", - "package main;\n" - "type capture = struct { content: str, start: size, end: size };\n" - "fn search2(k: i32) (void | []capture | nomem) = {\n" - "\tif (k == 0) { return; };\n" - "\tlet caps: []capture = [];\n" - "\tappend(caps, capture { content = \"xy\", start = 1, end = 3 });\n" - "\treturn caps;\n" - "};\n" - "fn tb(k: i32) (i32 | nomem) = {\n" - "\tlet r: (void | []capture) = search2(k)?;\n" - "\tif (r is []capture) { return 7; };\n" - "\treturn 8;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = tb(1);\n" - "\tif (x is nomem) { return 21; };\n" - "\tif (x is i32) { return 22; };\n" - "\treturn 23;\n" - "};\n", - BUILD_FAIL, MULTISUCC_DIAG, MULTISUCC_DIAG }, - - { "reject_success3", - "package main;\n" - "fn f(k: i32) (i32 | bool | u64 | nomem) = { return 7; };\n" - "fn g() (i32 | nomem) = {\n" - "\tlet v: i32 = f(1)?;\n" - "\treturn v;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = g();\n" - "\tif (x is i32) { return 0; };\n" - "\treturn 1;\n" - "};\n", - BUILD_FAIL, MULTISUCC_DIAG, MULTISUCC_DIAG }, - - /* F9 graduation rows: the ?-result is bare i32, so `is`/`match` - * over it must reject on BOTH stages (pre-fix wwstage accepted). */ - { "reject_try_is", - "package main;\n" - "fn f(k: i32) (i32 | nomem) = { return 7; };\n" - "fn g() (i32 | nomem) = {\n" - "\tif (f(1)? is i32) { return 1; };\n" - "\treturn 0;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = g();\n" - "\tif (x is i32) { return 0; };\n" - "\treturn 1;\n" - "};\n", - BUILD_FAIL, - "is on non-tagged-union", - "is/as: operand is not a tagged union" }, - - { "reject_try_match", - "package main;\n" - "fn f(k: i32) (i32 | nomem) = { return 7; };\n" - "fn g() (i32 | nomem) = {\n" - "\tmatch (f(1)?) {\n" - "\tcase i32 => return 1;\n" - "\t};\n" - "\treturn 0;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = g();\n" - "\tif (x is i32) { return 0; };\n" - "\treturn 1;\n" - "};\n", - BUILD_FAIL, - "match on non-tagged-union", - "match on non-tagged-union try-result" }, - - /* `!` shares the single-success collapse — one class, both ops - * (rob ruling, #133 precedent): pre-gate this BUILT on BOTH stages - * and silently aborted-on-success at runtime (probe q_card2_unw, - * graduated). */ - { "reject_unw_success2", - "package main;\n" - "type capture = struct { content: str, start: size, end: size };\n" - "fn search2(k: i32) (void | []capture | nomem) = {\n" - "\tif (k == 0) { return; };\n" - "\tlet caps: []capture = [];\n" - "\tappend(caps, capture { content = \"xy\", start = 1, end = 3 });\n" - "\treturn caps;\n" - "};\n" - "fn tb(k: i32) i32 = {\n" - "\tlet r: (void | []capture) = search2(k)!;\n" - "\tif (r is []capture) { return 7; };\n" - "\treturn 8;\n" - "};\n" - "export fn main() i32 = { return tb(1); };\n", - BUILD_FAIL, - "!: multi-success union unwired (task #14): bind and match instead", - "!: multi-success union unwired (task #14): bind and match instead" }, - - /* cstage's reject derives from the typed result, not the operator - * kind — `!` collapses identically, so its direct form must match - * verdicts too (pre-fix wwstage built this and misbehaved). */ - { "reject_tryunw_is", - "package main;\n" - "fn f(k: i32) (i32 | nomem) = { return 7; };\n" - "fn g() i32 = {\n" - "\tif (f(1)! is i32) { return 1; };\n" - "\treturn 0;\n" - "};\n" - "export fn main() i32 = { return g() - 1; };\n", - BUILD_FAIL, - "is on non-tagged-union", - "is/as: operand is not a tagged union" }, - - /* `?` nested in a CALL-ARG: the gate keys on the operand's type at - * the checker expression walk, so position must not matter — guards - * against a walker path that types call args without visiting the - * try node. */ - { "reject_callarg", - "package main;\n" - "fn f(k: i32) (i32 | bool | nomem) = { return 7; };\n" - "fn g(v: i32) i32 = { return v; };\n" - "fn h() (i32 | nomem) = {\n" - "\treturn g(f(1)?);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = h();\n" - "\tif (x is i32) { return 0; };\n" - "\treturn 1;\n" - "};\n", - BUILD_FAIL, MULTISUCC_DIAG, MULTISUCC_DIAG }, - - /* |success| == 1: the gate must not fire and the unwrap must keep - * working at runtime. The 3-member single-success shape - * (i32|e1|e2) is pinned by 925_tryprop_tag_remap_run. */ - { "accept_two_member", - "package main;\n" - "fn f(k: i32) (i32 | nomem) = { return 7; };\n" - "fn g() (i32 | nomem) = {\n" - "\tlet v: i32 = f(1)?;\n" - "\treturn v + 1;\n" - "};\n" - "export fn main() i32 = {\n" - "\tmatch (g()) {\n" - "\tcase let v: i32 => return v - 8;\n" - "\tcase nomem => return 2;\n" - "\t};\n" - "\treturn 3;\n" - "};\n", - 0, NULL, NULL }, - - /* |success| == 1 with MULTIPLE errors under `!`: the gate must not - * fire and the success unwrap must keep working at runtime (the - * `!` twin of 925's ? canary shape). */ - { "accept_unw_multi_error", - "package main;\n" - "type e1 = !void;\n" - "type e2 = !void;\n" - "fn g(which: i32) (i32 | e1 | e2) = {\n" - "\tif (which == 1) { return e1{}; };\n" - "\tif (which == 2) { return e2{}; };\n" - "\treturn 100;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet v: i32 = g(0)!;\n" - "\treturn v - 100;\n" - "};\n", - 0, NULL, NULL }, - - /* VOID success: (void|nomem)? as an expression-statement is the - * dominant lib/ shape (io writes etc.) — void counts as the one - * success member, nsucc == 1, the gate must not fire. */ - { "accept_void_success", - "package main;\n" - "fn f(k: i32) (void | nomem) = {\n" - "\treturn;\n" - "};\n" - "fn g() (i32 | nomem) = {\n" - "\tf(1)?;\n" - "\treturn 5;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = g();\n" - "\tmatch (x) {\n" - "\tcase let v: i32 => return v - 5;\n" - "\tcase nomem => return 2;\n" - "\t};\n" - "\treturn 3;\n" - "};\n", - 0, NULL, NULL }, - - /* Success variant is itself a NAMED tagged union: `f()? is i32` - * is `is` over (i32|bool) — tagged — and cstage ACCEPTS. The - * wwstage F9 mirror must key on the resolved success type, not - * on the try syntax, or this over-rejects. */ - { "accept_nested_tagged", - "package main;\n" - "type ab = (i32 | bool);\n" - "fn f(k: i32) (ab | nomem) = {\n" - "\tlet v: ab = 7;\n" - "\treturn v;\n" - "};\n" - "fn g() (i32 | nomem) = {\n" - "\tif (f(1)? is i32) { return 0; };\n" - "\treturn 1;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: (i32 | nomem) = g();\n" - "\tmatch (x) {\n" - "\tcase let v: i32 => return v;\n" - "\tcase nomem => return 2;\n" - "\t};\n" - "\treturn 3;\n" - "};\n", - 0, NULL, NULL }, -}; - -/* errlog_has — the build-failure stderr must carry the row's expected - * diagnostic; any other failure (parse error, crash) is a vacuous - * reject and must not pass. */ -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; -} - -static int -run_driver(const char *driver, const char *expect, const struct row *r, int i) -{ - char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160], cmd[1200]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/tpms_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/tpms_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/tpms_%d_%d", tmpdir, getpid(), i); - snprintf(errlog, sizeof errlog, "%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, "%s build -o %s %s 2>%s", - driver, outbin, src, errlog); - if (runwait(cmd) != 0) { - int rc = -1; - if (r->want != BUILD_FAIL) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - } else if (expect && !errlog_has(errlog, expect)) { - fprintf(stderr, "row[%s]: %s build failed without " - "expected diagnostic \"%s\"\n", - r->label, driver, expect); - rc = -3; /* failed, but for the wrong reason */ - } - runwait(rmcmd); - return rc; - } - - int got = runwait(outbin); - - runwait(rmcmd); - return got; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff (rule 10 on the accept rows). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/tpms_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/tpms_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/tpms_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - char wdrv[2120]; - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - struct { - const char *name; - const char *path; - int is_ww; - int gated_on_existence; - } drivers[] = { - { "cstage", cdrv, 0, 0 }, - { "wwstage", wdrv, 1, 1 }, - { NULL, NULL, 0, 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, - "tryprop_multisuccess: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < n; i++) { - const char *expect = drivers[d].is_ww - ? rows[i].expect_ww : rows[i].expect_cs; - int got = run_driver(drivers[d].path, expect, - &rows[i], i); - total++; - int bad = rows[i].want == BUILD_FAIL - ? (got != -1) : (got != rows[i].want); - if (bad) { - fprintf(stderr, - "tryprop_multisuccess[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].want == BUILD_FAIL) - continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "tryprop_multisuccess: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("tryprop_multisuccess: %d fixtures passed\n", total); - return 0; -} diff --git a/test/wcc/807_insert_elem.c b/test/wcc/807_insert_elem.c deleted file mode 100644 index cd4713a8..00000000 --- a/test/wcc/807_insert_elem.c +++ /dev/null @@ -1,844 +0,0 @@ -/* - * 807_insert_elem — cstage and wwstage agree, byte-for-byte and at - * runtime, that `insert(xs[idx], v)` inserts v BEFORE idx: len += 1, - * the tail [idx..oldlen) shifts up one stride, v lands at idx - * (the insert-half of task #35, delete()'s twin; regex fold-3's - * `|`/`?`/`*` compile-arm consumers, regex.ha:347/419/441). - * - * Lowering (BOTH stages, converged byte-identical by construction) - * is a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure) - * and the whole #34 value-store dispatch (scalar / str-slice header / - * tagged widen / struct fill) verbatim, one boxing choke-point — - * lands v at slot len-1; then a rotate-right of [idx, len) moves it - * home through an esz frame scratch (@insscr). The rotate is - * delete's shift loop in reverse (descending j, the safe memmove-up - * direction) and is a same-slice whole-stride raw byte move — no - * boxing exists for any element kind. idx evaluates BEFORE the grow - * (Hare's left-to-right operand order — the pregrow_len_idx row pins - * insert(xs[len(xs)-1], v) reading the pre-grow len; an end-insert - * via len(xs) cannot discriminate, see the end_insert_len row). - * - * idx == len is a legal end-insert per harec's shared append/insert - * checker arm (ref/harec/src/check.c:745, "insert" at :786; the - * ref/hare os/exec/platform_cmd.ha:86 `insert(cmd.env[len(...)...]` - * idiom) — pinned by the i64_end / end_insert_len rows. - * - * row | shape | want - * ----------------+----------------------------------------+------ - * i64_front | [7,11,13], insert(xs[0], 5) | 115 - * i64_middle | [7,11,13], insert(xs[1], 5) | 97 - * i64_end | [7,11,13], insert(xs[3], 5) (idx==len, | 157 - * | rotate loop never runs) | - * end_insert_len | insert(xs[len(xs)], v) — idx==len | 13 - * | spelled through a dynamic len read | - * pregrow_len_idx | insert(xs[len(xs)-1], v) — the eval- | 48 - * | order pin: pre-grow idx=1 -> [7,13,11],| - * | post-grow idx=2 -> [7,11,13] | - * i32_narrow | []i32 esz=4 — the MOVL copy tail | 42 - * u16_narrow | []u16 esz=2 — the MOVW copy tail | 43 - * u8_narrow | []u8 esz=1 — the MOVB copy tail | 44 - * empty_insert | insert(xs[0], v) on an empty slice | 15 - * | (grow 0->1; rotate degenerates) | - * str_elem | []str esz=24 — 3-qword headers move | 45 - * | whole | - * struct_elem | []p2t esz=16 — struct body insert + | 46 - * | survivor shift | - * tagged_56b | [](s6|bool) esz=56, value from a TYPED | - * | LOCAL (the regex newinst shape, | - * | ha:347); tag+payload survive the | 217 - * | rotate; match head + raw tail byte | - * tagged_cast | insert(insts[k], (7: inst_split)) — | - * | CAST-rvalue value boxed by append's | 27 - * | widen choke-point (ha:419/441 shape) | - * regex_shape | insert((*p)[i], v) behind *[]i64 — | - * | deref-of-local base, delete's | 171 - * | regex_shape twin | - * insert_in_loop | front-insert 1,2,3,4 -> [4,3,2,1] — | 47 - * | len bookkeeping under iteration + | - * | per-position checks | - * tagged_pregrow_val | #50 value eval-order pin, tagged | 50 - * | dst: v = (xs.len: size)+2 reads the | - * | PRE-grow len (post-grow boxing read 5) | - * scalar_pregrow_val | #50 scalar control: value-first | 55 - * | order was already Hare-correct | - * tagged_selfref_val | #50: v = xs[1], an element of the | 51 - * | dst — pre-grow boxing reads OLD base | - * tagged_str_payload | #50: str-variant box runs pre-grow | 52 - * tagged_regex_minrep | #50: the regex {,0} fold-5b shape, | 53 - * | len-reading value cast to a named | - * | variant alias (regex.ww:643-650) | - * tagged_realloc_selfref_loop | #50 ken k50a: 30-append | 56 - * | self-ref loop, value reads xs[0] across| - * | actual rt_ensure base moves | - * tagged_append_pregrow_val | #50 direct append() pin (the | 54 - * | fix site; insert inherits via desugar) | - * tagged_seq_positions | #50 ken k50b: sequenced inserts at | 57 - * | 0 then mid, both len-reading values, | - * | full final-order check [200,10,4,20] | - * tagged_void_variant | #50 ken k50c: void box (tag-only) | 58 - * | through @apptagscr, then a len-reading | - * | size insert over the mixed slice | - * - * Wants stay under 256 (the exit-status byte); the if-ladder rows - * return a distinct small failure code per check, so a wrong element - * pinpoints itself. - * reject_array | insert(t[0], v) on [3]i64 | BUILD_FAIL - * reject_nonindex | insert(xs, v) | BUILD_FAIL - * reject_range | insert(xs[0:1], v) — not Hare (harec | BUILD_FAIL - * | only parses an index place) | - * reject_arity1 | insert(xs[0]) | BUILD_FAIL - * reject_arity3 | insert(xs[0], a, b) — also covers the | BUILD_FAIL - * | harec with-length form (#35) | - * reject_spread | insert(xs[0], vs...) — multi form | BUILD_FAIL - * | deferred, message cites task #35 | - * - * BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring, - * both stages) — a build that fails for any other reason (parse error, - * crash) is a vacuous reject and fails the row. - * - * Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id, - * which subsumes the frame canary (TEXT main,$N — @insscr sizing) and - * the ins_l/ins_e label-counter symmetry. - */ -#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; -} - -/* want == BUILD_FAIL: the row must FAIL to build on both stages AND - * emit expect_err on stderr (the checker reject set — message text - * included, esp. the #35 cite on the deferred spread form — is part - * of the contract: rule 7, never a silent acceptance; without the - * message check a row would pass vacuously on any unrelated build - * failure). */ -#define BUILD_FAIL (-2147483647 - 1) - -struct row { - const char *label; - const char *src; - int want; - const char *expect_err; /* BUILD_FAIL rows: required stderr substring */ -}; - -static const struct row rows[] = { - { "i64_front", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tinsert(xs[0], 5);\n" - "\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n" - "};\n", - 115, NULL }, - - { "i64_middle", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tinsert(xs[1], 5);\n" - "\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n" - "};\n", - 97, NULL }, - - { "i64_end", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tinsert(xs[3], 5);\n" - "\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n" - "};\n", - 157, NULL }, - - /* idx == len spelled THROUGH a dynamic len(xs) read (the - * ref/hare os/exec idiom). NOTE: this row does NOT discriminate - * idx eval order — under the desugar, post-grow idx=newlen - * degenerates the rotate and lands v at the end too; the - * pregrow_len_idx row below is the eval-order pin. */ - { "end_insert_len", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tinsert(xs[len(xs)], 13);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\treturn xs[2]: i32;\n" - "};\n", - 13, NULL }, - - /* THE pre-grow eval-order pin (Hare's left-to-right operand - * order): idx = len(xs)-1 reads the PRE-grow len -> idx=1 -> - * [7,13,11]; a post-grow evaluation would compute idx=2 and - * produce a plain end-insert [7,11,13] — every position is - * checked, so the orders are distinguishable. */ - { "pregrow_len_idx", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tinsert(xs[len(xs) - 1], 13);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\tif (xs[0] != 7) { return 2; };\n" - "\tif (xs[1] != 13) { return 3; };\n" - "\tif (xs[2] != 11) { return 4; };\n" - "\treturn 48;\n" - "};\n", - 48, NULL }, - - { "i32_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i32 = [];\n" - "\tappend(xs, 4i32);\n" - "\tappend(xs, 2i32);\n" - "\tinsert(xs[1], 9i32);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\tif (xs[0] != 4i32) { return 2; };\n" - "\tif (xs[1] != 9i32) { return 3; };\n" - "\tif (xs[2] != 2i32) { return 4; };\n" - "\treturn 42;\n" - "};\n", - 42, NULL }, - - { "u16_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u16 = [];\n" - "\tappend(xs, 4u16);\n" - "\tappend(xs, 2u16);\n" - "\tinsert(xs[1], 9u16);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\tif (xs[0] != 4u16) { return 2; };\n" - "\tif (xs[1] != 9u16) { return 3; };\n" - "\tif (xs[2] != 2u16) { return 4; };\n" - "\treturn 43;\n" - "};\n", - 43, NULL }, - - { "u8_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tappend(xs, 3u8);\n" - "\tinsert(xs[0], 2u8);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\tif (xs[0] != 2u8) { return 2; };\n" - "\tif (xs[1] != 5u8) { return 3; };\n" - "\tif (xs[2] != 3u8) { return 4; };\n" - "\treturn 44;\n" - "};\n", - 44, NULL }, - - { "empty_insert", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tinsert(xs[0], 5);\n" - "\treturn xs[0]: i32 + len(xs)*10;\n" - "};\n", - 15, NULL }, - - { "str_elem", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []str = [];\n" - "\tlet a: str = \"abc\";\n" - "\tlet g: str = \"fghi\";\n" - "\tappend(xs, a);\n" - "\tappend(xs, g);\n" - "\tlet b: str = \"de\";\n" - "\tinsert(xs[1], b);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\tif (xs[0].len != 3) { return 2; };\n" - "\tif (xs[1].len != 2) { return 3; };\n" - "\tif (xs[2].len != 4) { return 4; };\n" - "\treturn 45;\n" - "};\n", - 45, NULL }, - - { "struct_elem", - "package main;\n" - "type p2t = struct { x: i64, y: i64 };\n" - "export fn main() i32 = {\n" - "\tlet xs: []p2t = [];\n" - "\tappend(xs, p2t { x = 1, y = 2 });\n" - "\tappend(xs, p2t { x = 5, y = 6 });\n" - "\tinsert(xs[1], p2t { x = 3, y = 4 });\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\tif (xs[0].x != 1 || xs[0].y != 2) { return 2; };\n" - "\tif (xs[1].x != 3 || xs[1].y != 4) { return 3; };\n" - "\tif (xs[2].x != 5 || xs[2].y != 6) { return 4; };\n" - "\treturn 46;\n" - "};\n", - 46, NULL }, - - /* 56B element from a TYPED LOCAL — the regex fold-3 newinst - * shape (regex.ha:347: `insert(insts[split_idx], newinst)`). - * Tag qword + 48B payload: seven whole-qword moves through - * @insscr; tag AND payload must survive both the append store - * and the rotate. Readback is split like 804's tagged_56b row: - * match proves tag + head (s.a), and the tail qword of the - * SHIFTED element (f = 18, element 2 byte 48 -> absolute byte - * 160) is read RAW via a *u8 over xs.ptr — the indexed-match - * payload cursor truncates past 32B (pre-existing, task #43). */ - { "tagged_56b", - "package main;\n" - "type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n" - "type cell = (s6 | bool);\n" - "export fn main() i32 = {\n" - "\tlet xs: []cell = [];\n" - "\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n" - "\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n" - "\tappend(xs, p0);\n" - "\tappend(xs, p2);\n" - "\tlet p1: cell = (s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 });\n" - "\tinsert(xs[1], p1);\n" - "\tlet r: i32 = 0;\n" - "\tmatch (xs[1]) {\n" - "\tcase let s: s6 => r = s.a: i32;\n" - "\tcase bool => r = 99;\n" - "\t};\n" - "\tlet bp: *u8 = xs.ptr: *u8;\n" - "\tr += (bp[160]: i32) * 10;\n" - "\treturn r + len(xs)*10;\n" - "};\n", - 217, NULL }, - - /* CAST-rvalue value into a tagged slice — the regex ha:419/441 - * shape `insert(insts[term_start_idx], after_idx: inst_split)`; - * append's widen choke-point boxes it PRE-grow (#50). */ - { "tagged_cast", - "package main;\n" - "type inst_lit = rune;\n" - "type inst_split = i64;\n" - "type inst = (inst_lit | inst_split);\n" - "export fn main() i32 = {\n" - "\tlet insts: []inst = [];\n" - "\tappend(insts, ('a': inst_lit));\n" - "\tappend(insts, ('b': inst_lit));\n" - "\tinsert(insts[1], (7: inst_split));\n" - "\tif (len(insts) != 3) { return 1; };\n" - "\tlet r: i32 = 0;\n" - "\tmatch (insts[1]) {\n" - "\tcase let z: inst_split => r += (z: i32);\n" - "\tcase => return 2;\n" - "\t};\n" - "\tmatch (insts[2]) {\n" - "\tcase let l: inst_lit => { if ((l: rune) == 'b') { r += 20; }; };\n" - "\tcase => return 3;\n" - "\t};\n" - "\treturn r;\n" - "};\n", - 27, NULL }, - - /* The deref-of-local base — delete's regex_shape twin: the - * header lives behind a *[]i64 param. */ - { "regex_shape", - "package main;\n" - "fn ins_at(i: i64, p: *[]i64, v: i64) void = {\n" - "\tinsert((*p)[i], v);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 3);\n" - "\tins_at(1, &xs, 2);\n" - "\tif (len(xs) != 3) { return 1; };\n" - "\treturn (xs[0] + xs[1]*10 + xs[2]*50): i32;\n" - "};\n", - 171, NULL }, - - /* Pins len bookkeeping under iteration: each front-insert must - * see the grown len and shift the whole accumulated tail; - * per-position checks of [4,3,2,1] make any wrong order, missed - * shift, or stale len visible. */ - { "insert_in_loop", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tlet v: i64 = 1;\n" - "\tfor (v <= 4) {\n" - "\t\tinsert(xs[0], v);\n" - "\t\tv += 1;\n" - "\t};\n" - "\tif (len(xs) != 4) { return 1; };\n" - "\tlet k: i32 = 0;\n" - "\tfor (k < 4) {\n" - "\t\tif (xs[k] != (4 - k): i64) { return 2 + k; };\n" - "\t\tk += 1;\n" - "\t};\n" - "\treturn 47;\n" - "};\n", - 47, NULL }, - - /* #50 THE value eval-order pin for the TAGGED-dst arm (ken's - * f50v4_tagged shape exact): v = (xs.len: size)+2 widens into - * the box. Pre-grow len=2 -> v=4; the pre-#50 post-grow boxing - * read len=3 -> v=5 (the exit-15 path). The scalar arm always - * ordered value-first (scalar_pregrow_val below); #50 aligns - * the boxing arm to it. */ - { "tagged_pregrow_val", - "package main;\n" - "type un = (size | void);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 10: size);\n" - "\tappend(xs, 20: size);\n" - "\tinsert(xs[1], ((xs.len: size) + 2));\n" - "\tmatch (xs[1]) {\n" - "\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n" - "\tcase void => { return 2; };\n" - "\t};\n" - "\tif (len(xs) != 3) { return 3; };\n" - "\treturn 50;\n" - "};\n", - 50, NULL }, - - /* #50 scalar no-regress control (ken's f50_insertorder): the - * scalar-dst arm was Hare-correct pre-#50 — value reads the - * pre-grow len. */ - { "scalar_pregrow_val", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []size = [];\n" - "\tappend(xs, 10: size);\n" - "\tappend(xs, 20: size);\n" - "\tinsert(xs[1], len(xs): size);\n" - "\tif (xs[1] != 2) { return 1; };\n" - "\tif (len(xs) != 3) { return 2; };\n" - "\treturn 55;\n" - "};\n", - 55, NULL }, - - /* #50 SELF-REFERENCE value: v is an element of the destination - * itself — pre-grow boxing must read the OLD base (and survive - * a rt_ensure realloc via the frame scratch): [10,20] -> - * insert(xs[0], xs[1]) -> [20,10,20]. */ - { "tagged_selfref_val", - "package main;\n" - "type un = (size | void);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 10: size);\n" - "\tappend(xs, 20: size);\n" - "\tinsert(xs[0], xs[1]);\n" - "\tmatch (xs[0]) {\n" - "\tcase let v: size => { if (v != 20) { return 1; }; };\n" - "\tcase void => { return 2; };\n" - "\t};\n" - "\tmatch (xs[1]) {\n" - "\tcase let v: size => { if (v != 10) { return 3; }; };\n" - "\tcase void => { return 4; };\n" - "\t};\n" - "\tmatch (xs[2]) {\n" - "\tcase let v: size => { if (v != 20) { return 5; }; };\n" - "\tcase void => { return 6; };\n" - "\t};\n" - "\tif (len(xs) != 3) { return 7; };\n" - "\treturn 51;\n" - "};\n", - 51, NULL }, - - /* #50 str-payload tagged box: the widen store's str branch - * (AX=ptr BX=len) also runs pre-grow now — header lands - * whole. */ - { "tagged_str_payload", - "package main;\n" - "type su = (str | void);\n" - "export fn main() i32 = {\n" - "\tlet ss: []su = [];\n" - "\tappend(ss, \"aa\");\n" - "\tinsert(ss[0], \"bb\");\n" - "\tmatch (ss[0]) {\n" - "\tcase let s: str => {\n" - "\t\tif (s.len != 2) { return 1; };\n" - "\t\tif (s[0] != 'b') { return 2; };\n" - "\t};\n" - "\tcase void => { return 3; };\n" - "\t};\n" - "\tmatch (ss[1]) {\n" - "\tcase let s: str => { if (s[0] != 'a') { return 4; }; };\n" - "\tcase void => { return 5; };\n" - "\t};\n" - "\treturn 52;\n" - "};\n", - 52, NULL }, - - /* #50 the regex {,0}-class shape standalone (regex.ww:643-650, - * the fold-5b consumer reviewer-5b's mutant killed): a - * len-reading value CAST to a named variant alias — - * `((insts.len: size) + 2): inst_split`. Pre-grow len=2 -> 4. */ - { "tagged_regex_minrep", - "package main;\n" - "type ispl = size;\n" - "type un = (ispl | void);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 7: ispl);\n" - "\tappend(xs, 8: ispl);\n" - "\tinsert(xs[0], ((xs.len: size) + 2): ispl);\n" - "\tmatch (xs[0]) {\n" - "\tcase let v: ispl => { if (v != 4) { return (v: i32) + 10; }; };\n" - "\tcase void => { return 2; };\n" - "\t};\n" - "\tif (len(xs) != 3) { return 3; };\n" - "\treturn 53;\n" - "};\n", - 53, NULL }, - - /* #50 ken's k50a: 30 appends force repeated rt_ensure realloc; - * every value reads xs[0] (as-unwrap + arithmetic) off the - * potentially-moved base — the one shape that pins the OLD-base - * read across an ACTUAL base move, which single-grow rows - * can't. */ - { "tagged_realloc_selfref_loop", - "package main;\n" - "type un = (void | size);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 100: size);\n" - "\tlet i: size = 0;\n" - "\tfor (i < 30) {\n" - "\t\tappend(xs, ((xs[0] as size) + i));\n" - "\t\ti += 1;\n" - "\t};\n" - "\tif (len(xs) != 31) { return 1; };\n" - "\tlet k: size = 1;\n" - "\tfor (k < 31) {\n" - "\t\tmatch (xs[k]) {\n" - "\t\tcase let v: size => { if (v != 100 + (k - 1)) { return 2; }; };\n" - "\t\tcase void => { return 3; };\n" - "\t\t};\n" - "\t\tk += 1;\n" - "\t};\n" - "\treturn 56;\n" - "};\n", - 56, NULL }, - - /* #50 direct append() (no insert desugar) — the fix lives in - * append's tagged arm, so pin it without the rotate. */ - { "tagged_append_pregrow_val", - "package main;\n" - "type un = (size | void);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 10: size);\n" - "\tappend(xs, 20: size);\n" - "\tappend(xs, (xs.len: size) + 2);\n" - "\tmatch (xs[2]) {\n" - "\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n" - "\tcase void => { return 2; };\n" - "\t};\n" - "\tif (len(xs) != 3) { return 3; };\n" - "\treturn 54;\n" - "};\n", - 54, NULL }, - - /* #50 ken's k50b: SEQUENCED inserts at position edges (0, then - * mid), each with a len-reading value — every boxing must see - * its own pre-grow len (2 -> 200, then 3 -> 4), and the second - * insert's rotate must shift the first's result correctly: - * [10,20] -> [200,10,20] -> [200,10,4,20]. */ - { "tagged_seq_positions", - "package main;\n" - "type un = (void | size);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 10: size);\n" - "\tappend(xs, 20: size);\n" - "\tinsert(xs[0], ((xs.len: size) * 100));\n" - "\tmatch (xs[0]) {\n" - "\tcase let v: size => { if (v != 200) { return 1; }; };\n" - "\tcase void => { return 2; };\n" - "\t};\n" - "\tinsert(xs[2], ((xs.len: size) + 1));\n" - "\tmatch (xs[2]) {\n" - "\tcase let v: size => { if (v != 4) { return 3; }; };\n" - "\tcase void => { return 4; };\n" - "\t};\n" - "\tmatch (xs[1]) { case let v: size => { if (v != 10) { return 5; }; }; case void => { return 6; }; };\n" - "\tmatch (xs[3]) { case let v: size => { if (v != 20) { return 7; }; }; case void => { return 8; }; };\n" - "\tif (len(xs) != 4) { return 9; };\n" - "\treturn 57;\n" - "};\n", - 57, NULL }, - - /* #50 ken's k50c: VOID-variant value — a tag-only box through - * the fresh @apptagscr (the zeroed scratch IS the payload), - * then a len-reading size insert over the mixed slice. */ - { "tagged_void_variant", - "package main;\n" - "type un = (void | size);\n" - "export fn main() i32 = {\n" - "\tlet xs: []un = [];\n" - "\tappend(xs, 5: size);\n" - "\tinsert(xs[0], void);\n" - "\tif (!(xs[0] is void)) { return 1; };\n" - "\tmatch (xs[1]) { case let v: size => { if (v != 5) { return 2; }; }; case void => { return 3; }; };\n" - "\tinsert(xs[1], ((xs.len: size)));\n" - "\tmatch (xs[1]) { case let v: size => { if (v != 2) { return 4; }; }; case void => { return 5; }; };\n" - "\tif (len(xs) != 3) { return 6; };\n" - "\treturn 58;\n" - "};\n", - 58, NULL }, - - { "reject_array", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet t: [3]i64 = [1, 2, 3];\n" - "\tinsert(t[0], 9);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "insert must operate on a slice" }, - - { "reject_nonindex", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tinsert(xs, 6u8);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "insert: operand must be an indexing expression" }, - - { "reject_range", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tappend(xs, 6u8);\n" - "\tinsert(xs[0:1], 7u8);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "insert: range place is invalid" }, - - { "reject_arity1", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tinsert(xs[0]);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "insert: takes exactly two arguments" }, - - { "reject_arity3", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tinsert(xs[0], 6u8, 7u8);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "insert: takes exactly two arguments" }, - - { "reject_spread", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tlet vs: []u8 = [];\n" - "\tappend(vs, 1u8);\n" - "\tinsert(xs[0], vs...);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "insert: spread form insert(xs[i], vs...) unimplemented (task #35)" }, -}; - -/* errlog_has — the build-failure stderr must carry the row's expected - * diagnostic; any other failure (parse error, crash) is a vacuous - * reject and must not pass. */ -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; -} - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160], cmd[1200]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/inse_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/inse_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/inse_%d_%d", tmpdir, getpid(), i); - snprintf(errlog, sizeof errlog, "%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, "%s build -o %s %s 2>%s", - driver, outbin, src, errlog); - if (runwait(cmd) != 0) { - int rc = -1; - if (r->want != BUILD_FAIL) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - } else if (r->expect_err && - !errlog_has(errlog, r->expect_err)) { - fprintf(stderr, "row[%s]: %s build failed without " - "expected diagnostic \"%s\"\n", - r->label, driver, r->expect_err); - rc = -3; /* failed, but for the wrong reason */ - } - runwait(rmcmd); - return rc; - } - - int got = runwait(outbin); - - runwait(rmcmd); - return got; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Both insert lowerings are written fresh, so this is - * the converged-by-construction gate: any drift in the rotate loop, - * the ins_l/ins_e label sequence, the @insscr frame slot, or the - * reused append body shows here. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/inse_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/inse_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/inse_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "insert_elem: 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++; - int bad = rows[i].want == BUILD_FAIL - ? (got != -1) : (got != rows[i].want); - if (bad) { - fprintf(stderr, - "insert_elem[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].want == BUILD_FAIL) - continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "insert_elem: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("insert_elem: %d fixtures passed\n", total); - return 0; -} diff --git a/test/wcc/808_arrlit_overlong.c b/test/wcc/808_arrlit_overlong.c deleted file mode 100644 index fc8e4a9a..00000000 --- a/test/wcc/808_arrlit_overlong.c +++ /dev/null @@ -1,507 +0,0 @@ -/* - * 808_arrlit_overlong — an array literal with MORE elements than the - * declared [N] must be a loud checker reject in BOTH stages (#71). - * - * The bug: `let a: [2]u8 = [1u8, 2u8, 3u8];` was silently accepted by - * both checkers — type_assignable fails on the length mismatch, but - * arrlit_init_fits (#130 accept-if-fits) only range-checked the - * elements and never compared the literal's count against the declared - * length. cgen then stored every element at its natural offset, writing - * past the slot: stack-frame smash for locals (the `[1,2,3...]`-into- - * `[2]int` repeat form clobbered the saved BP outright), silent - * neighbour corruption for module-level DATA. The repeat-marker form - * was worse on cstage: clet's has_arr_repeat bypass skipped ALL checks - * for any `...` literal, so `[2]u8 = [999...]` also dodged the #130 - * range check that wwstage already enforced. - * - * The fix (BOTH stages, one choke point each): the shared accept-if- - * fits helper (cmd/wcc/check.c arrlit_init_fits; selfhost/cmd/wcc/ - * check.ww checkarrlitfits) pre-counts the literal's elements (skipping - * the `...` marker) and rejects count > N naming both counts. All four - * declaration contexts (local let / module let / def / struct-field - * literal) funnel through that helper. cstage clet's repeat bypass is - * narrowed to non-array targets so repeat literals into arrays run the - * same checks wwstage always ran. - * - * #105 (the W3 fold): a NAMED-ALIAS element type ([2]row, row=[2]int) - * dodged wwstage's nested recursion — the choke point's inner gate - * keyed on the raw elemtn kind (N_TNAME, not N_TARRAY), so the module - * static-DATA emitter silently TRUNCATED the overlong inner literal - * (exit-masked once #60 removed the read-side segv; cstage loud- - * rejects every spelling via its typed-literal assignability net). - * Fixed by chasing elemtn through resolvealias (transitive) before - * the recursion gate — alias spellings of any depth now take the same - * count + range checks as the direct shape, at all four declaration - * contexts. The alias_nested_* rows pin the flip; alias_exact_module - * is the 0/0 byte-id control (ken's m7c_global_ok). - * - * Out of scope, probed + filed separately: UNDER-long literals (no - * `...`) stay accepted in both stages (Hare rejects); `[0]`/`[_]` - * alen==0 sentinel conflation; wwstage assign/call-arg overlong - * acceptance (cstage already rejects those positions); OUTER alias- - * of-array overlong (`let g: arr = [5 elems]`, arr=[4]int) — the - * call-site N_TARRAY gates are alias-blind, ww still truncates - * (#105-sibling, filed); cstage accepting the out-of-range NESTED - * narrow element ww rejects (range net asymmetry, pre-existing on - * the direct spelling). - * - * accept rows | want - * ----------------------------------+------ - * exact_local [2]int = [1,2] | runs, 0 - * exact_module module-level [2] | runs, 0 - * exact_def def [3] = [1,2,3] | runs, 0 - * exact_field struct f=[2 elems] | runs, 0 - * repeat_fill [4]int = [9...] | runs, 0 - * repeat_partial [4]int = [1,2...] | runs, 0 - * infer_len [_]int = [1,2,3] | runs, 0 - * alias_exact_module [2]row exact | runs, 0 (#105 control) - * - * reject rows (build must FAIL on both drivers, and stderr must name - * both counts — per-stage expected substring: cstage catches the - * NESTED rows through its typed-literal assignability net instead of - * the choke-point diag, so those carry a different cstage substring) - * ---------------------------------------------- - * overlong local / module / def(5-vs-3) / struct-field - * overlong repeat `[2]int = [1,2,3...]` - * overlong narrow `[2]u8 = [1u8,2u8,3u8]` - * nested module `[2][2]int = [[1,2,3],[4,5]]` (inner overlong — - * pre-fix wwstage emitted corrupted DATA: 1,2,4,5) - * nested field struct{f:[2][2]int} inner overlong - * alias nested module / 2lvl / def / field — the #105 flips: same - * inner-overlong shapes spelled through `type row = [2]int` (and - * `row2 = row`); pre-fix wwstage silently truncated (module DATA) - * or smashed frames (cstage was already loud on every row) - * alias nested local — ww pre-fix loud via the LATE cgen #270-1c - * fatal; pins the text move to the checker count diag - */ -#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 want; }; - -static const struct row rows[] = { - { "exact_local", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2]int = [1, 2];\n" - "\treturn a[1]: i32 - 2;\n" - "};\n", - 0 }, - - { "exact_module", - "package main;\n" - "let g: [2]int = [4, 5];\n" - "export fn main() i32 = {\n" - "\treturn g[1]: i32 - 5;\n" - "};\n", - 0 }, - - { "exact_def", - "package main;\n" - "def TAB: [3]int = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn TAB[2]: i32 - 3;\n" - "};\n", - 0 }, - - { "exact_field", - "package main;\n" - "type s = struct { f: [2]int, g: int };\n" - "export fn main() i32 = {\n" - "\tlet v: s = s{ f = [6, 7], g = 8 };\n" - "\treturn v.f[1]: i32 + v.g: i32 - 15;\n" - "};\n", - 0 }, - - { "repeat_fill", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]int = [9...];\n" - "\treturn a[3]: i32 - 9;\n" - "};\n", - 0 }, - - { "repeat_partial", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]int = [1, 2...];\n" - "\treturn a[3]: i32 - 2;\n" - "};\n", - 0 }, - - { "infer_len", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [_]int = [10, 20, 30];\n" - "\treturn a.len: i32 - 3;\n" - "};\n", - 0 }, - - /* #105 control (ken's m7c_global_ok): alias-elem module global, - * exact fit — must keep building 0/0 byte-id through the chased - * recursion gate. `N: int` cast spelling: the bare-int nested - * accept is cstage-rejected (#17), out of this row's scope. */ - { "alias_exact_module", - "package main;\n" - "type row = [2]int;\n" - "let g: [2]row = [[1: int, 2: int], [4: int, 5: int]];\n" - "export fn main() i32 = {\n" - "\tif (g[0][0] != 1) { return 1; };\n" - "\tif (g[1][1] != 5) { return 2; };\n" - "\treturn 0;\n" - "};\n", - 0 }, -}; - -/* Overlong literals — both stages must FAIL the build (the old accept - * stored every element at its natural offset: frame smash) AND the - * diagnostic must carry the expected substring (the choke-point reject - * names BOTH counts; the nested rows reach cstage's pre-existing - * assignability net instead, hence per-stage substrings). */ -struct negrow { - const char *label; - const char *src; - const char *diag_c; /* expected stderr substring, cstage */ - const char *diag_w; /* expected stderr substring, wwstage */ -}; - -#define OVERLONG_3V2 "array literal has 3 elements but declared array holds 2" - -static const struct negrow neg[] = { - { "overlong_local", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2]int = [1, 2, 3];\n" - "\treturn 0;\n" - "};\n", - OVERLONG_3V2, OVERLONG_3V2 }, - - { "overlong_module", - "package main;\n" - "let g: [2]int = [1, 2, 3];\n" - "export fn main() i32 = { return 0; };\n", - OVERLONG_3V2, OVERLONG_3V2 }, - - /* counts deliberately differ from the other rows so a diag that - * hardcodes 3/2 instead of naming the real counts trips here */ - { "overlong_def", - "package main;\n" - "def TAB: [3]int = [1, 2, 3, 4, 5];\n" - "export fn main() i32 = { return 0; };\n", - "array literal has 5 elements but declared array holds 3", - "array literal has 5 elements but declared array holds 3" }, - - { "overlong_field", - "package main;\n" - "type s = struct { f: [2]int };\n" - "export fn main() i32 = {\n" - "\tlet v: s = s{ f = [1, 2, 3] };\n" - "\treturn 0;\n" - "};\n", - OVERLONG_3V2, OVERLONG_3V2 }, - - /* repeat marker with too many explicit elements — the worst - * pre-fix case (wrote at the saved BP) */ - { "overlong_repeat", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2]int = [1, 2, 3...];\n" - "\treturn 0;\n" - "};\n", - OVERLONG_3V2, OVERLONG_3V2 }, - - /* narrow element width (sub-8B store path) */ - { "overlong_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2]u8 = [1u8, 2u8, 3u8];\n" - "\treturn 0;\n" - "};\n", - OVERLONG_3V2, OVERLONG_3V2 }, - - /* nested inner overlong, module DATA — pre-fix wwstage silently - * emitted 1,2,4,5; cstage rejects via assignability, wwstage via - * the recursive choke point */ - { "nested_module", - "package main;\n" - "let g: [2][2]int = [[1, 2, 3], [4, 5]];\n" - "export fn main() i32 = { return 0; };\n", - "not assignable", OVERLONG_3V2 }, - - { "nested_field", - "package main;\n" - "type s = struct { f: [2][2]int };\n" - "export fn main() i32 = {\n" - "\tlet v: s = s{ f = [[1, 2, 3], [4, 5]] };\n" - "\treturn 0;\n" - "};\n", - "not assignable", OVERLONG_3V2 }, - - /* #105 flips: alias-elem inner overlong, the four declaration - * contexts through the one (now chased) choke point. Pre-fix - * wwstage built all four silently — the module row emitted - * truncated DATA (1,2,4,5; ken's m7c_global, exit-masked since - * #60). cstage was already loud on each via assignability. */ - { "alias_nested_module", - "package main;\n" - "type row = [2]int;\n" - "let g: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" - "export fn main() i32 = { return 0; };\n", - "not assignable", OVERLONG_3V2 }, - - /* 2-level alias — pins the transitive chase */ - { "alias_nested_2lvl", - "package main;\n" - "type row = [2]int;\n" - "type row2 = row;\n" - "let g: [2]row2 = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" - "export fn main() i32 = { return 0; };\n", - "not assignable", OVERLONG_3V2 }, - - { "alias_nested_def", - "package main;\n" - "type row = [2]int;\n" - "def TAB: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" - "export fn main() i32 = { return 0; };\n", - "not assignable", OVERLONG_3V2 }, - - /* local let — ww was ALREADY loud pre-fix, but via the cgen - * #270-1c aggregate-element fatal; the chase moves it to the - * checker count diag (ken's m7/m7b text move). This row pins the - * new text so a regression back to the late generic fatal reds. */ - { "alias_nested_local", - "package main;\n" - "type row = [2]int;\n" - "export fn main() i32 = {\n" - "\tlet g: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n" - "\treturn 0;\n" - "};\n", - "not assignable", OVERLONG_3V2 }, - - { "alias_nested_field", - "package main;\n" - "type row = [2]int;\n" - "type s = struct { f: [2]row };\n" - "export fn main() i32 = {\n" - "\tlet v: s = s{ f = [[1: int, 2: int, 3: int], [4: int, 5: int]] };\n" - "\treturn 0;\n" - "};\n", - "not assignable", OVERLONG_3V2 }, -}; - -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/aol_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/aol_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/aol_%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 literal must error on `driver` AND - * the diagnostic must contain `diag`; returns 0 when the build - * correctly FAILS with the expected text, non-zero otherwise. */ -static int -build_should_fail(const char *dname, const char *driver, - const struct negrow *r, const char *diag, int i) -{ - char s[128], tmpdir[64], errf[128], outbin[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/aoln_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/aoln_%d_%d.ww", tmpdir, getpid(), i); - snprintf(errf, sizeof errf, "%s/aoln_%d_%d.err", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/aoln_%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(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s", - driver, outbin, s, errf); - int rc = runwait(cmd); - - int bad = 0; - if (rc == 0) { - fprintf(stderr, "arrlit_overlong[%s][%s]: built ok, " - "expected a loud error\n", dname, r->label); - bad = 1; - } else { - char ebuf[4096]; - size_t n = 0; - FILE *ef = fopen(errf, "rb"); - if (ef) { - n = fread(ebuf, 1, sizeof ebuf - 1, ef); - fclose(ef); - } - ebuf[n] = '\0'; - if (strstr(ebuf, diag) == NULL) { - fprintf(stderr, "arrlit_overlong[%s][%s]: rejected " - "but diagnostic lacks \"%s\"; got: %s\n", - dname, r->label, diag, ebuf); - bad = 1; - } - } - runwait(rmcmd); - return bad; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/aol_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/aol_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/aol_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "arrlit_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, - "arrlit_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++; - const char *diag = - strcmp(drivers[d].name, "cstage") == 0 - ? neg[i].diag_c : neg[i].diag_w; - if (build_should_fail(drivers[d].name, - drivers[d].path, &neg[i], diag, 100 + i) != 0) - fail++; - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "arrlit_overlong: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("arrlit_overlong: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/809_delete_range.c b/test/wcc/809_delete_range.c deleted file mode 100644 index dcd1aa81..00000000 --- a/test/wcc/809_delete_range.c +++ /dev/null @@ -1,604 +0,0 @@ -/* - * 809_delete_range — cstage and wwstage agree, byte-for-byte and at - * runtime, that the range form `delete(xs[lo:hi])` removes elements - * [lo, hi) from a slice: the tail [hi..len) shifts down count = hi-lo - * strides, len -= count, cap unchanged; lo defaults 0, hi defaults - * len (so delete(xs[:]) clears the slice, storage retained). The - * range half of the delete() builtin — fold-5a prereq P2 for the - * regex.ha:333 consumer `delete(jump_idxs[group_level][..])` (harec - * ref/harec/src/check.c:1981-2027 accepts EXPR_SLICE alongside the - * 804-covered ACCESS_INDEX form). - * - * Lowering (BOTH stages, converged byte-identical by construction): - * push &hdr, lo and count = hi-lo, then an ascending word-copy loop - * moves element j+count onto element j until j+count >= len, then - * hdr.len -= count. The per-element move is 804's same-slice - * whole-stride word copy with a DYNAMIC src offset (count*esz via a - * src register) instead of the constant one-stride; the esz spread - * below (1/2/4/8/16/24) re-proves every copy-tail arm against the - * dynamic src. Base shapes: local slice ident, deref-of-local - * ptr-to-slice, and — new for the range arm — an INDEXED local slice - * base xs[g][lo:hi] (the regex fold-5a consumer shape). Bounds are - * implicit (no range check), matching the 804 single-element arm and - * the rest of cgen — so no out-of-bounds abort rows here; a - * violating range is as undefined as a violating index. - * - * row | shape | want - * -----------------+----------------------------------------+------ - * full_range | [7,11,13], delete(xs[:]) -> len 0 | 7 - * full_explicit | [7,11,13], delete(xs[0:3]) -> len 0 | 8 - * full_twice | delete(xs[:]) twice — second is a | 9 - * | no-op on the emptied slice (count 0) | - * head | [1,2,3], delete(xs[:2]) — lo default 0 | 13 - * mid | [1..5], delete(xs[1:3]) | 148 - * tail_open | [1,2,3], delete(xs[1:]) — hi default | 11 - * | len, copy loop never entered | - * empty_range | [1,2], delete(xs[1:1]) — count 0, | 23 - * | self-copy loop, len unchanged | - * end_boundary | [1,2], delete(xs[len:len]) — lo==hi== | 212 - * | len, loop exits at entry, no deref | - * | past the end | - * empty_explicit | delete(xs[0:0]) on a never-appended | 31 - * | empty slice (ptr nil) — count 0 and | - * | len 0, nothing dereferenced | - * single_via_range | delete(xs[1:2]) == delete(ys[1]) | 42 - * | element-for-element | - * cap_preserved | manual {ptr,len=3,cap=8} header, | 80 - * | delete(xs[:]); cap must still read 8 | - * u8_narrow | []u8 esz=1 — MOVB tail, dynamic src | 215 - * u16_narrow | []u16 esz=2 — MOVW tail, dynamic src | 25 - * i32_narrow | []i32 esz=4 — MOVL tail, dynamic src | 50 - * struct16 | []pair esz=16 — 2 whole qwords, no | 216 - * | sub-word tail (804's struct esz twin) | - * str_elem | []str esz=24 — 3-qword headers move | 235 - * | whole across a 2-stride shift | - * regex_exact | [][]size, delete(jj[lvl][:]) — the | 10 - * | EXACT regex.ha:333 consumer shape | - * | (indexed base, size-typed index) | - * deref_shape | delete((*p)[:]) behind *[]u8 — the | 17 - * | 804 regex_shape twin | - * order_of_eval | delete(xs[lof():hif()]) — lo then hi, | 16 - * | each CALLED exactly once (trace must | - * | read 12; harec evaluates the slicing | - * | operands left-to-right, once) | - * alias_visible | delete(xs[poke(&xs):]) — lo's write | 91 - * | through &xs lands BEFORE the shift | - * | (loop reads ptr/len/elems fresh from | - * | the header, after operand eval) | - * reject_array | delete(t[0:2]) on [3]i64 — "delete | BUILD_FAIL - * | must operate on a slice" | - * reject_str | delete(s[:]) on str — slicing a str | BUILD_FAIL - * | yields str, not a slice | - * - * BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring, - * both stages) — a build that fails for any other reason (parse error, - * crash) is a vacuous reject and fails the row. - * - * Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id, - * which subsumes the frame canary (TEXT main,$N) and the rdl_l/rdl_e - * label-counter symmetry. - */ -#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; -} - -/* want == BUILD_FAIL: the row must FAIL to build on both stages AND - * emit expect_err on stderr (the checker reject set — message text - * included — is part of the contract: rule 7, never a silent - * acceptance; without the message check a row would pass vacuously on - * any unrelated build failure). */ -#define BUILD_FAIL (-2147483647 - 1) - -struct row { - const char *label; - const char *src; - int want; - const char *expect_err; /* BUILD_FAIL rows: required stderr substring */ -}; - -static const struct row rows[] = { - { "full_range", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tdelete(xs[:]);\n" - "\treturn len(xs): i32 + 7;\n" - "};\n", - 7, NULL }, - - { "full_explicit", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tdelete(xs[0:3]);\n" - "\treturn len(xs): i32 + 8;\n" - "};\n", - 8, NULL }, - - /* Second full-range delete sees len == 0: count = len - 0 = 0, - * the copy loop exits at entry, len -= 0. Pins that an emptied - * slice survives a re-clear (the regex `)` arm clears - * jump_idxs[group_level] whether or not `|` populated it). */ - { "full_twice", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tdelete(xs[:]);\n" - "\tdelete(xs[:]);\n" - "\treturn len(xs): i32 + 9;\n" - "};\n", - 9, NULL }, - - { "head", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tappend(xs, 3);\n" - "\tdelete(xs[:2]);\n" - "\treturn xs[0]: i32 + len(xs)*10;\n" - "};\n", - 13, NULL }, - - { "mid", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tappend(xs, 3);\n" - "\tappend(xs, 4);\n" - "\tappend(xs, 5);\n" - "\tdelete(xs[1:3]);\n" - "\treturn (xs[0]*100 + xs[1]*10 + xs[2]): i32 + len(xs);\n" - "};\n", - 148, NULL }, - - { "tail_open", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tappend(xs, 3);\n" - "\tdelete(xs[1:]);\n" - "\treturn xs[0]: i32 + len(xs)*10;\n" - "};\n", - 11, NULL }, - - { "empty_range", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tdelete(xs[1:1]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 23, NULL }, - - /* lo == hi == len: the loop guard j+count >= len holds at entry - * with j = len, so nothing past the end is ever addressed. */ - { "end_boundary", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tdelete(xs[len(xs):len(xs)]);\n" - "\treturn (xs[0]*10 + xs[1]): i32 + len(xs)*100;\n" - "};\n", - 212, NULL }, - - /* Never-appended empty slice: ptr is nil. count 0 and len 0 mean - * the loop body (the only dereferencing code) never runs. */ - { "empty_explicit", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tdelete(xs[0:0]);\n" - "\treturn len(xs): i32 + 31;\n" - "};\n", - 31, NULL }, - - { "single_via_range", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 7);\n" - "\tappend(xs, 11);\n" - "\tappend(xs, 13);\n" - "\tdelete(xs[1:2]);\n" - "\tlet ys: []i64 = [];\n" - "\tappend(ys, 7);\n" - "\tappend(ys, 11);\n" - "\tappend(ys, 13);\n" - "\tdelete(ys[1]);\n" - "\tif (len(xs) != len(ys)) { return 99; };\n" - "\tif (xs[0] != ys[0]) { return 98; };\n" - "\tif (xs[1] != ys[1]) { return 97; };\n" - "\treturn 42;\n" - "};\n", - 42, NULL }, - - { "cap_preserved", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet hb: [8]u8; hb[0] = 4u8; hb[1] = 6u8; hb[2] = 9u8;\n" - "\tlet xs: []u8; xs.ptr = &hb[0]; xs.len = 3; xs.cap = 8;\n" - "\tdelete(xs[:]);\n" - "\treturn (xs.cap*10 + xs.len): i32;\n" - "};\n", - 80, NULL }, - - { "u8_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 1u8);\n" - "\tappend(xs, 2u8);\n" - "\tappend(xs, 3u8);\n" - "\tappend(xs, 4u8);\n" - "\tappend(xs, 5u8);\n" - "\tdelete(xs[1:4]);\n" - "\treturn (xs[0]*10u8 + xs[1]): i32 + len(xs)*100;\n" - "};\n", - 215, NULL }, - - { "u16_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []u16 = [];\n" - "\tappend(xs, 1u16);\n" - "\tappend(xs, 2u16);\n" - "\tappend(xs, 3u16);\n" - "\tappend(xs, 4u16);\n" - "\tdelete(xs[1:3]);\n" - "\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n" - "};\n", - 25, NULL }, - - { "i32_narrow", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []i32 = [];\n" - "\tappend(xs, 10i32);\n" - "\tappend(xs, 20i32);\n" - "\tappend(xs, 30i32);\n" - "\tappend(xs, 40i32);\n" - "\tdelete(xs[2:4]);\n" - "\treturn xs[0] + xs[1] + len(xs)*10;\n" - "};\n", - 50, NULL }, - - /* esz 16: exactly two whole qwords per element — the only spread - * point with multiple MOVQs and NO sub-word tail. */ - { "struct16", - "package main;\n" - "type pair = struct { a: i64, b: i64 };\n" - "export fn main() i32 = {\n" - "\tlet xs: []pair = [];\n" - "\tappend(xs, pair{ a = 1, b = 2 });\n" - "\tappend(xs, pair{ a = 3, b = 4 });\n" - "\tappend(xs, pair{ a = 5, b = 6 });\n" - "\tdelete(xs[1:2]);\n" - "\treturn (xs[0].a*10 + xs[1].b): i32 + len(xs)*100;\n" - "};\n", - 216, NULL }, - - { "str_elem", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet xs: []str = [];\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = \"de\";\n" - "\tlet g: str = \"fghi\";\n" - "\tlet h: str = \"jklmn\";\n" - "\tappend(xs, a);\n" - "\tappend(xs, b);\n" - "\tappend(xs, g);\n" - "\tappend(xs, h);\n" - "\tdelete(xs[1:3]);\n" - "\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n" - "};\n", - 235, NULL }, - - /* The fold-5a consumer, shape-EXACT: indexed local-slice base, - * size-typed outer index, defaulted full range (regex.ha:333 - * `delete(jump_idxs[group_level][..])`; ww spells `[..]` as - * `[:]`, D7). The header lives at jj.ptr + g*size([]size) — - * outer stride off the type table, never a literal. */ - { "regex_exact", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet jump_idxs: [][]size = [];\n" - "\tlet lvl0: []size = [];\n" - "\tlet a: size = 3;\n" - "\tlet b: size = 9;\n" - "\tappend(lvl0, a);\n" - "\tappend(lvl0, b);\n" - "\tappend(jump_idxs, lvl0);\n" - "\tlet group_level: size = 0;\n" - "\tdelete(jump_idxs[group_level][:]);\n" - "\treturn len(jump_idxs[0]): i32 + len(jump_idxs): i32 * 10;\n" - "};\n", - 10, NULL }, - - { "deref_shape", - "package main;\n" - "fn clear(p: *[]u8) void = {\n" - "\tdelete((*p)[:]);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet xs: []u8 = [];\n" - "\tappend(xs, 5u8);\n" - "\tappend(xs, 6u8);\n" - "\tclear(&xs);\n" - "\treturn len(xs): i32 + 17;\n" - "};\n", - 17, NULL }, - - /* lo and hi as CALLs: each side effect fires exactly once, lo - * before hi (trace 12, never 21/121/122) — pins that neither - * bound expression is re-evaluated by the count computation or - * the copy loop. */ - { "order_of_eval", - "package main;\n" - "let trace: i64 = 0;\n" - "fn lof() size = {\n" - "\ttrace = trace*10 + 1;\n" - "\treturn 1;\n" - "};\n" - "fn hif() size = {\n" - "\ttrace = trace*10 + 2;\n" - "\treturn 3;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tappend(xs, 3);\n" - "\tappend(xs, 4);\n" - "\tdelete(xs[lof():hif()]);\n" - "\tif (trace != 12) { return 90; };\n" - "\treturn (xs[0]*10 + xs[1]): i32 + len(xs);\n" - "};\n", - 16, NULL }, - - /* lo mutates the slice it is deleting from: only the header - * ADDRESS is taken before operand eval; ptr/len/elements are read - * after, so poke's write to xs[0] survives into the result. */ - { "alias_visible", - "package main;\n" - "fn poke(p: *[]i64) size = {\n" - "\t(*p)[0] = 9;\n" - "\treturn 1;\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet xs: []i64 = [];\n" - "\tappend(xs, 1);\n" - "\tappend(xs, 2);\n" - "\tappend(xs, 3);\n" - "\tdelete(xs[poke(&xs):]);\n" - "\treturn xs[0]: i32 * 10 + len(xs);\n" - "};\n", - 91, NULL }, - - { "reject_array", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet t: [3]i64 = [1, 2, 3];\n" - "\tdelete(t[0:2]);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "delete must operate on a slice" }, - - { "reject_str", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet s: str = \"abc\";\n" - "\tdelete(s[:]);\n" - "\treturn 0;\n" - "};\n", - BUILD_FAIL, "delete must operate on a slice" }, -}; - -/* errlog_has — the build-failure stderr must carry the row's expected - * diagnostic; any other failure (parse error, crash) is a vacuous - * reject and must not pass. */ -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; -} - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160]; - char cmd[1200]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/delr_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/delr_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/delr_%d_%d", tmpdir, getpid(), i); - snprintf(errlog, sizeof errlog, "%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, "%s build -o %s %s 2>%s", - driver, outbin, src, errlog); - if (runwait(cmd) != 0) { - int rc = -1; - if (r->want != BUILD_FAIL) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - } else if (r->expect_err && - !errlog_has(errlog, r->expect_err)) { - fprintf(stderr, "row[%s]: %s build failed without " - "expected diagnostic \"%s\"\n", - r->label, driver, r->expect_err); - rc = -3; /* failed, but for the wrong reason */ - } - runwait(rmcmd); - return rc; - } - - int got = runwait(outbin); - - runwait(rmcmd); - return got; -} - -/* asm_byte_identical — compile the row's source standalone with w6c and - * w6c_ww and diff. Both range-delete lowerings are written fresh, so - * this is the converged-by-construction gate: any drift in the shift - * loop, the rdl_l/rdl_e label sequence, the dynamic-src copy, or the - * indexed-base header address shows here. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/delr_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/delr_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/delr_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "delete_range: 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++; - int bad = rows[i].want == BUILD_FAIL - ? (got != -1) : (got != rows[i].want); - if (bad) { - fprintf(stderr, - "delete_range[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].want == BUILD_FAIL) - continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "delete_range: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("delete_range: %d fixtures passed\n", total); - return 0; -} diff --git a/test/wcc/809_idx_structlit_store.c b/test/wcc/809_idx_structlit_store.c deleted file mode 100644 index f8308c5f..00000000 --- a/test/wcc/809_idx_structlit_store.c +++ /dev/null @@ -1,550 +0,0 @@ -/* - * 809_idx_structlit_store — cstage and wwstage agree, byte-for-byte and - * at runtime, that a struct-LITERAL store into an INDEXED element place - * (`a[i] = pt{...}`, `cs[i] = capture{...}`, `(*ts)[i].caps[k] = - * capture{...}`) and into a DEREF place (`*p = pt{...}`) writes every - * field (task #20; ken's p13 array find + prober PG4 slice extension; - * gates regex fold-5a's run_thread groupstart capture store, - * regex.ha:643-651). - * - * Pre-fix BOTH stages compiled these byte-identically WRONG (gate-blind - * — only a runtime pin can hold this): the N_ASSIGN N_INDEX-lhs arm's - * aggregate branch (#270-1b) gated its rhs on ident/dot/deref shapes, - * so an N_STRUCTLIT rhs fell to the scalar store tail; cgexpr on a - * struct literal emits NOTHING (AX stays 0) and the tail stored ONE - * zero word at the element base. Net effect: every literal field - * silently dropped, the element's first 8 bytes zeroed — for a - * str-leading element (the regex capture shape) that nulls content.ptr - * and the next read of the str field SEGFAULTS. The N_UN(STAR) deref - * arm (`*p = pt{...}`) had the same fall-to-scalar tail — same class. - * - * The fix (BOTH stages, converged byte-identical): a struct-lit rhs - * aimed at an N_INDEX, N_UN(STAR), or indexed-base N_DOT place - * (`a[i].f = pt{...}`, reviewer-20 sibling — the a[i].f legacy arm's - * fldstoreop tail had the same silent drop) skips the legacy arms and - * routes through the F6 assign-resolver (cgplaceaddr) — the SAME C1.25 - * aggregate arm that wires `(*ts)[i].field = capture{...}` N_DOT - * places: materialise the literal into a FRESH per-use @placescr slot - * (cg_structlit_fill_bp / cgstructlitfillbp: nested literals, str - * fields, TK_ELLIPSIS autofill), resolve the place address, word-copy - * esz bytes. Close-by-construction: every non-ident place kind (DOT / - * INDEX / UN-STAR) now funnels struct-lit stores through that single - * arm; ident places keep their enumerated fill paths. Class boundary - * for OTHER rhs kinds at these places: tuple-lit/str-lit into INDEX - * and DOT places work (pinned below); array-lit rhs at assignment is - * unwired for every place kind and now dies LOUD (task #32, build-fail - * rows below); tuple-lit through deref truncates (filed #31-E); CALL - * rhs into INDEX/UN-STAR places stores only RAX (filed #31-G). - * - * row | shape | want - * ------------------+-----------------------------------------+------ - * arr_elem | a[1] = pt{3,4}; a[1].x*10+a[1].y | 34 - * arr_neighbor | store a[1]; a[0]/a[2] field-set intact | 82 - * arr_var_idx | a[geti()] = pt{...} (runtime index) | 34 - * small_elem | 8B one-field elem (esz<=8 word-copy) | 52 - * nested_lit | a[1] = outer{ in = inner{3,4}, t=5 } | 45 - * partial_ellipsis | prefill 9,9; a[1] = pt{x=3, ...} → y=0 | 30 - * alias_lit | type qt = pt; a[1] = qt{3,4} (chase | 34 - * | peels the NAMED alias before TY_STRUCT) | - * all_default | prefill 9,9; a[1] = pt{} zeroes EVERY | 7 - * | word (fill-loop floor) + 7 | - * tuple_elem | a[1] = (3,4); copy-out readback | 34 - * dotidx_field | a[1].p = pt{3,4} (indexed-base DOT | 34 - * | place, reviewer-20 sibling fix) | - * slice_elem_str | cs[1] = capture{...} 56B str-leading | 128 - * fieldplace_slice | ts[0].caps[1] = capture{...} | 128 - * derefspine_slice | (*tsp)[0].caps[1] = capture{...} | 128 - * callee_capture | (*ts)[i].caps[k] = capture{...} in a | 197 - * | callee w/ *[]thr param; str readback via | - * | strings.compare + len + both neighbors | - * | (the EXACT fold-5a consumer shape) | - * deref_lit | *p = pt{3,4} (N_UN place, same class) | 34 - * global_arr | g[1] = pt{3,4}, g: [3]pt module let | 34 - * arrlit_idx_loud | a[1] = [3,4] — LOUD build-fail (#32) | -1 - * arrlit_deref_loud | *p = [3,4] — LOUD build-fail (#32) | -1 - * - * global_arr is RUNTIME-ONLY (byteid=0): an uninitialised [N]struct - * value-global already diverges on master — wwstage emits a zero - * `DATAW main.g(SB)` record (under-sized: 24B for a 48B array), - * cstage emits none. Pre-existing, independent of this fix (task #11 - * family: value-global data emission); the store/readback this row - * pins is symmetric and correct on both stages. The arrlit_* rows pin - * task #32's loud boundary (want -1 = driver build MUST fail; pre-#32 - * both stages silently zeroed one word); flip them to runtime rows - * when #32 wires the fill. - */ -#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; int byteid; }; - -/* The 56B regex capture shape: str header leading + 4 size fields. - * Shared preamble for the slice rows; only the store site differs. */ -#define CAPTURE_DEFS \ - "package main;\n" \ - "type capture = struct {\n" \ - "\tcontent: str,\n" \ - "\tstart: size,\n" \ - "\tstart_bytesize: size,\n" \ - "\tend: size,\n" \ - "\tend_bytesize: size,\n" \ - "};\n" - -#define CAPTURE_SEED \ - "\tlet cs: []capture = [];\n" \ - "\tappend(cs, capture { content = \"a\", start = 1: size, " \ - "start_bytesize = 1: size, end = 1: size, " \ - "end_bytesize = 1: size });\n" \ - "\tappend(cs, capture { content = \"b\", start = 2: size, " \ - "start_bytesize = 2: size, end = 2: size, " \ - "end_bytesize = 2: size });\n" - -#define CAPTURE_STORE(place) \ - "\t" place " = capture { content = \"grp\", start = 3: size, " \ - "start_bytesize = 4: size, end = 60: size, " \ - "end_bytesize = 61: size };\n" - -static const struct row rows[] = { - { "arr_elem", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [3]pt;\n" - "\ta[1] = pt { x = 3u64, y = 4u64 };\n" - "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" - "};\n", - 34, 1 }, - - { "arr_neighbor", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [3]pt;\n" - "\ta[0].x = 7u64; a[2].y = 9u64;\n" - "\ta[1] = pt { x = 3u64, y = 4u64 };\n" - "\treturn (a[0].x * 10u64 + a[2].y + a[1].x): i32;\n" - "};\n", - 82, 1 }, - - { "arr_var_idx", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "fn geti() size = { return 1: size; };\n" - "export fn main() i32 = {\n" - "\tlet a: [3]pt;\n" - "\ta[geti()] = pt { x = 3u64, y = 4u64 };\n" - "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" - "};\n", - 34, 1 }, - - { "small_elem", - "package main;\n" - "type one = struct { v: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [2]one;\n" - "\ta[0].v = 2u64;\n" - "\ta[1] = one { v = 5u64 };\n" - "\treturn (a[1].v * 10u64 + a[0].v): i32;\n" - "};\n", - 52, 1 }, - - { "nested_lit", - "package main;\n" - "type inner = struct { a: u64, b: u64 };\n" - "type outer = struct { in: inner, t: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [2]outer;\n" - "\ta[1] = outer { in = inner { a = 3u64, b = 4u64 }, " - "t = 5u64 };\n" - "\treturn (a[1].in.b * 10u64 + a[1].t): i32;\n" - "};\n", - 45, 1 }, - - { "partial_ellipsis", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [3]pt;\n" - "\ta[1].x = 9u64; a[1].y = 9u64;\n" - "\ta[1] = pt { x = 3u64, ... };\n" - "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" - "};\n", - 30, 1 }, - - { "alias_lit", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "type qt = pt;\n" - "export fn main() i32 = {\n" - "\tlet a: [3]pt;\n" - "\ta[1] = qt { x = 3u64, y = 4u64 };\n" - "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" - "};\n", - 34, 1 }, - - { "all_default", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [3]pt;\n" - "\ta[1].x = 9u64; a[1].y = 9u64;\n" - "\ta[1] = pt {};\n" - "\treturn (a[1].x * 10u64 + a[1].y + 7u64): i32;\n" - "};\n", - 7, 1 }, - - { "tuple_elem", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2](u64, u64);\n" - "\ta[1] = (3u64, 4u64);\n" - "\tlet t: (u64, u64) = a[1];\n" - "\treturn (t.0 * 10u64 + t.1): i32;\n" - "};\n", - 34, 1 }, - - { "dotidx_field", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "type box = struct { p: pt, t: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: [2]box;\n" - "\ta[1].p.x = 9u64; a[1].p.y = 9u64;\n" - "\tif (a[1].p.x != 9u64 || a[1].p.y != 9u64) { return 250; };\n" - "\ta[1].p = pt { x = 3u64, y = 4u64 };\n" - "\treturn (a[1].p.x * 10u64 + a[1].p.y): i32;\n" - "};\n", - 34, 1 }, - - { "slice_elem_str", - CAPTURE_DEFS - "export fn main() i32 = {\n" - CAPTURE_SEED - CAPTURE_STORE("cs[1]") - "\tif (cs[0].start != 1) { return 250; };\n" - "\treturn (cs[1].start + cs[1].start_bytesize + cs[1].end + " - "cs[1].end_bytesize): i32;\n" - "};\n", - 128, 1 }, - - { "fieldplace_slice", - CAPTURE_DEFS - "type thr = struct { pc: size, caps: []capture };\n" - "export fn main() i32 = {\n" - CAPTURE_SEED - "\tlet ts: []thr = [];\n" - "\tappend(ts, thr { pc = 5: size, caps = cs });\n" - CAPTURE_STORE("ts[0].caps[1]") - "\tif (ts[0].caps[0].start != 1) { return 250; };\n" - "\treturn (ts[0].caps[1].start + ts[0].caps[1].start_bytesize + " - "ts[0].caps[1].end + ts[0].caps[1].end_bytesize): i32;\n" - "};\n", - 128, 1 }, - - { "derefspine_slice", - CAPTURE_DEFS - "type thr = struct { pc: size, caps: []capture };\n" - "export fn main() i32 = {\n" - CAPTURE_SEED - "\tlet ts: []thr = [];\n" - "\tappend(ts, thr { pc = 5: size, caps = cs });\n" - "\tlet tsp = &ts;\n" - CAPTURE_STORE("(*tsp)[0].caps[1]") - "\tif (ts[0].caps[0].start != 1) { return 250; };\n" - "\treturn (ts[0].caps[1].start + ts[0].caps[1].start_bytesize + " - "ts[0].caps[1].end + ts[0].caps[1].end_bytesize): i32;\n" - "};\n", - 128, 1 }, - - /* The fold-5a consumer pin: regex.ha:643-651 verbatim shape — - * callee takes *[]thr, double-indexes through the deref spine, - * stores the 56B capture literal. Readback covers the str field - * (strings.compare + len — pre-fix this SEGFAULTED on the nulled - * content.ptr), both neighbor elements (56B store must not - * smear), and all four size fields. 3+4+60+61+3*23 = 197. */ - { "callee_capture", - "package main;\n" - "\n" - "import strings;\n" - "\n" - "type capture = struct {\n" - "\tcontent: str,\n" - "\tstart: size,\n" - "\tstart_bytesize: size,\n" - "\tend: size,\n" - "\tend_bytesize: size,\n" - "};\n" - "type thr = struct { pc: size, caps: []capture };\n" - "fn store(ts: *[]thr, i: size, idx: size) void = {\n" - "\t(*ts)[i].caps[idx] = capture {\n" - "\t\tcontent = \"grp\",\n" - "\t\tstart = 3: size,\n" - "\t\tstart_bytesize = 4: size,\n" - "\t\tend = 60: size,\n" - "\t\tend_bytesize = 61: size,\n" - "\t};\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet cs: []capture = [];\n" - "\tappend(cs, capture { content = \"a\", start = 1: size, " - "start_bytesize = 1: size, end = 1: size, " - "end_bytesize = 1: size });\n" - "\tappend(cs, capture { content = \"b\", start = 2: size, " - "start_bytesize = 2: size, end = 2: size, " - "end_bytesize = 2: size });\n" - "\tappend(cs, capture { content = \"c\", start = 9: size, " - "start_bytesize = 9: size, end = 9: size, " - "end_bytesize = 9: size });\n" - "\tlet ts: []thr = [];\n" - "\tappend(ts, thr { pc = 5: size, caps = cs });\n" - "\tstore(&ts, 0: size, 1: size);\n" - "\tif (strings.compare(ts[0].caps[1].content, \"grp\") != 0) " - "{ return 250; };\n" - "\tif (len(ts[0].caps[1].content) != 3) { return 249; };\n" - "\tif (ts[0].caps[0].end != 1 || ts[0].caps[2].start != 9) " - "{ return 248; };\n" - "\tif (strings.compare(ts[0].caps[2].content, \"c\") != 0) " - "{ return 247; };\n" - "\tlet acc = ts[0].caps[1].start + ts[0].caps[1].start_bytesize\n" - "\t\t+ ts[0].caps[1].end + ts[0].caps[1].end_bytesize\n" - "\t\t+ ts[0].caps[1].start * 23;\n" - "\treturn acc: i32;\n" - "};\n", - 197, 1 }, - - { "deref_lit", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: pt = pt { x = 1u64, y = 2u64 };\n" - "\tlet p: *pt = &a;\n" - "\t*p = pt { x = 3u64, y = 4u64 };\n" - "\treturn (a.x * 10u64 + a.y): i32;\n" - "};\n", - 34, 1 }, - - { "global_arr", - "package main;\n" - "type pt = struct { x: u64, y: u64 };\n" - "let g: [3]pt;\n" - "export fn main() i32 = {\n" - "\tg[1] = pt { x = 3u64, y = 4u64 };\n" - "\treturn (g[1].x * 10u64 + g[1].y): i32;\n" - "};\n", - 34, 0 }, - - { "arrlit_idx_loud", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2][2]u64;\n" - "\ta[1] = [3u64, 4u64];\n" - "\treturn (a[1][0] * 10u64 + a[1][1]): i32;\n" - "};\n", - -1, 0 }, - - { "arrlit_deref_loud", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [2]u64;\n" - "\tlet p: *[2]u64 = &a;\n" - "\t*p = [3u64, 4u64];\n" - "\treturn (a[0] * 10u64 + a[1]): i32;\n" - "};\n", - -1, 0 }, -}; - -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/isls_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/isls_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/isls_%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) { - /* want == -1 rows pin a LOUD build refusal (task #32). */ - if (r->want != -1) - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - runwait(rmcmd); - return -1; - } - - int got = runwait(outbin); - - runwait(rmcmd); - return got; -} - -/* asm_byte_identical — #103 sep layout: the driver flip moves build - * artifacts from next-to-source .s to a .sepwork/ scratch - * dir, so drive `ww build --sep -o ` twice — once with cstage's w6c - * and once with the wwstage compiler (WW_W6C override; w6a/w6l stay - * cstage — only the .s is compared) — then concat each build's - * per-package .sepwork asm (sorted glob, identical package set: - * callee_capture pulls strings, so this is multi-package) and diff. The - * driver route — not a bare `w6c src.ww` — so importing rows resolve; - * --sep is flip-invariant (green pre- and post-flip). WW_PKGCACHE pins - * the package cache to the scratch dir so out/.pkgcache stays untouched. - * Pre-fix the asm was byte-identically WRONG (both stages dropped the - * fill), so these rows pin only that the converged fix stays symmetric; - * the runtime rows above carry correctness. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[96], tmpdir[64], outc[128], outw[128]; - char cs[96], ws[96], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/isls_asm_%d_d_%d", getpid(), i); - snprintf(src, sizeof src, "%s/main809.ww", tmpdir); - snprintf(outc, sizeof outc, "%s/main809c", tmpdir); - snprintf(outw, sizeof outw, "%s/main809w", tmpdir); - snprintf(cs, sizeof cs, "%s/all_cs.s", tmpdir); - snprintf(ws, sizeof ws, "%s/all_ww.s", tmpdir); - - mkdir(tmpdir, 0755); - FILE *f = fopen(src, "wb"); - if (!f) { - snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); - if (system(cmd)) {} - return -1; - } - fputs(r->src, f); - fclose(f); - - int rc = 0; - snprintf(cmd, sizeof cmd, - "cd %s && WW_PKGCACHE=%s/pkgc_c %s/ww build --sep -o %s %s 2>/dev/null", - tmpdir, tmpdir, bin, outc, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage sep build failed\n", r->label); - rc = -1; - } - if (rc == 0) { - snprintf(cmd, sizeof cmd, - "cd %s && WW_PKGCACHE=%s/pkgc_w WW_W6C=%s/w6c_ww " - "%s/ww build --sep -o %s %s 2>/dev/null", - tmpdir, tmpdir, bin, bin, outw, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: wwstage sep build failed\n", - r->label); - rc = -1; - } - } - if (rc == 0) { - snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null", - outc, cs); if (system(cmd)) {} - snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null", - outw, ws); if (system(cmd)) {} - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - if (rc != 0) - fprintf(stderr, - "row[%s]: cstage vs wwstage asm differs\n", - r->label); - } - snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); - if (system(cmd)) {} - 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 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, - "idx_structlit_store: 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, - "idx_structlit_store[%s][%s]: " - "exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (!rows[i].byteid) continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "idx_structlit_store: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("idx_structlit_store: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/811_inferred_let_struct.c b/test/wcc/811_inferred_let_struct.c deleted file mode 100644 index 86212757..00000000 --- a/test/wcc/811_inferred_let_struct.c +++ /dev/null @@ -1,362 +0,0 @@ -/* - * 811_inferred_let_struct — cstage and wwstage agree, byte-for-byte and - * at runtime, on field access through an ANNOTATION-LESS struct-literal - * binding `let p = pt { ... };` (task #24, fold-5 prereq; the PG5 - * wwstage pair). - * - * The bug (wwstage only): for an inferred binding the checker planted - * the struct decl's BODY node (N_TSTRUCT, exprtype N_STRUCTLIT returns - * ms.decl.lhs per #66) as the let's type — but every cgen local-arm - * dispatch (cgdot field read, cgassign tagged-field store, the alias - * peel) is N_TNAME-keyed. The body matched no arm, so `p.f` fell - * through to the module-qualified fallback and emitted the FIELD NAME - * as a global symbol (`MOVQ f(SB), AX` — the #211 name-leak family; - * loud at link, silent corruption if a same-named global exists), and - * a tagged-field assign fell to the assign-resolver's TY_TAGGED loud - * bound ("assign-resolver: tagged field not wired"). cstage was - * unaffected: check.c:1477 clet carries Sym.type (tinfo) and its - * emission is annotation-invariant. - * - * The fix (wwstage checker, check.ww checkletassign): normalize the - * inferred binding to the synthesized N_TNAME (mktname), making it - * indistinguishable from the annotated form downstream — every read/ - * assign/is/as arm then takes the already-byte-id annotated route. - * - * Mutation coverage: on pre-fix wwstage every unannotated row LINK- - * FAILS (undefined reference to the field name), so run_driver's -1 - * catches a regression outright; the annotated rows pin that the - * normalization didn't perturb the annotated path (byte-id rows assert - * cstage == wwstage asm for all rows). - * - * row | shape | want - * ---------------------+------------------------------------+------ - * plain_read | let p = pt{..}; p.x*10 + p.y | 42 - * tagged_read_is_as | (void|size) field: read + is/as | 6 - * tagged_void_read | min=void; `is void` arm | 7 - * tagged_assign | r.min = 2: size through inferred | 3 - * tagged_str_field | (void|str): assign + as-str .len | 14 - * boxed_prebound | field-wise build, box into union, | 103 - * | match-extract, is/as (PG5h) | - * annotated_pin | same as tagged_assign but with an | 3 - * | explicit `: rep` annotation | - * nested_read | plain nested s.f.g read (master | 42 - * | ran right but cs!=ww asm) | - * ellipsis_fill | pt{ x = 4, ... } autofill (master | 40 - * | wwstage link-failed) | - * paren_form | (pt{..}) — parser unwraps parens, | 42 - * | same N_STRUCTLIT rhs (master | - * | wwstage link-failed) | - */ -#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[] = { - { "plain_read", - "package main;\n" - "type pt = struct { x: size, y: size };\n" - "export fn main() i32 = {\n" - "\tlet p = pt { x = 4: size, y = 2: size };\n" - "\treturn (p.x * 10 + p.y): i32;\n" - "};\n", - 42 }, - - { "tagged_read_is_as", - "package main;\n" - "type rep = struct { id: size, min: (void | size) };\n" - "export fn main() i32 = {\n" - "\tlet r = rep { id = 1: size, min = 5: size };\n" - "\tlet acc = 0: size;\n" - "\tacc += r.id;\n" - "\tif (r.min is size) { acc += r.min as size; };\n" - "\treturn acc: i32;\n" - "};\n", - 6 }, - - { "tagged_void_read", - "package main;\n" - "type rep = struct { id: size, min: (void | size) };\n" - "export fn main() i32 = {\n" - "\tlet r = rep { id = 0: size, min = void };\n" - "\tlet acc = 0: size;\n" - "\tif (r.min is void) { acc += 7; };\n" - "\tif (r.min is size) { acc += r.min as size; };\n" - "\treturn acc: i32;\n" - "};\n", - 7 }, - - { "tagged_assign", - "package main;\n" - "type rep = struct { id: size, min: (void | size) };\n" - "export fn main() i32 = {\n" - "\tlet r = rep { id = 0: size, min = void };\n" - "\tr.id = 1;\n" - "\tr.min = 2: size;\n" - "\tlet acc = 0: size;\n" - "\tacc += r.id;\n" - "\tif (r.min is size) { acc += r.min as size; };\n" - "\treturn acc: i32;\n" - "};\n", - 3 }, - - { "tagged_str_field", - "package main;\n" - "type rep = struct { id: size, min: (void | size), name: (void | str) };\n" - "export fn main() i32 = {\n" - "\tlet b = rep { id = 0: size, min = void, name = void };\n" - "\tb.id = 2;\n" - "\tb.min = 9: size;\n" - "\tb.name = \"hey\";\n" - "\tlet acc = 0: size;\n" - "\tacc += b.id;\n" - "\tif (b.min is size) { acc += b.min as size; };\n" - "\tif (b.name is str) { acc += (b.name as str).len: size; };\n" - "\treturn acc: i32;\n" - "};\n", - 14 }, - - /* PG5h: field-wise construction through the inferred binding, - * boxed into a tagged union, match-extracted, is/as off the - * binding — the fold-5b inst_repeat consumer shape. */ - { "boxed_prebound", - "package main;\n" - "type inst_lit = rune;\n" - "type rep3 = struct { id: size, min: (void | size), max: (void | size) };\n" - "type inst = (inst_lit | rep3);\n" - "export fn main() i32 = {\n" - "\tlet r = rep3 { id = 0: size, min = void, max = void };\n" - "\tr.id = 1;\n" - "\tr.min = 2: size;\n" - "\tr.max = 5: size;\n" - "\tlet pre = 0: size;\n" - "\tif (r.min is size) { pre += r.min as size; };\n" - "\tif (pre != 2) { return 200 + pre: i32; };\n" - "\tlet v: inst = r;\n" - "\tlet acc = 0: size;\n" - "\tmatch (v) {\n" - "\tcase let ir: rep3 => {\n" - "\t\tacc += ir.id;\n" - "\t\tif (ir.min is size) { acc += ir.min as size; };\n" - "\t\tif (ir.max is size && ir.max as size == 5) { acc += 100; };\n" - "\t};\n" - "\tcase let l: inst_lit => { acc += 1; };\n" - "\t};\n" - "\treturn acc: i32;\n" - "};\n", - 103 }, - - /* Regression pin: the explicit annotation must stay on its prior - * (already byte-id) route — the normalization only fires for the - * inferred binding. */ - { "annotated_pin", - "package main;\n" - "type rep = struct { id: size, min: (void | size) };\n" - "export fn main() i32 = {\n" - "\tlet r: rep = rep { id = 0: size, min = void };\n" - "\tr.id = 1;\n" - "\tr.min = 2: size;\n" - "\tlet acc = 0: size;\n" - "\tacc += r.id;\n" - "\tif (r.min is size) { acc += r.min as size; };\n" - "\treturn acc: i32;\n" - "};\n", - 3 }, - - /* Plain nested s.f.g through the inferred binding: on master - * wwstage this RAN correctly but emitted different asm (the body- - * node planting took a divergent cgdot route) — pins byte-id. */ - { "nested_read", - "package main;\n" - "type in_ = struct { g: size };\n" - "type out_ = struct { f: in_ };\n" - "export fn main() i32 = {\n" - "\tlet s = out_ { f = in_ { g = 6: size } };\n" - "\treturn (s.f.g * 7): i32;\n" - "};\n", - 42 }, - - /* Trailing-`...` autofill (parse/expr.ww TK_ELLIPSIS, stash on - * s.op): same N_STRUCTLIT rhs kind, so the normalization must - * cover it — master wwstage link-failed this form too. */ - { "ellipsis_fill", - "package main;\n" - "type pt = struct { x: size, y: size };\n" - "export fn main() i32 = {\n" - "\tlet p = pt { x = 4: size, ... };\n" - "\treturn (p.x * 10 + p.y): i32;\n" - "};\n", - 40 }, - - /* Parenthesized literal: the parser unwraps parens (no N_PAREN - * node), so the rhs is the same bare N_STRUCTLIT — pins that the - * form-coverage claim holds at the parse layer. */ - { "paren_form", - "package main;\n" - "type pt = struct { x: size, y: size };\n" - "export fn main() i32 = {\n" - "\tlet p = (pt { x = 4: size, y = 2: size });\n" - "\treturn (p.x * 10 + p.y): i32;\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/ils_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/ils_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/ils_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. Pre-fix the unannotated rows emitted `MOVQ - * (SB), AX` on wwstage only, so the diff was non-empty (and - * the wwstage link failed). */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/ils_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/ils_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/ils_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "inferred_let_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, - "inferred_let_struct[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "inferred_let_struct: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("inferred_let_struct: all %d fixtures passed\n", total); - return 0; -} diff --git a/test/wcc/812_agg_assign_width.c b/test/wcc/812_agg_assign_width.c deleted file mode 100644 index 680fde86..00000000 --- a/test/wcc/812_agg_assign_width.c +++ /dev/null @@ -1,493 +0,0 @@ -/* - * 812_agg_assign_width — cstage and wwstage agree, byte-for-byte and - * at runtime, that an aggregate ASSIGN copies the WHOLE value (task - * #49, Family A of ken's silent-set triage, /tmp/ken_silent_triage.md). - * - * The bug: plain whole-aggregate reassignment `b = a` fell to the - * N_ASSIGN scalar tail in BOTH stages and copied ONE MOVQ — word 0 of - * a 16/24/48B struct (ken f49_min cs.s asm proof; fA_16b shows even a - * 16B {size,size} fails). Same class at three more positions: a - * struct-lit FIELD initialised from an ident source (`outer{.., r=r}`, - * ken f38b / x5f-h) word0-copied inside cg_structlit_fill; a deref - * place `*p = s` truncated to 8 bytes (#31-A); a module-let global - * `g = a` / `g = pt{...}` stored one word (scalar `MOVQ AX, g(SB)`). - * Tagged/match context was INCIDENTAL — the hole is tagged-independent - * and gate-blind (byte-identical wrongness), latent only because lib - * style is let-init (`let b = a` runs the #265/#268 full-width copy — - * the working sibling these fixes mirror). - * - * The fix (BOTH stages, converged byte-identical): ONE place-resolved - * mem-to-mem copy funnel — cg_aggcopy/aggcopy, extracted verbatim from - * the C1.25 assign-resolver tail — fed by aggarg_srcaddr (source addr → - * SI) and a LEAQ of the destination (BP slot or g(SB) symbol → BX), - * wired at: the N_ASSIGN ident-aggregate arm (local + global), the - * deref-place divert into the existing resolver aggregate arm, and the - * structlit-fill aggregate-field arm. Non-addressable aggregate rhs - * shapes die LOUD (rule 7) instead of silently truncating. - * - * MUTATION: every row below fails at master 7545bf7 (probed 2026-06-05, - * ken's matrix f49_min/f49b/f49c/fA_16b/f38b + impl-A probes pA_*: all - * non-zero exits, byte-id both stages — the class is gate-blind, so - * runtime readback is the only oracle). - * - * row | shape | want - * ---------------------+-------------------------------------+------ - * assign_16b | {size,size} b = a, x*10+y readback | 39 - * assign_24b_plain | {size,size,size} b = a, sum | 6 - * assign_48b | 6-field b = a, sum | 21 - * assign_odd_12b | {u32,u32,u32} 12B maxalign-4 (gA2) | 0 - * assign_match_bind | dst = s from a match binding over | 0 - * | (st|void), st has a tagged member | - * | (ken f49_min — the ORIGINAL #49 | - * | context, regression pin) | - * lit_field_from_ident | outer{tag, r = r} field fill (f38b) | 21 - * assign_str_field | {id, name:str} b = a, ladder | 0 - * assign_nested | {tag, inner{x,y}} b = a, sum | 6 - * arrelem_roundtrip | a[1] = b (kin, pre-wired #270-1b) | 15 - * | then cc = a[1] (the new read side) | - * assign_deref | *p = s, 24B struct (#31-A fold) w/ | 27 - * | guards either side of the pointee | - * assign_global | g = a then b = g (module-let dst | 36 - * | and src, both new arms) | - * assign_dot_source | b = o.i (N_DOT source via | 6 - * | aggarg_srcaddr) | - * assign_tuple | (size,size) u = t slot copy | 13 - * global_structlit | g = pt{...} (DST_GLOBAL fill wire) | 18 - * alias_named_assign | type row = st; b = a (ken R1/gA3b — | 0 - * | full alias chase, field-wise init) | - * alias_twolevel_assign| ali -> base -> struct chain (gA6) | 0 - * neighbor_guard | b = a with live guards either side | 0 - * | of b — over-copy smashes them | - */ -#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[] = { - { "assign_16b", - "package main;\n" - "type p2 = struct { x: size, y: size };\n" - "export fn main() i32 = {\n" - "\tlet a: p2 = p2 { x = 3: size, y = 9: size };\n" - "\tlet b: p2 = p2 { x = 0: size, y = 0: size };\n" - "\tb = a;\n" - "\treturn (b.x * 10 + b.y): i32;\n" - "};\n", - 39 }, - - { "assign_24b_plain", - "package main;\n" - "type st = struct { id: size, x: size, y: size };\n" - "export fn main() i32 = {\n" - "\tlet a: st = st { id = 1: size, x = 2: size, y = 3: size };\n" - "\tlet b: st = st { id = 0: size, x = 0: size, y = 0: size };\n" - "\tb = a;\n" - "\treturn (b.id + b.x + b.y): i32;\n" - "};\n", - 6 }, - - { "assign_48b", - "package main;\n" - "type big = struct { a: size, b: size, c: size, d: size, e: size, f: size };\n" - "export fn main() i32 = {\n" - "\tlet x: big = big { a = 1: size, b = 2: size, c = 3: size, d = 4: size, e = 5: size, f = 6: size };\n" - "\tlet y: big = big { a = 0: size, b = 0: size, c = 0: size, d = 0: size, e = 0: size, f = 0: size };\n" - "\ty = x;\n" - "\treturn (y.a + y.b + y.c + y.d + y.e + y.f): i32;\n" - "};\n", - 21 }, - - /* ken gA2 — odd size: 12B {u32,u32,u32}, maxalign 4. Pins the - * MOVQ-run + MOVL tail of the funnel (the fi.fsz slot-padded - * skew worry) — NOT a multiple of 8. Pre-fix at master: word0 - * copies a+b in one MOVQ, c lost → exit 3 (probed both stages). */ - { "assign_odd_12b", - "package main;\n" - "type tri = struct { a: u32, b: u32, c: u32 };\n" - "export fn main() i32 = {\n" - "\tlet x: tri = tri { a = 7: u32, b = 8: u32, c = 9: u32 };\n" - "\tlet y: tri = tri { a = 0: u32, b = 0: u32, c = 0: u32 };\n" - "\ty = x;\n" - "\tif (y.a != 7) { return 1; };\n" - "\tif (y.b != 8) { return 2; };\n" - "\tif (y.c != 9) { return 3; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* ken f49_min verbatim — the ORIGINAL #49 filing context: - * assign from a match binding, struct with a tagged member. - * Pre-fix exit 4 (dst.m's tag word never copied). */ - { "assign_match_bind", - "package main;\n" - "type st = struct { id: size, m: (void | size) };\n" - "type un = (st | void);\n" - "export fn main() i32 = {\n" - "\tlet src: un = st { id = 6: size, m = 8: size };\n" - "\tlet dst: st = st { id = 0: size, m = void };\n" - "\tmatch (src) {\n" - "\tcase let s: st => {\n" - "\t\tif (!(s.m is size)) { return 1; };\n" - "\t\tdst = s;\n" - "\t};\n" - "\tcase void => { return 2; };\n" - "\t};\n" - "\tif (dst.id != 6) { return 3; };\n" - "\tif (!(dst.m is size)) { return 4; };\n" - "\tif (dst.m as size != 8) { return 5; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* ken f38b shape — struct-lit FIELD from an ident source. Pre-fix - * exit: o.r.id copied (word0), x/y zero → 6. */ - { "lit_field_from_ident", - "package main;\n" - "type rep = struct { id: size, x: size, y: size };\n" - "type outer = struct { tag: size, r: rep };\n" - "export fn main() i32 = {\n" - "\tlet r: rep = rep { id = 6: size, x = 7: size, y = 8: size };\n" - "\tlet o: outer = outer { tag = 4: size, r = r };\n" - "\tif (o.tag != 4) { return 99; };\n" - "\treturn (o.r.id + o.r.x + o.r.y): i32;\n" - "};\n", - 21 }, - - /* 3-word str header inside the copied struct: the header words - * past .ptr are exactly what a word0 copy drops. Ladder form — - * `.len` in mixed arithmetic trips the PRE-EXISTING task-#26 - * typing divergence (cstage rejects), which is not this class. */ - { "assign_str_field", - "package main;\n" - "type named = struct { id: size, name: str };\n" - "export fn main() i32 = {\n" - "\tlet a: named = named { id = 7: size, name = \"hello\" };\n" - "\tlet b: named = named { id = 0: size, name = \"\" };\n" - "\tb = a;\n" - "\tif (b.id != 7) { return 1; };\n" - "\tif (b.name.len != 5) { return 2; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - { "assign_nested", - "package main;\n" - "type inner = struct { x: size, y: size };\n" - "type outer = struct { tag: size, i: inner };\n" - "export fn main() i32 = {\n" - "\tlet a: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size } };\n" - "\tlet b: outer = outer { tag = 0: size, i = inner { x = 0: size, y = 0: size } };\n" - "\tb = a;\n" - "\treturn (b.tag + b.i.x + b.i.y): i32;\n" - "};\n", - 6 }, - - /* a[i] = b was already wired (#270-1b INDEX-place word-copy) — - * this row pins the KIN pair: the pre-existing store plus the - * NEW ident-from-index read (`cc = a[1]` word0-copied pre-fix). */ - { "arrelem_roundtrip", - "package main;\n" - "type pt = struct { x: size, y: size, z: size };\n" - "export fn main() i32 = {\n" - "\tlet a: [2]pt = [pt { x = 0: size, y = 0: size, z = 0: size }, pt { x = 0: size, y = 0: size, z = 0: size }];\n" - "\tlet b: pt = pt { x = 4: size, y = 5: size, z = 6: size };\n" - "\ta[1] = b;\n" - "\tif (a[0].x != 0) { return 99; };\n" - "\tlet cc: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" - "\tcc = a[1];\n" - "\treturn (cc.x + cc.y + cc.z): i32;\n" - "};\n", - 15 }, - - /* #31-A fold: deref place from an addressable aggregate ident — - * pre-fix stored 8 bytes (probe pA_deref exit 2). Guards either - * side of the pointee slot per ken gA4: an over-wide copy through - * the resolved place smashes one. */ - { "assign_deref", - "package main;\n" - "type pt = struct { x: size, y: size, z: size };\n" - "export fn main() i32 = {\n" - "\tlet guard1: size = 111: size;\n" - "\tlet d: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" - "\tlet guard2: size = 222: size;\n" - "\tlet s: pt = pt { x = 8: size, y = 9: size, z = 10: size };\n" - "\tlet p: *pt = &d;\n" - "\t*p = s;\n" - "\tif (guard1 != 111) { return 98; };\n" - "\tif (guard2 != 222) { return 97; };\n" - "\treturn (d.x + d.y + d.z): i32;\n" - "};\n", - 27 }, - - /* Module-let global as BOTH destination (`g = a`, LEAQ g(SB) - * dest) and source (`b = g`, aggarg_srcaddr global arm). */ - { "assign_global", - "package main;\n" - "type pt = struct { x: size, y: size, z: size };\n" - "let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" - "export fn main() i32 = {\n" - "\tlet a: pt = pt { x = 11: size, y = 12: size, z = 13: size };\n" - "\tg = a;\n" - "\tif (g.z != 13) { return 99; };\n" - "\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" - "\tb = g;\n" - "\treturn (b.x + b.y + b.z): i32;\n" - "};\n", - 36 }, - - { "assign_dot_source", - "package main;\n" - "type inner = struct { x: size, y: size, z: size };\n" - "type outer = struct { tag: size, i: inner };\n" - "export fn main() i32 = {\n" - "\tlet o: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size, z = 4: size } };\n" - "\tlet b: inner = inner { x = 0: size, y = 0: size, z = 0: size };\n" - "\tb = o.i;\n" - "\treturn (b.x + b.z): i32;\n" - "};\n", - 6 }, - - /* Tuple ident reassign rides the same funnel (8B/elem slots). */ - { "assign_tuple", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet t: (size, size) = (4: size, 9: size);\n" - "\tlet u: (size, size) = (0: size, 0: size);\n" - "\tu = t;\n" - "\treturn (u.0 + u.1): i32;\n" - "};\n", - 13 }, - - /* Global structlit reassign — newly wired through the DST_GLOBAL - * fill (pre-fix: one scalar MOVQ of AX≈0 to g(SB)). */ - { "global_structlit", - "package main;\n" - "type pt = struct { x: size, y: size, z: size };\n" - "let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" - "export fn main() i32 = {\n" - "\tg = pt { x = 5: size, y = 6: size, z = 7: size };\n" - "\treturn (g.x + g.y + g.z): i32;\n" - "};\n", - 18 }, - - /* ken R1 (gA3b): ALIAS-named aggregate — the funnel dispatch must - * FULL-chase the alias (type_chase_named / chased tinfo); a - * single peel left cstage at TY_NAMED, fell to the scalar tail - * and word0-copied while wwstage copied full-width (live cs≠ww). - * FIELD-WISE init: the struct-LIT spelling can't pin the ww side - * (it louds earlier at the pre-existing task-#7 aggregate-let - * bound — the #5 alias-arc's hole, not this funnel's). */ - { "alias_named_assign", - "package main;\n" - "type st = struct { a: size, b: size };\n" - "type row = st;\n" - "export fn main() i32 = {\n" - "\tlet a: row;\n" - "\ta.a = 4; a.b = 9;\n" - "\tlet b: row;\n" - "\tb.a = 0; b.b = 0;\n" - "\tb = a;\n" - "\tif (b.a != 4) { return 1; };\n" - "\tif (b.b != 9) { return 2; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* ken gA6: TWO-level alias chain (`ali -> base -> struct`) — pins - * that the dispatch is a full CHASE, not a deeper fixed peel. */ - { "alias_twolevel_assign", - "package main;\n" - "type ali = base;\n" - "type base = struct { a: size, b: size, c: size };\n" - "export fn main() i32 = {\n" - "\tlet a: ali;\n" - "\ta.a = 4; a.b = 9; a.c = 13;\n" - "\tlet b: ali;\n" - "\tb.a = 0; b.b = 0; b.c = 0;\n" - "\tb = a;\n" - "\tif (b.a != 4) { return 1; };\n" - "\tif (b.b != 9) { return 2; };\n" - "\tif (b.c != 13) { return 3; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* Neighbor guards on both sides of the destination slot: a copy - * WIDER than size(T) (the inverse regression) smashes one. */ - { "neighbor_guard", - "package main;\n" - "type pt = struct { x: size, y: size, z: size };\n" - "export fn main() i32 = {\n" - "\tlet guard1: size = 111: size;\n" - "\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" - "\tlet guard2: size = 222: size;\n" - "\tlet a: pt = pt { x = 1: size, y = 2: size, z = 3: size };\n" - "\tb = a;\n" - "\tif (guard1 != 111) { return 1; };\n" - "\tif (guard2 != 222) { return 2; };\n" - "\tif (b.x + b.y + b.z != 6) { return 3; };\n" - "\treturn 0;\n" - "};\n", - 0 }, -}; - -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/aggas_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/aggas_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/aggas_%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; -} - -/* asm_byte_identical — generate .s via cstage's w6c and wwstage's - * w6c_ww and diff. The class is gate-blind (both stages were wrong - * identically), so byte-id here pins that the CONVERGED fix stays - * converged. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/aggas_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/aggas_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/aggas_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "agg_assign_width: 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, - "agg_assign_width[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "agg_assign_width: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("agg_assign_width: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/814_def_arr_infer_len.c b/test/wcc/814_def_arr_infer_len.c deleted file mode 100644 index 8fe1678a..00000000 --- a/test/wcc/814_def_arr_infer_len.c +++ /dev/null @@ -1,366 +0,0 @@ -/* - * 814_def_arr_infer_len — cstage and wwstage agree, byte-for-byte and at - * runtime, that a `def NAME: [_]T = [...]` array infers its length from - * the initialiser's element count — the `def` twin of the `let` path - * pinned by 684 (task #11 / #5; #7 wired only the let decl path). - * - * The bug (BOTH stages, byte-id identical): the `[_]` infer sentinel - * (alen=0 / nil length-child) was stamped on the `let` decl path but NOT - * on the `def` decl path. A `def [_]int = [10,20,30]` stayed sized 0, - * emitted no/short DATA, and indexed reads (`TAB[1]`) / `.len` returned - * garbage with no diagnostic (rule-7 silent miscompile). - * - * The fix (CHECKER ONLY — cgen already keys stride/length/DATA off the - * stamped length, so `def [3]int` works today): - * - cstage cmd/wcc/check.c N_DEF pass-2: mirror the module N_LET path — - * if d->type is `[_]T` (TY_ARRAY, alen==0), chase the rhs type, - * rebuild type_array(.., iu->alen), and re-point BOTH d->type AND the - * installed SK_DEF Sym (an indexed read resolves the def through its - * Sym, so the Sym repoint is required). - * - wwstage selfhost/cmd/wcc/check.ww N_DEF arm: call inferarraylen(c,d) - * BEFORE resolvewalk stamps d.lhs's tinfo (the N_TARRAY AST is the - * SSoT; no Sym repoint needed). A `def [_]T` whose init is not an - * array literal can't infer → LOUD error, never a silent length-0. - * - * `def` is MODULE-SCOPE ONLY in both stages, so there is exactly one def - * site per stage and every element type rides the single inference arm - * (it is gated only on TY_ARRAY+alen==0, agnostic to the element type). - * - * SCOPE (corrected at impl-probe — the inference is complete, but two - * adjacent shapes hit SEPARATE PRE-EXISTING cgen gaps that fail the SAME - * way on an explicit-length `def [N]T`, so they are NOT #11 and are - * excluded here; each is filed as its own task): - * - `def`-array `.len`/`.ptr` on wwstage falls to the SB fallback and - * emits `MOVQ len(SB)` (w6l: undefined reference). cstage handles it - * (so cstage vs wwstage asm also DIVERGES) — the wwstage cgdot - * def-array arm is missing, the def twin of #7's let-global cgdot - * fix. Every `.len` row is dropped for this reason. - * - `def [_]str` / `def [N]str` emit no DATA symbol (w6l: undefined - * reference, BOTH stages) — the #270 / str-slice-array-element DATA - * lineage. Every str row is dropped for this reason. - * The pin therefore exercises INDEX reads across int / u8 / multi-dim, - * which is exactly the shape #11 corrupts and is mutation-sensitive - * (a collapse-to-0 def lays no DATA row, so the indexed read strikes - * garbage / segv). - * - * The NEG row (`def [_]int = 5`, non-array init) proves the loud-error - * arm fires (rule-7: never a silent zero-length array). - * - * row | shape | want - * --------------+--------------------------------------------+----- - * def_int_i1 | def [_]int=[10,20,30], TAB[1] (ken oracle)| 20 - * def_int_last | def [_]int=[10,20,30], TAB[2] (full len) | 30 - * def_one_elem | def [_]int=[7], A[0] (1-elem edge) | 7 - * def_u8_first | def [_]u8=[1..5], B[0] (narrow stride) | 1 - * def_u8_last | def [_]u8=[1..5], B[4] | 5 - * def_2d_first | def [_][2]int=[[1,2],[3,4],[5,6]], D[0][0] | 1 - * def_2d_last | def [_][2]int=[..], D[2][1] | 6 - * NEG def_noarr | def [_]int = 5; (non-array init) | BUILD FAIL - */ -#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 want; }; - -static const struct row rows[] = { - /* INDEX reads only — the shape #11 corrupts. Reading the LAST - * element of an N-element def proves the inference stamped the full - * length: a collapse-to-0 lays no DATA row at that offset, so the - * read strikes garbage / segv. This is mutation-sensitive without - * `.len` (whose def-array codegen is a SEPARATE pre-existing gap — - * see the header). */ - { "def_int_i1", - "package main;\n" - "def TAB: [_]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn TAB[1]: i32;\n" - "};\n", - 20 }, - - { "def_int_last", - "package main;\n" - "def TAB: [_]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn TAB[2]: i32;\n" - "};\n", - 30 }, - - /* 1-element edge — the minimal non-empty count. */ - { "def_one_elem", - "package main;\n" - "def A: [_]int = [7];\n" - "export fn main() i32 = {\n" - "\treturn A[0]: i32;\n" - "};\n", - 7 }, - - { "def_u8_first", - "package main;\n" - "def B: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n" - "export fn main() i32 = {\n" - "\treturn B[0]: i32;\n" - "};\n", - 1 }, - - /* narrow (1B) stride — the last element of 5 proves both the stride - * and the inferred length. */ - { "def_u8_last", - "package main;\n" - "def B: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n" - "export fn main() i32 = {\n" - "\treturn B[4]: i32;\n" - "};\n", - 5 }, - - /* multi-dim: outer `[_]` infers 3 from the row count, element type - * is the explicit `[2]int`. The def static-init DATA path lays the - * full 3x2 aggregate (the `let` runtime-store path for this shape is - * separately #270-1c-blocked, so this is def-only today). */ - { "def_2d_first", - "package main;\n" - "def D: [_][2]int = [[1, 2], [3, 4], [5, 6]];\n" - "export fn main() i32 = {\n" - "\treturn D[0][0]: i32;\n" - "};\n", - 1 }, - - { "def_2d_last", - "package main;\n" - "def D: [_][2]int = [[1, 2], [3, 4], [5, 6]];\n" - "export fn main() i32 = {\n" - "\treturn D[2][1]: i32;\n" - "};\n", - 6 }, -}; - -/* `def [_]T` whose initialiser is not an array literal can't infer its - * length — both stages must FAIL the build (loud diagnostic, not a silent - * zero-length array). `def` requires an `= value` init (parser), so the - * no-init case can't reach the checker; the non-array init is the only - * negative shape. */ -static const char *neg[] = { - /* non-array initialiser */ - "package main;\n" - "def TAB: [_]int = 5;\n" - "export fn main() i32 = { return 0; };\n", -}; - -/* Per-neg expected diagnostic body (parallel to neg[]); shared by both - * stages (cstage prepends file:line:col, wwstage does not — #20). */ -static const char *neg_diag[] = { - "[_]T needs an array-literal initialiser", -}; - -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/dail_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/dail_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dail_%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; -} - -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 `def [_]T` that can't infer must error on - * `driver`; returns 0 when the build FAILS *and* emits `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 tmpdir[64], s[128], outbin[128], errf[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/dailn_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/dailn_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dailn_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. - * The bug is byte-id-BLIND (both stages emitted identical wrong asm), so - * this is a rule-10 convergence invariant, not a mutation detector. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dail_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/dail_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/dail_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "def_arr_infer_len: 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, - "def_arr_infer_len[%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], - neg_diag[i], 100 + i) != 0) { - fprintf(stderr, - "def_arr_infer_len[%s][neg%d]: built ok, " - "expected a loud error\n", - drivers[d].name, i); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "def_arr_infer_len: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("def_arr_infer_len: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/816_def_arr_len.c b/test/wcc/816_def_arr_len.c deleted file mode 100644 index 2aacb762..00000000 --- a/test/wcc/816_def_arr_len.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * 816_def_arr_len — cstage and wwstage agree, byte-for-byte and at runtime, - * that `.len` on a module-level `def NAME: [N]T = [...]` array is the static - * element count (an immediate `MOVQ $alen, AX`). - * - * The bug (GAP-A.len, WWSTAGE-ONLY): the #7 let-global `.len` cgdot arm in - * selfhost/cmd/wcc/cgenexpr.ww gated on letvartnode (c.lets only). A `def` - * lives in c.defs, missed that arm, and fell through to the module-qualified - * SB fallback that mis-emitted `MOVQ len(SB)` → w6l: undefined reference to - * 'len' (a build/link failure, not silent). cstage cgen.c already emitted the - * immediate, so the two stages also DIVERGED. The fix adds a def-twin `.len` - * arm using defvartnode (the def-side mirror of letvartnode) — `.len` ONLY. - * - * SCOPE: `.len` only. `.ptr` (cstage itself emits LEAQ (BP), task GAP-A.ptr) - * and `.cap` (arrays have no cap; cstage rejects, wwstage silently emits - * garbage, task GAP-A.cap) are SEPARATE bugs, not mirrored here. - * - * Covers BOTH the explicit-length `def [3]int` and the inferred `def [_]int` - * (#11-stamped) so the deftnode/.rhs immediate path is exercised for the - * inference case too. - * - * row | shape | want - * ---------------+---------------------------------------------+----- - * explicit_len | def A: [3]int=[10,20,30], A.len | 3 - * infer_len | def B: [_]int=[1,2,3,4], B.len | 4 - * sum | A.len + B.len (ken oracle) | 7 - * one_elem | def C: [_]int=[9], C.len | 1 - * u8_len | def D: [_]u8=[1u8..5u8], D.len | 5 - */ -#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[] = { - { "explicit_len", - "package main;\n" - "def A: [3]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn A.len: i32;\n" - "};\n", - 3 }, - - { "infer_len", - "package main;\n" - "def B: [_]int = [1, 2, 3, 4];\n" - "export fn main() i32 = {\n" - "\treturn B.len: i32;\n" - "};\n", - 4 }, - - /* ken oracle: explicit + inferred together = 7. */ - { "sum", - "package main;\n" - "def A: [3]int = [10, 20, 30];\n" - "def B: [_]int = [1, 2, 3, 4];\n" - "export fn main() i32 = {\n" - "\treturn (A.len: i32) + (B.len: i32);\n" - "};\n", - 7 }, - - { "one_elem", - "package main;\n" - "def C: [_]int = [9];\n" - "export fn main() i32 = {\n" - "\treturn C.len: i32;\n" - "};\n", - 1 }, - - /* narrow stride — `.len` is a count, independent of element width. */ - { "u8_len", - "package main;\n" - "def D: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n" - "export fn main() i32 = {\n" - "\treturn D.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/dal_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/dal_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dal_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. - * Post-fix both stages emit `MOVQ $alen, AX`; the rule-10 convergence - * invariant. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dal_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/dal_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/dal_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "def_arr_len: 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, - "def_arr_len[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "def_arr_len: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("def_arr_len: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/817_arr_cap_reject.c b/test/wcc/817_arr_cap_reject.c deleted file mode 100644 index 622ddb01..00000000 --- a/test/wcc/817_arr_cap_reject.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * 817_arr_cap_reject — a fixed-size array has NO capacity word; `.cap` on an - * array is INVALID ww and BOTH stages must LOUDLY REJECT at check time - * (GAP-A.cap, task #12; drew ruling .ai/drew-gapa-ptr-ruling.md). `.len` - * (GAP-A.len, 7b0e09e) AND `.ptr` (≡ &A[0], the sanctioned ww backing- - * pointer spelling, task #13 divergence-record) stay VALID on an array — - * only `.cap` is rejected. - * - * The bug (each stage misbehaved DIFFERENTLY on the same invalid construct, - * hence both-stage): - * - cstage check.c typed `.cap` on a TY_ARRAY as i32 → cgen then hard- - * rejected with a generic "unsupported field-read shape" (loud but at - * the WRONG layer, vague message). - * - wwstage check.ww stamped `.cap` → cgen link-error (def-global - * `A.cap` → w6l undefined 'cap') or SILENT garbage (local `a.cap`→30). - * - * The fix (BOTH checkers, one `.cap`-on-array gate each, byte-identical - * diagnostic body): reject early with "no field 'cap' on a fixed-size array - * (arrays have no capacity; use .len)". Checker-only: the reject makes the - * buggy cgen `.cap` array paths unreachable (close by construction). - * - * Mutation-sanity: the genuinely-silent pre-fix case is wwstage's LOCAL - * .cap, which BUILT and returned garbage (30) — neg_local_cap is the row - * with real mutation power (built-ok pre-fix → caught here). The other - * neg rows were already loud pre-fix but at the WRONG layer (cstage cgen - * vague-reject on use; wwstage global link-error); this commit converges - * all of them onto one early checker diagnostic. - * - * neg row | shape | gate - * -----------------+----------------------------------------+---------- - * neg_local_cap | local [3]int, a.cap | build FAIL - * neg_def_cap | def A:[3]int, A.cap | build FAIL - * neg_let_glob_cap | let G:[3]int (module), G.cap | build FAIL - * neg_infer_cap | def A:[_]int, A.cap (#11 infer path) | build FAIL - * - * pos row | shape | want - * -----------------+----------------------------------------+------ - * pos_local_len | local [3]int, a.len | 3 - * pos_def_len | def A:[_]int, A.len | 3 - * pos_local_ptr | local [3]int, *a.ptr (sanctioned idiom) | 10 - * - * The pos_local_ptr row pins that `.ptr`-on-array stays VALID (cgen - * unchanged in this commit; the def-global `.ptr` cgen fix + its build+run - * pin are task #11 / test 818, NOT here — so this control uses a LOCAL - * array `.ptr`, which is correct in both stages today). - */ -#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 want; }; - -static const struct row rows[] = { - { "pos_local_len", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [3]int = [10, 20, 30];\n" - "\treturn a.len: i32;\n" - "};\n", - 3 }, - - { "pos_def_len", - "package main;\n" - "def A: [_]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn A.len: i32;\n" - "};\n", - 3 }, - - /* `.ptr`-on-array is the sanctioned ww spelling for &a[0] (task #13). - * LOCAL array `.ptr` is correct in BOTH stages today (cgen unchanged - * here); the def-global `.ptr` cgen fix is task #11 / test 818. */ - { "pos_local_ptr", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [3]int = [10, 20, 30];\n" - "\tlet p: *int = a.ptr;\n" - "\treturn (*p): i32;\n" - "};\n", - 10 }, -}; - -/* `.cap` on a fixed array — both stages must FAIL the build (loud checker - * diagnostic, not silent garbage / link-error). */ -static const char *neg[] = { - /* neg_local_cap */ - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [3]int = [1, 2, 3];\n" - "\treturn a.cap: i32;\n" - "};\n", - /* neg_def_cap */ - "package main;\n" - "def A: [3]int = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn A.cap: i32;\n" - "};\n", - /* neg_let_glob_cap */ - "package main;\n" - "let G: [3]int = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn G.cap: i32;\n" - "};\n", - /* neg_infer_cap — the #11 [_] infer path */ - "package main;\n" - "def A: [_]int = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn A.cap: i32;\n" - "};\n", -}; - -/* Per-neg expected diagnostic body (parallel to neg[]); all four rows hit - * the same "no field 'cap' on a fixed-size array" path on both stages. */ -static const char *neg_diag[] = { - "no field 'cap' on a fixed-size array", - "no field 'cap' on a fixed-size array", - "no field 'cap' on a fixed-size array", - "no field 'cap' on a fixed-size array", -}; - -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/acr_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/acr_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/acr_%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; -} - -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 — `.cap` on an array must error on `driver`; returns 0 - * when the build FAILS *and* emits `diag`. A crash (segfault) wraps as a - * nonzero "w6c failed" with no diagnostic, so the substring distinguishes - * it from a clean reject (#20). */ -static int -build_should_fail(const char *driver, const char *src, const char *diag, - int i) -{ - char tmpdir[64], s[128], outbin[128], errf[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/acrn_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/acrn_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/acrn_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/acr_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/acr_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/acr_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "arr_cap_reject: 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_cap_reject[%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], - neg_diag[i], 100 + i) != 0) { - fprintf(stderr, - "arr_cap_reject[%s][neg%d]: built ok, " - "expected a loud error\n", - drivers[d].name, i); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "arr_cap_reject: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("arr_cap_reject: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/818_arr_ptr_global.c b/test/wcc/818_arr_ptr_global.c deleted file mode 100644 index 91b0d605..00000000 --- a/test/wcc/818_arr_ptr_global.c +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 818_arr_ptr_global — `.ptr` on a fixed-size array is the sanctioned ww - * spelling for &A[0] (task #13 divergence-record, drew ruling - * .ai/drew-gapa-ptr-ruling.md). For a LOCAL array the backing pointer is the - * frame slot (LEAQ off(BP)); for a module GLOBAL array (let or def) it is the - * symbol address (LEAQ name(SB)). cstage cgen emitted LEAQ off(BP) for BOTH - * flavours — and for a global, off==0, so `*A.ptr` read the frame's first - * slot = stack garbage (the GAP-A.ptr silent miscompile, task #11; the - * off==0/global base-selection class of #231/#48). - * - * The fix (cmd/w6c/cgen.c N_DOT array .ptr arm + selfhost cgenexpr.ww def - * arm): when off==0 and the operand is a module global (let_islet || - * def_isarraydef), emit LEAQ name(SB) instead of LEAQ (BP), mirroring the - * def-array index base at cgen.c:4367. wwstage's LET-global `.ptr` was - * already correct (LEAQ name(SB)); only the DEF-global arm was missing. - * After this commit local / let-global / def-global `.ptr` are byte- - * IDENTICAL across both stages. - * - * row | shape | want - * -----------------+-----------------------------------------+------ - * def_glob_ptr | def A:[3]int, *A.ptr (THE regression) | 10 - * let_glob_ptr | let G:[3]int, *G.ptr (BP→SB converge) | 5 - * local_ptr | local [3]int, (a.ptr)[2] (unchanged) | 3 - * - * def_glob_ptr / let_glob_ptr are the mutation-sane rows: pre-fix they read - * stack garbage. local_ptr guards the 14 live local-`.ptr` consumers (cgen - * unchanged for locals). A byte-id row pins cstage==wwstage `.s` for the - * def-global `.ptr` source (the convergence the fix delivers). - */ -#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[] = { - /* def_glob_ptr — THE regression pin: pre-fix `*A.ptr` reads stack - * garbage because cstage LEAQ (BP)'d the def-global base. */ - { "def_glob_ptr", - "package main;\n" - "def A: [3]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\tlet p: *int = A.ptr;\n" - "\treturn (*p): i32;\n" - "};\n", - 10 }, - - /* let_glob_ptr — pins the cstage BP→SB convergence (wwstage was - * already right for let-global, so this closes the latent cs≠ww). */ - { "let_glob_ptr", - "package main;\n" - "let G: [3]int = [5, 6, 7];\n" - "export fn main() i32 = {\n" - "\tlet p: *int = G.ptr;\n" - "\treturn (*p): i32;\n" - "};\n", - 5 }, - - /* local_ptr — the 14-site regression guard; cgen unchanged for a - * LOCAL array (off!=0 → LEAQ off(BP)). p[2] strides element 2. */ - { "local_ptr", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [3]int = [1, 2, 3];\n" - "\tlet p: *int = a.ptr;\n" - "\treturn p[2]: i32;\n" - "};\n", - 3 }, -}; - -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/apg_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/apg_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/apg_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/apg_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/apg_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/apg_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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_ptr_global: 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_ptr_global[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "arr_ptr_global: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("arr_ptr_global: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/819_def_str_table.c b/test/wcc/819_def_str_table.c deleted file mode 100644 index 5bd298cf..00000000 --- a/test/wcc/819_def_str_table.c +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 819_def_str_table — `def C: [N]str = [...]` module-level str-array static - * init + element load (#8 / GAP-B, #270 family). A `let [N]str` global static- - * inits correctly (#18); the def-twin did NOT — both stages emitted the index - * ref `LEAQ main.C(SB)` but NEVER emitted the backing DATA block, so w6l failed - * with `undefined reference to 'main.C'` (LOUD, symmetric, both stages — not - * silent, not cs≠ww). - * - * ROOT: emit_strarray_data / emitstrarraydata (the str-array DATAW header + - * per-element A_DATAR ptr-reloc emitter) was gated to the "DATAW" directive - * (let only). A def array routes through emit_array_data(..,"DATA",..) so the - * gate skipped it → no DATA block. The str backing must live in DATAW anyway - * (w6a requires a DATAR reloc-holder be a DATAW slot, asm.c:362); def - * immutability is checker-enforced, independent of the section bit. The fix - * drops the directive gate in BOTH stages so a def str-array rides the same - * DATAW+DATAR emitter as let. int-defs are plain DATA (no reloc) — unaffected. - * - * row | shape | want - * -----------------+---------------------------------------------+------ - * def_str_len | def C:[2]str; C.len | 2 - * def_str_elem0 | def C:[2]str=["ab","cde"]; C[0].len | 2 - * def_str_elem1 | def C:[2]str=["ab","cde"]; C[1].len | 3 - * def_str_content | def C:[2]str=["ab","cde"]; C[1][0] ('c') | 99 (DATAR reloc - * | | resolves to the - * | | right _S_ rodata) - * def_str_multi | def C:[3]str=["a","bb","ccc"]; C[2].len | 3 - * def_str_varbind | def C:[3]str; let s=C[2]; s.len | 3 - * let_str_elem1 | let C:[2]str=["ab","cde"]; C[1].len (CTRL) | 3 (#18, already - * | | works; no-regr) - * - * Each def row fails-to-LINK pre-fix (mutation-sane: delete the gate-drop and - * the def rows go back to w6l undefined). let_str_elem1 is the #18 regression - * guard. A byte-id pass pins cstage==wwstage `.s` for every row (the both- - * wrong-identical convergence: pre-fix both omit the block, post-fix both emit - * the same DATAW+DATAR). - */ -#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[] = { - /* def_str_len — `.len` on a def str-array (static element count; works - * via the def-twin .len arm even pre-#8, but pinned here for the set). */ - { "def_str_len", - "package main;\n" - "def C: [2]str = [\"ab\", \"cde\"];\n" - "export fn main() i32 = {\n" - "\treturn C.len: i32;\n" - "};\n", - 2 }, - - /* def_str_elem0 — THE regression: load element 0, read its .len. - * Pre-#8 the def DATA block is never emitted → w6l undefined main.C. */ - { "def_str_elem0", - "package main;\n" - "def C: [2]str = [\"ab\", \"cde\"];\n" - "export fn main() i32 = {\n" - "\treturn C[0].len: i32;\n" - "};\n", - 2 }, - - /* def_str_elem1 — element 1 .len (ken oracle row). */ - { "def_str_elem1", - "package main;\n" - "def C: [2]str = [\"ab\", \"cde\"];\n" - "export fn main() i32 = {\n" - "\treturn C[1].len: i32;\n" - "};\n", - 3 }, - - /* def_str_content — C[1][0] is 'c' (99). Proves the per-element A_DATAR - * ptr-reloc resolves to the right _S_ rodata row, not just that a block - * exists. */ - { "def_str_content", - "package main;\n" - "def C: [2]str = [\"ab\", \"cde\"];\n" - "export fn main() i32 = {\n" - "\treturn C[1][0]: i32;\n" - "};\n", - 99 }, - - /* def_str_multi — 3-element def array, element 2 .len. */ - { "def_str_multi", - "package main;\n" - "def C: [3]str = [\"a\", \"bb\", \"ccc\"];\n" - "export fn main() i32 = {\n" - "\treturn C[2].len: i32;\n" - "};\n", - 3 }, - - /* def_str_varbind — bind an element to a let, then read .len (ken - * matrix var-bind form: a full 24B str-header element copy). */ - { "def_str_varbind", - "package main;\n" - "def C: [3]str = [\"x\", \"yy\", \"zzz\"];\n" - "export fn main() i32 = {\n" - "\tlet s: str = C[2];\n" - "\treturn s.len: i32;\n" - "};\n", - 3 }, - - /* let_str_elem1 — CONTROL: the #18 let-global str-array (already works). - * Guards against the gate-drop regressing the let path. */ - { "let_str_elem1", - "package main;\n" - "let C: [2]str = [\"ab\", \"cde\"];\n" - "export fn main() i32 = {\n" - "\treturn C[1].len: i32;\n" - "};\n", - 3 }, -}; - -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/dst_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/dst_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dst_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dst_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/dst_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/dst_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "def_str_table: 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, - "def_str_table[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "def_str_table: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("def_str_table: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/820_arr_zero_vs_infer.c b/test/wcc/820_arr_zero_vs_infer.c deleted file mode 100644 index b7a1e112..00000000 --- a/test/wcc/820_arr_zero_vs_infer.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * 820_arr_zero_vs_infer — an EXPLICIT zero/short fixed-size array over-filled - * by its initializer (`[0]int = [1,2]`, `[2]int = [1,2,3]`) is a LOUD length- - * mismatch on BOTH stages; an INFER `[_]` still infers its length from the - * initializer (task #9; ken oracle .ai/ken-9-oracle.md, rob spec - * .ai/rob-9-spec.md). - * - * The bug: post-resolve_type, both `[0]` and `[_]` collapse to alen==0 — the - * Type loses the distinction. The #71 over-fill diagnostic was suppressed for - * alen==0, so `[0]int = [1,2]` slipped past and each stage misbehaved - * DIFFERENTLY (byte-id-blind): - * - cstage silently RESIZED [0]→[2] (exit 2), or for the `def`/`let` cases - * resized too. - * - wwstage kept [0] and OOB-read / SEGFAULTed (exit 8 / 139), or resized - * the local (exit 2) — inconsistent across local vs module. - * - * The fix (one both-stage CHECKER commit): the AST RETAINS the distinction the - * Type loses — an infer `[_]` leaves the N_TARRAY length-child NULL, an - * explicit `[N]` (incl `[0]`) carries an N_INTLIT. cstage gates the four - * infer-resize / no-init sites on is_infer_arr() and drops the - * `alen > 0` exemption at the over-fill check; wwstage's shared count-gate - * checkarrlitfits fires whenever `arrtn.rhs != nil`. So an explicit `[N]=[init]` - * with count>N louds in EVERY context, INCLUDING N==0, before codegen. - * - * Mutation-sanity: every neg `[0]` row BUILT+RAN pre-fix (silent resize / OOB); - * it must now FAIL to build (the rows assert build-FAIL, which only holds - * post-fix). def_two_overfill (`[2]=[1,2,3]`) is the #71 N>0 regression guard. - * - * neg row | shape | gate - * -------------------+------------------------------------+---------- - * def_zero_overfill | def X:[0]int=[1,2] | build FAIL - * let_zero_overfill | let X:[0]int=[1,2] (module) | build FAIL - * local_zero_overfill| local [0]int=[1,2] | build FAIL - * def_two_overfill | def X:[2]int=[1,2,3] (#71 guard) | build FAIL - * str_zero_overfill | def X:[0]str=["a"] (elem-agnostic) | build FAIL - * - * pos row | shape | want - * ------------+--------------------------------+------ - * infer_ctl | def X:[_]int=[10,20]; X[1] | 20 (#11 infer works) - * empty_zero | let X:[0]int=[]; return 7 | 7 (legit empty array) - * infer_len | let X:[_]int=[1,2,3]; X.len | 3 (infer unaffected) - */ -#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; -} - -/* beid — include this row in the cstage-vs-wwstage byte-id sweep. Both the - * empty_zero global (`let X:[0]int=[]`) and the empty_zero_local row now - * converge byte-identically (beid=1): #15 closed the empty-`[0]T` cgen - * divergence — wwstage's spurious zero-width `DATAW main.X(SB),""` (cstage - * omits a zero-byte global) and its over-allocated `$16` local frame (cstage - * `$0` — a zero-length array reserves no slot) are both gated on the array - * being non-empty. See .ai/rob-15-spec.md: cgen.ww emitletdataw gates the - * array DATAW on `sz > 0`; cgenutil.ww slotsize returns 0 for a TY_ARRAY of - * alen==0. Both forms still run 7. */ -struct row { const char *label; const char *src; int want; int beid; }; - -static const struct row rows[] = { - /* infer_ctl — `[_]` still infers length from the initializer (#11). */ - { "infer_ctl", - "package main;\n" - "def X: [_]int = [10, 20];\n" - "export fn main() i32 = {\n" - "\treturn X[1]: i32;\n" - "};\n", - 20, 1 }, - - /* empty_zero — a real zero-length array (`[0]int = []`) stays VALID. - * beid=1: #15 closed the empty-array-global DATAW divergence. */ - { "empty_zero", - "package main;\n" - "let X: [0]int = [];\n" - "export fn main() i32 = {\n" - "\treturn 7;\n" - "};\n", - 7, 1 }, - - /* empty_zero_local — a LOCAL zero-length array (`[0]int = []`) reserves - * no frame slot (#15: cstage `$0`, wwstage was `$16`). beid=1. */ - { "empty_zero_local", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet x: [0]int = [];\n" - "\treturn 7;\n" - "};\n", - 7, 1 }, - - /* void_local — a zero-SIZE (not zero-length) local: `done = void` sizes - * 0 via slotsize, but cstage cglet defaults a non-composite local to an - * 8B slot. #15 dropped localreserve's sub-8 floor, which had masked this - * — letslotsize now floors a void local to 8 (cstage parity). beid=1: a - * regression here (void slot 0) collides with the spilled param. */ - { "void_local", - "package main;\n" - "type done = void;\n" - "export fn main() i32 = {\n" - "\tlet d: done;\n" - "\tlet a: i32 = 7;\n" - "\treturn a;\n" - "};\n", - 7, 1 }, - - /* infer_len — `[_]` infer is unaffected, `.len` reads the real count. */ - { "infer_len", - "package main;\n" - "let X: [_]int = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn X.len: i32;\n" - "};\n", - 3, 1 }, -}; - -/* An EXPLICIT `[N]int = [init]` with init-count > N — both stages must FAIL - * the build (loud over-fill diagnostic, not silent resize / OOB). */ -static const char *neg[] = { - /* def_zero_overfill */ - "package main;\n" - "def X: [0]int = [1, 2];\n" - "export fn main() i32 = {\n" - "\treturn X[1]: i32;\n" - "};\n", - /* let_zero_overfill */ - "package main;\n" - "let X: [0]int = [1, 2];\n" - "export fn main() i32 = {\n" - "\treturn X[1]: i32;\n" - "};\n", - /* local_zero_overfill */ - "package main;\n" - "export fn main() i32 = {\n" - "\tlet X: [0]int = [1, 2];\n" - "\treturn X[1]: i32;\n" - "};\n", - /* def_two_overfill — the #71 N>0 regression guard, must stay loud */ - "package main;\n" - "def X: [2]int = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn X[1]: i32;\n" - "};\n", - /* str_zero_overfill — element-type-agnostic */ - "package main;\n" - "def X: [0]str = [\"a\"];\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/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(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 over-fill 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/azin_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/azin_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/out", 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 2>/dev/null", - driver, outbin, s); - int rc = runwait(cmd); - runwait(rmcmd); - return rc == 0 ? -1 : 0; /* build must NOT succeed */ -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], 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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "arr_zero_vs_infer: 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_zero_vs_infer[%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, - "arr_zero_vs_infer[%s][neg%d]: built ok, " - "expected a loud error\n", - drivers[d].name, i); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (!rows[i].beid) - continue; /* see `beid` — out-of-#9 divergence */ - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "arr_zero_vs_infer: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("arr_zero_vs_infer: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/821_def_str_index_reject.c b/test/wcc/821_def_str_index_reject.c deleted file mode 100644 index 11f6ea0d..00000000 --- a/test/wcc/821_def_str_index_reject.c +++ /dev/null @@ -1,352 +0,0 @@ -/* - * 821_def_str_index_reject — indexing a bare DEF-GLOBAL scalar str is INVALID - * ww and BOTH stages must LOUDLY REJECT at check time (#14, the #8 sibling; - * drew ruling .ai/drew-14-ruling.md, rob spec .ai/rob-14-spec.md). - * - * `str[i] -> u8` itself is VALID ww — a sanctioned Go-like direct byte-index, - * a deliberate divergence from Hare's `strings::toutf8(s)[i]` (the Hare - * reference checker rejects str-index, harec check.c:362). lib/strings is - * load-bearing on it (compare/dup/join). The reject is SCOPED to ONE operand - * shape: a bare def-global SCALAR str. - * - * The bug (both-stage, but each side misbehaved DIFFERENTLY): - * - cstage: `def S:str="hi"; S[0]` BUILT and emitted an unbacked `main.S(SB)` - * base load — at runtime a frame-garbage base → SILENT SEGFAULT. - * - wwstage: the same source referenced an unbacked symbol → w6l LINK-FAIL. - * A `def` is an inline compile-time CONSTANT (def-as-constant; not storage- - * backed like a `let`), so it has no address to index. The fix REJECTS that - * one operand at the checker (N_IDENT operand + SK_DEF sym + scalar TY_STR - * base), surfacing a source error before cgen — segfault gone by construction. - * - * Mutation-sanity: the genuinely-silent pre-fix case is cstage's def-scalar - * index, which BUILT (then segfaulted) — neg rows assert BUILD-FAIL, so they - * FAIL pre-fix on cstage (built-ok) and pass post-fix. wwstage already - * link-failed pre-fix. - * - * neg row | shape | gate - * --------------------+----------------------------------------+---------- - * def_scalar_index | def S:str="hi", return S[0]:i32 | build FAIL - * def_scalar_varbind | def S:str="hi", let c=S[1] | build FAIL - * - * pos row | shape | want - * --------------------+----------------------------------------+------ - * let_scalar_index | module let S:str="hi", S[0]:i32 | 104 ('h') - * param_index | fn f(s:str) i32 = s[0]:i32; f("hi") | 104 ('h') - * local_index | local let s:str="hi", s[0]:i32 | 104 ('h') - * def_strarray_elem | def C:[2]str=["ab","cd"], C[1][0]:i32 | 99 ('c') - * - * The def_strarray_elem row pins the #8 materialised array-element str stays - * VALID: the outer operand `C[1]` is N_INDEX (not N_IDENT), so the scalar-str - * gate excludes it; and a bare def str-ARRAY operand's base is TY_ARRAY (not - * scalar str), also excluded. let/param/local + string-literal operands stay - * valid (the lib/strings compare/dup pattern). - */ -#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 want; }; - -static const struct row rows[] = { - /* module-level `let` str is storage-backed (DATA) → indexable. */ - { "let_scalar_index", - "package main;\n" - "let S: str = \"hi\";\n" - "export fn main() i32 = {\n" - "\treturn S[0]: i32;\n" - "};\n", - 104 }, - - { "param_index", - "package main;\n" - "fn f(s: str) i32 = {\n" - "\treturn s[0]: i32;\n" - "};\n" - "export fn main() i32 = {\n" - "\treturn f(\"hi\");\n" - "};\n", - 104 }, - - { "local_index", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet s: str = \"hi\";\n" - "\treturn s[0]: i32;\n" - "};\n", - 104 }, - - /* #8 materialised array-element str — `C[1]` is N_INDEX, not N_IDENT, - * so the def-scalar-str gate excludes it; must STAY valid. */ - { "def_strarray_elem", - "package main;\n" - "def C: [2]str = [\"ab\", \"cd\"];\n" - "export fn main() i32 = {\n" - "\treturn C[1][0]: i32;\n" - "};\n", - 99 }, - - /* CRITICAL PRESERVE — INTERP-style def-global scalar str `.len`/`.ptr` - * N_DOT FIELD read (lib w6l/dynout.ww INTERP, load-bearing in the ELF - * emit; bootstrap dies if this breaks). The #14 reject is on N_INDEX, - * NOT N_DOT — this field read must STAY valid. 3 + *"abc".ptr(='a'=97) - * = 100. */ - { "def_scalar_field", - "package main;\n" - "def S: str = \"abc\";\n" - "export fn main() i32 = {\n" - "\tlet n: i32 = S.len: i32;\n" - "\tlet p: *u8 = S.ptr;\n" - "\treturn n + (*p): i32;\n" - "};\n", - 100 }, - - /* def-global NON-str array index — the scalar-str gate (TY_STR only) - * must NOT over-catch a legit def-array index (the #8 array path). */ - { "def_arr_index", - "package main;\n" - "def A: [3]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn A[1]: i32;\n" - "};\n", - 20 }, -}; - -/* Indexing a bare def-global scalar str — both stages must FAIL the build - * (loud checker diagnostic, not silent segfault / link-error). */ -static const char *neg[] = { - /* def_scalar_index */ - "package main;\n" - "def S: str = \"hi\";\n" - "export fn main() i32 = {\n" - "\treturn S[0]: i32;\n" - "};\n", - /* def_scalar_varbind */ - "package main;\n" - "def S: str = \"hi\";\n" - "export fn main() i32 = {\n" - "\tlet c = S[1];\n" - "\treturn c: i32;\n" - "};\n", -}; - -/* Per-neg expected diagnostic body (parallel to neg[]); both rows hit the - * same "cannot index a def-constant str" path on both stages. */ -static const char *neg_diag[] = { - "cannot index a def-constant str", - "cannot index a def-constant str", -}; - -static int -run_driver(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/dsi_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/dsi_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dsi_%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); - - /* #8: -o pins binary + .sepwork scratch under tmpdir; rm -rf on - * every exit path removes the now-non-empty dir. */ - 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 -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 — indexing a def-global scalar str must error on - * `driver`; returns 0 when the build FAILS *and* emits `diag`. A crash - * (segfault) wraps as a nonzero "w6c failed" with no diagnostic, so the - * substring distinguishes it from a clean reject (#20). */ -static int -build_should_fail(const char *driver, const char *src, const char *diag, - int i) -{ - char tmpdir[64], s[128], outbin[128], cmd[1024], errf[128], rmcmd[160]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/dsin_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/dsin_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/dsin_%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); - - /* #8: -o pins binary + any .sepwork scratch under tmpdir; rm -rf - * removes the now-non-empty dir. */ - 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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dsi_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/dsi_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/dsi_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "def_str_index_reject: 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, - "def_str_index_reject[%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], - neg_diag[i], 100 + i) != 0) { - fprintf(stderr, - "def_str_index_reject[%s][neg%d]: built ok, " - "expected a loud error\n", - drivers[d].name, i); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "def_str_index_reject: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("def_str_index_reject: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/822_struct_global_byval_arg.c b/test/wcc/822_struct_global_byval_arg.c deleted file mode 100644 index 35e94346..00000000 --- a/test/wcc/822_struct_global_byval_arg.c +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 822_struct_global_byval_arg — a module-global struct passed BY VALUE to - * a fn (`let g: pt = pt{...}; take(g)`) was silently miscompiled, mirror- - * opposite on the two stages (task #150, the off==0 localfind footgun / - * GAP-A.ptr / #231 family): - * - * - cstage (cmd/w6c/cgen.c by-value struct-IDENT call-arg arm): localfind - * of a global returns 0, so the BP loads read the stack FRAME, never - * `LEAQ main.g(SB)` → garbage (ken saw 104, not the field sum). - * - wwstage (selfhost cgenexpr/cgenutil pushargsrev struct arm): the - * ≤16B-struct ident with no local slot fell past the local fast path - * AND was excluded from the #271 aggregate arm (structident), landing - * on the scalar single-PUSHQ default — ONE word for a 2-word struct, - * so the callee's word1 read stack garbage (silent field-drop). - * - * The fix (ONE both-stage cgen commit, converged byte-IDENTICAL): when the - * struct-arg operand is a module-global (off==0 && let_islet||def_isstructdef), - * resolve the global base into BX via `LEAQ name(SB)` and copy ALL - * eightbytes (`MOVQ off(BX),AX; PUSHQ`) high→low — the #129-A.2 struct- - * global access shape. The LOCAL path (off!=0) is untouched. - * - * row | shape | want - * ------------+------------------------------------------------+------ - * two_field | let g:pt{a:int,b:int}=pt{a=5,b=9}; take(g) | 14 - * narrow_mix | let g:struct{a:i32,b:i32,c:int}; sum | 6 - * three_word | let g:struct{a,b,c:int} (24B, #271 arm); sum | 7 - * ctrl_local | LOCAL struct arg (off!=0, unchanged, byte-id) | 14 - * - * two_field / narrow_mix are the mutation-sane rows: pre-fix cstage reads - * the frame (garbage) and wwstage drops word1. three_word confirms the >16B - * #271 aggregate arm already handles a struct global (let_islet base). - * ctrl_local guards the unchanged local-struct path. A byte-id row pins - * cstage==wwstage `.s` for each source — the convergence the fix delivers. - * - * Explicit-typed globals ONLY (`let g: pt = ...`): an inferred `let g = - * pt{}` would loud via the separate checker Bug 1 (#18) and mask #150. - */ -#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[] = { - /* two_field — THE regression pin: 2-field 16B struct global by value. - * Pre-fix cstage reads the frame (garbage), wwstage drops field b. */ - { "two_field", - "package main;\n" - "type pt = struct { a: int, b: int };\n" - "let g: pt = pt { a = 5, b = 9 };\n" - "fn take(x: pt) int = { return x.a + x.b; };\n" - "export fn main() i32 = { return take(g): i32; };\n", - 14 }, - - /* narrow_mix — i32/i32/int packs two narrow fields into eightbyte 0, - * int into eightbyte 1; 16B → the struct-ident fast arm (the fix). */ - { "narrow_mix", - "package main;\n" - "type pt = struct { a: i32, b: i32, c: int };\n" - "let g: pt = pt { a = 1, b = 2, c = 3 };\n" - "fn take(x: pt) int = { return (x.a: int) + (x.b: int) + x.c; };\n" - "export fn main() i32 = { return take(g): i32; };\n", - 6 }, - - /* three_word — 24B struct global (>16B) routes through the #271 - * aggregate arm; confirms the global base (let_islet) is honoured. */ - { "three_word", - "package main;\n" - "type pt = struct { a: int, b: int, c: int };\n" - "let g: pt = pt { a = 1, b = 2, c = 4 };\n" - "fn take(x: pt) int = { return x.a + x.b + x.c; };\n" - "export fn main() i32 = { return take(g): i32; };\n", - 7 }, - - /* ctrl_local — a LOCAL struct arg (off!=0). cgen unchanged; pins the - * local-struct push path stays byte-id (no regress). */ - { "ctrl_local", - "package main;\n" - "type pt = struct { a: int, b: int };\n" - "fn take(x: pt) int = { return x.a + x.b; };\n" - "export fn main() i32 = {\n" - "\tlet g: pt = pt { a = 5, b = 9 };\n" - "\treturn take(g): i32;\n" - "};\n", - 14 }, -}; - -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/sga_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/sga_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/sga_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/sga_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/sga_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/sga_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "struct_global_byval_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, - "struct_global_byval_arg[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "struct_global_byval_arg: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("struct_global_byval_arg: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/823_inferred_global_let.c b/test/wcc/823_inferred_global_let.c deleted file mode 100644 index bff8daf7..00000000 --- a/test/wcc/823_inferred_global_let.c +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 823_inferred_global_let — an inferred-type (annotation-less) module-global - * `let g = ;` must type-resolve at every downstream read. cstage's - * module N_LET pass-2 (cmd/wcc/check.c) stamped `d->type` from the rhs but - * never repointed the Sym that pass-1 installed with the annotation-less NULL - * type — so every later N_IDENT read of the global resolved its type as nil: - * `let n = 5; return n + 0` → `arithmetic on non-numeric type` - * `let g = pt{..}; g.a + g.b` → `arithmetic on non-numeric type` - * `let g = pt{..}; take(g)` → `argument type not assignable to pt` - * - * The fix (cmd/w6c/cgen.c sibling check.c) mirrors the #11 [_]-array Sym - * repoint a few lines up: after `d->type = type_default(rt)`, repoint the - * Sym at the stamped type. cstage-ONLY — wwstage's single-pass resolve - * already stamps the inferred global (all three rows built clean pre-fix), - * so this LIFTS cstage from loud-reject to accept, converging acceptance. - * No asm moves for code that already built; the inferred form now reaches - * the same cgen as the explicit-typed form, so cstage==wwstage byte-id. - * - * row | shape | want - * --------------+---------------------------------------------+----- - * scalar | let n = 5; return n + 0 (broadest case) | 5 - * struct_field | let g = pt{a=7,b=9}; g.a + g.b | 16 - * struct_arg | let g = pt{a=5,b=9}; take(g) (rides #150-A) | 14 - * - * All three are mutation-sane: pre-fix cstage loud-rejects each (the build - * fails), so a regression that drops the repoint re-louds and fails the row. - * The byte-id rows pin cstage==wwstage `.s` for the now-accepted inferred - * source. - */ -#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[] = { - /* scalar — the broadest case: an inferred scalar global read in - * arithmetic. The read must flow through the repointed Sym's numeric - * type; pre-fix cstage `arithmetic on non-numeric type` (a bare - * `return n: i32` would NOT loud — the cast's target type masks the - * nil Sym — so `+ 0` is the mutation-sane shape). */ - { "scalar", - "package main;\n" - "let n = 5;\n" - "export fn main() i32 = {\n" - "\treturn (n + 0): i32;\n" - "};\n", - 5 }, - - /* struct_field — inferred struct global, both fields read. pre-fix - * cstage `arithmetic on non-numeric type`. */ - { "struct_field", - "package main;\n" - "type pt = struct { a: int, b: int };\n" - "let g = pt { a = 7, b = 9 };\n" - "export fn main() i32 = {\n" - "\treturn (g.a + g.b): i32;\n" - "};\n", - 16 }, - - /* struct_arg — inferred struct global passed BY VALUE; rides the - * #150 Commit A cgen marshalling. pre-fix cstage `argument type - * not assignable to pt`. */ - { "struct_arg", - "package main;\n" - "type pt = struct { a: int, b: int };\n" - "let g = pt { a = 5, b = 9 };\n" - "fn take(x: pt) int = {\n" - "\treturn x.a + x.b;\n" - "};\n" - "export fn main() i32 = {\n" - "\treturn take(g): i32;\n" - "};\n", - 14 }, -}; - -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/igl_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/igl_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/igl_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/igl_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/igl_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/igl_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "inferred_global_let: 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, - "inferred_global_let[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "inferred_global_let: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("inferred_global_let: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/824_inferred_float_global.c b/test/wcc/824_inferred_float_global.c deleted file mode 100644 index 8647f8a7..00000000 --- a/test/wcc/824_inferred_float_global.c +++ /dev/null @@ -1,266 +0,0 @@ -/* - * 824_inferred_float_global — an inferred-type (annotation-less) module-global - * `let pi = 3.5;` whose init is a FLOAT literal must load as a float (MOVSD) - * at every downstream read, exactly as the explicit-typed `let pi: f64 = 3.5` - * does. WWSTAGE-ONLY bug (cstage already correct post-#150-B / #18). - * - * Root (selfhost/cmd/wcc/cgen.ww): the checker backfills the inferred decl's - * type annotation to the literal's type node `untyped_float` (check.ww - * checkletassign). letemitsize keys global slot-sizing on letscalarprim / - * letfloatprim, NEITHER of which recognises `untyped_float` → returns 0 → the - * global is DROPPED from collectlets: no DATAW slot emitted, and every read - * falls through cgident's `isletvar` guard to the silent bare return, leaving - * X0 holding a stale spilled value. `let pi = 3.5; (pi * 2.0): int` then - * computed 2.0*2.0 = 4, not 7 — silent, no diagnostic. - * - * defaultinferredlets already type_defaults an inferred-INT global's - * `untyped_int` annotation to the machine word `int`; its comment explicitly - * carved the float twin out and deferred it to #135, because cstage USED to - * integer-type an inferred float at the use site (MOVQ) so defaulting ww-side - * alone would diverge (cs≠ww, rule-10). #150-B fixed cstage to type_default - * `untyped_float` → f64 and load MOVSD, so the divergence is gone: this commit - * flips the carve-out (default `untyped_float` → f64, peeling one unary +/- as - * the int arm does) and adds a name-keyword fallback to cgident's let-float - * gate (the renamed `f64`/`f32` N_TNAME carries no stamped tinfo cgen can read, - * so isfloattype's stamp-read misses it; letfloatprim on the keyword fires). - * wwstage now emits the IDENTICAL `LEAQ main.pi(SB); MOVSD (CX), X0` as cstage. - * - * row | shape | want - * ------------+---------------------------------------------+----- - * infer_mul | let pi = 3.5; (pi * 2.0): int (the bug) | 7 - * infer_read | let r = 2.5; (r + 0.5): int | 3 - * infer_neg | let g = -2.5; (g * -2.0): int (unary peel) | 5 - * infer_f32 | let q = 1.5f32; (q + 1.5f32): int (MOVSS) | 3 - * ctrl_expl | let e: f64 = 3.5; (e * 2.0): int (explicit) | 7 - * ctrl_int | let n = 5; (n + 1): int (int stays MOVQ) | 6 - * - * Pre-fix the infer_* rows ran wwstage=garbage (stale X0 / dropped global) and - * cstage=correct (cs≠ww); post-fix all six are byte-identical between stages. - * ctrl_int guards that the inferred-INT default (untyped_int → int, MOVQ) is - * untouched; ctrl_expl guards the explicit-f64 path didn't regress. - */ -#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[] = { - /* infer_mul — the bug: inferred float global read in a float binop, - * cast to int. Pre-fix wwstage dropped main.pi (no DATAW) and left X0 - * stale → 2.0*2.0 = 4; cstage 7. */ - { "infer_mul", - "package main;\n" - "let pi = 3.5;\n" - "export fn main() i32 = {\n" - "\treturn (pi * 2.0): i32;\n" - "};\n", - 7 }, - - /* infer_read — second inferred-float read shape (+, not *). */ - { "infer_read", - "package main;\n" - "let r = 2.5;\n" - "export fn main() i32 = {\n" - "\treturn (r + 0.5): i32;\n" - "};\n", - 3 }, - - /* infer_neg — inferred float global with a unary-minus init; exercises - * defaultinferredlets' single-unary peel (mirror of the int arm). */ - { "infer_neg", - "package main;\n" - "let g = -2.5;\n" - "export fn main() i32 = {\n" - "\treturn (g * -2.0): i32;\n" - "};\n", - 5 }, - - /* infer_f32 — a bare f32-suffixed literal infers f32; the float load - * must pick MOVSS, not MOVSD. (f32 already worked via letfloatprim's - * "f32"; row guards the suffix-inferred path stays MOVSS.) */ - { "infer_f32", - "package main;\n" - "let q = 1.5f32;\n" - "export fn main() i32 = {\n" - "\treturn (q + 1.5f32): i32;\n" - "};\n", - 3 }, - - /* ctrl_expl — explicit f64 annotation; already worked (lv.tnode is the - * non-nil f64 node). No-regress guard. */ - { "ctrl_expl", - "package main;\n" - "let e: f64 = 3.5;\n" - "export fn main() i32 = {\n" - "\treturn (e * 2.0): i32;\n" - "};\n", - 7 }, - - /* ctrl_int — inferred INT global. defaultinferredlets defaults it to - * `int` (MOVQ scalar load); the float arm must NOT pull it into MOVSD. - * No-regress guard for the int path. */ - { "ctrl_int", - "package main;\n" - "let n = 5;\n" - "export fn main() i32 = {\n" - "\treturn (n + 1): i32;\n" - "};\n", - 6 }, -}; - -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/ifg_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/ifg_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/ifg_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/ifg_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/ifg_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/ifg_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "inferred_float_global: 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, - "inferred_float_global[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "inferred_float_global: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("inferred_float_global: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/825_errfirst_union_try.c b/test/wcc/825_errfirst_union_try.c deleted file mode 100644 index ad88a359..00000000 --- a/test/wcc/825_errfirst_union_try.c +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 825_errfirst_union_try — error-first tagged-union try (`?`/`!`) success-tag - * (#52, the #216 deferred divergence). wwstage HARDCODED the success tag as 0, - * so for an ERROR-FIRST union — error variant at tag 0, success at tag 1+ (e.g. - * `(e | u16)` with `type e = !i32`) — the success (tag 1) failed the `CMPQ $0` - * success check and fell to the error path: `!` → exit(1), `?` → propagate. - * cstage was already correct via cg_tagged_success_tag (cmd/w6c/cgen.c:857): - * if any variant is an error, the success tag is the first NON-error variant's - * index, else 0. The `.s` diff was literally `CMPQ $1` (cstage) vs `CMPQ $0` - * (wwstage). - * - * The fix (selfhost cgenexpr.ww only — cstage UNCHANGED): a `successtag` - * helper mirroring cstage's, applied at the FOUR sites that shared the tag-0 / - * first-param assumption — cgtryprop CMPQ, cgtryunw CMPQ, and the - * cgtrytupleshift / cgtrytaggedshift payload-variant lookups (successvariant). - * - * row | shape | want - * ----------------+----------------------------------------+------ - * errfirst_must | (e|u16) `!` unwrap (THE repro) | 44 - * errfirst_prop | (e|u16) `?` propagate through outer fn | 44 - * succfirst_ctrl | (u16|e) `!` — successtag=0, CMPQ $0 | 7 - * errfirst_3var | (e1|e2|u16) success at index 2 | 99 - * errfirst_tuple | (e|(u16,u16)) `!` — successvariant shift| 51 - * - * errfirst_tuple is the LATENT-site row: the cgtrytupleshift payload-shift only - * bites an error-first union whose SUCCESS variant is itself a tuple. Pre-fix it - * fell two ways — the CMPQ $0 rejected tag-1 (→ exit 1), AND even with the CMPQ - * fixed the shift read ou.params.type_ (the error variant, not a tuple) so the - * rvalue tuple never filled the cursor (verified mutation: 88, not 51). The - * successvariant fix routes the shift to the tuple. The sibling cgtrytaggedshift - * (nested-tagged success) shares the identical successvariant call; it is not - * exercised here because nested-tagged union *construction* (the return-coercion - * box, main.ok) has a separate pre-existing cs!=ww divergence — #52-independent - * (present success-first too) — filed for its own fix. - * - * Exit codes are the process low-8-bits: 300 & 0xFF = 44. errfirst_* are the - * mutation-sane rows: pre-fix wwstage = exit 1 (wrong-tag → error path), - * cstage = correct → cs!=ww. succfirst_ctrl is the byte-id no-regress guard: - * successtag returns 0 there, so its `CMPQ $0` is UNCHANGED. A byte-id pass - * pins cstage==wwstage `.s` for every row. - */ -#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[] = { - /* errfirst_must — THE repro: error-first `(e|u16)`, `!` unwrap of a - * tag-1 success. Pre-fix wwstage CMPQ $0 vs tag 1 → error → exit 1. */ - { "errfirst_must", - "package main;\n" - "type e = !i32;\n" - "type u = (e | u16);\n" - "fn ok() u = { return 300u16; };\n" - "export fn main() i32 = {\n" - "\tlet x = ok()!;\n" - "\treturn x: i32;\n" - "};\n", - 44 }, - - /* errfirst_prop — the `?` twin: outer propagates inner's success - * through the same error-first union, then `!` unwraps. */ - { "errfirst_prop", - "package main;\n" - "type e = !i32;\n" - "type u = (e | u16);\n" - "fn inner() u = { return 300u16; };\n" - "fn outer() u = {\n" - "\tlet v = inner()?;\n" - "\treturn v;\n" - "};\n" - "export fn main() i32 = {\n" - "\treturn outer()!: i32;\n" - "};\n", - 44 }, - - /* succfirst_ctrl — success-FIRST `(u16|e)`, successtag=0: the byte-id - * no-regress guard, CMPQ $0 unchanged. */ - { "succfirst_ctrl", - "package main;\n" - "type e = !i32;\n" - "type u2 = (u16 | e);\n" - "fn g() u2 = { return 7u16; };\n" - "export fn main() i32 = {\n" - "\treturn g()!: i32;\n" - "};\n", - 7 }, - - /* errfirst_3var — two error variants then success: successtag walks - * past index 0 AND 1 to return 2, pinning the walk (not 0, not 1). */ - { "errfirst_3var", - "package main;\n" - "type e1 = !i32;\n" - "type e2 = !i64;\n" - "type u3 = (e1 | e2 | u16);\n" - "fn h() u3 = { return 99u16; };\n" - "export fn main() i32 = {\n" - "\treturn h()!: i32;\n" - "};\n", - 99 }, - - /* errfirst_tuple — error-first union whose SUCCESS variant is a tuple: - * exercises cgtrytupleshift via successvariant. Pre-fix exit 1 (CMPQ $0 - * rejects tag 1); CMPQ-fixed-but-shift-reverted gives 88; full fix → 51 - * (300+7 = 307, 307 & 0xFF = 51). */ - { "errfirst_tuple", - "package main;\n" - "type e = !i32;\n" - "type ut = (e | (u16, u16));\n" - "fn ok() ut = { return (300u16, 7u16); };\n" - "export fn main() i32 = {\n" - "\tlet t = ok()!;\n" - "\tlet (a, b) = t;\n" - "\treturn (a: i32) + (b: i32);\n" - "};\n", - 51 }, -}; - -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/efu_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/efu_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/efu_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/efu_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/efu_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/efu_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "errfirst_union_try: 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, - "errfirst_union_try[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "errfirst_union_try: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("errfirst_union_try: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/827_str_eq.c b/test/wcc/827_str_eq.c deleted file mode 100644 index dc39a9d1..00000000 --- a/test/wcc/827_str_eq.c +++ /dev/null @@ -1,310 +0,0 @@ -/* - * 827_str_eq — str `==` / `!=` is a CONTENT compare (rt_streq), not a - * pointer compare. #146 (the ww-twin of the #154 cstage fix). - * - * THE BUG (wwstage-only; cstage was already correct via #154): wwstage - * cgbin (selfhost/cmd/wcc/cgenexpr.ww) had NO str-awareness, so every - * `==`/`!=` fell to the generic comparison tail — a single `CMPQ BX, AX` - * on the eager-eval'd registers. For a str, eager-eval collapses the - * 3-word header to its ptr word, so the compare became ptr-vs-ptr: two - * DISTINCT-pointer equal-content strings answered FALSE on wwstage while - * cstage (which CALLs rt_streq) answered TRUE. THE divergence row is - * eq_dup_true: `let a="abc"; let b=strings.dup("abc"); a==b`. - * - * THE FIX: cgbin now has a str ==/!= branch at the top (before the - * generic eval), mirroring cstage cgen.c cbinop:4564-4623 — push rhs - * then lhs (len, ptr each), POPQ DI/SI/DX/CX = a.ptr,a.len,b.ptr,b.len - * (rt/streq.s ABI), CALL rt_streq -> AX in {0,1}, XORQ $1 for !=. - * - * Two row sets: - * runrows — built+RUN on BOTH drivers (need strings.dup for a - * distinct-pointer copy, so they `import strings` and build - * via `ww` which links the lib). These pin RUNTIME - * correctness; eq_dup_true/ne_dup_false are mutation-sane - * (pre-fix wwstage gave the wrong ptr-compare answer). - * asmrows — import-free single files run straight through w6c / - * w6c_ww for the BYTE-ID headline (w6c compiles one file and - * can't resolve strings.dup, so the import rows can't be - * byte-id'd directly; these import-free str==/!= sources - * exercise the identical cgbin branch and pin cs==ww `.s`). - * - * runrows | want - * -------------------------------------------------+------ - * eq_dup_true a=="abc", b=dup("abc"); a==b | 1 (THE bug) - * ne_dup_false a=="abc", b=dup("abc"); a!=b | 0 (!= twin) - * diff_content "abc"=="abd" | 0 (control) - * eq_self a="x"; a==a | 1 (same ptr) - * empty a="", b=dup(""); a==b | 1 (len-0 edge) - */ -#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 runrows[] = { - /* eq_dup_true — THE regression pin: distinct pointer, equal - * content. Pre-fix wwstage ptr-compared -> 0 (WRONG); cstage - * CALLs rt_streq -> 1. */ - { "eq_dup_true", - "package main;\n" - "import strings;\n" - "export fn main() i32 = {\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = strings.dup(\"abc\");\n" - "\tif (a == b) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 1 }, - - /* ne_dup_false — the != twin: distinct pointer, equal content, so - * `!=` is FALSE. Pre-fix wwstage ptr-compared -> != true (WRONG). */ - { "ne_dup_false", - "package main;\n" - "import strings;\n" - "export fn main() i32 = {\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = strings.dup(\"abc\");\n" - "\tif (a != b) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* diff_content — distinct content; both stages agree (control). */ - { "diff_content", - "package main;\n" - "export fn main() i32 = {\n" - "\tif (\"abc\" == \"abd\") { return 1; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* eq_self — same pointer, equal content; both stages agree. */ - { "eq_self", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: str = \"x\";\n" - "\tif (a == a) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 1 }, - - /* empty — len-0 edge: rt_streq must match two empty strings. */ - { "empty", - "package main;\n" - "import strings;\n" - "export fn main() i32 = {\n" - "\tlet a: str = \"\";\n" - "\tlet b: str = strings.dup(\"\");\n" - "\tif (a == b) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 1 }, -}; - -/* Import-free sources for the byte-id pass (w6c compiles a single file - * and can't resolve cross-module strings.dup). Each exercises the cgbin - * str ==/!= branch; the `.s` must be identical cstage vs wwstage. */ -static const struct row asmrows[] = { - /* be_eq_local — local str == local str (ident operand load). */ - { "be_eq_local", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = \"abd\";\n" - "\tif (a == b) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 0 }, - - /* be_ne_local — the != twin (pins the XORQ $1, AX). */ - { "be_ne_local", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: str = \"abc\";\n" - "\tlet b: str = \"abd\";\n" - "\tif (a != b) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 1 }, - - /* be_eq_strlit — strlit == strlit (non-ident cgexpr operand). */ - { "be_eq_strlit", - "package main;\n" - "export fn main() i32 = {\n" - "\tif (\"abc\" == \"abc\") { return 1; };\n" - "\treturn 0;\n" - "};\n", - 1 }, - - /* be_eq_global — module-GLOBAL str == global str. This is the - * raison d'etre of the typeisstr gate (vs the spec's nodeisstr, - * whose N_IDENT arm keys on localfindnode and returns false for a - * global, leaving a latent cs!=ww hole). The cgstreqpush global arm - * (LEAQ name(SB)) must byte-match cstage's #154 let_islet branch. */ - { "be_eq_global", - "package main;\n" - "let g: str = \"abc\";\n" - "let h: str = \"abd\";\n" - "export fn main() i32 = {\n" - "\tif (g == h) { return 1; };\n" - "\treturn 0;\n" - "};\n", - 0 }, -}; - -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/seq_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/seq_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/seq_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/seq_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/seq_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/seq_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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 nrun = (int)(sizeof runrows / sizeof runrows[0]); - int nasm = (int)(sizeof asmrows / sizeof asmrows[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_eq: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < nrun; i++) { - int got = run_driver(drivers[d].path, &runrows[i], i); - total++; - if (got != runrows[i].want) { - fprintf(stderr, - "str_eq[%s][%s]: exit=%d want=%d\n", - drivers[d].name, runrows[i].label, - got, runrows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < nasm; i++) { - total++; - if (asm_byte_identical(bin, &asmrows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, "str_eq: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("str_eq: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/828_overlong_arrlit.c b/test/wcc/828_overlong_arrlit.c deleted file mode 100644 index e70b51de..00000000 --- a/test/wcc/828_overlong_arrlit.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * 828_overlong_arrlit — an OVERLONG array literal (more initialisers than the - * declared [N]) is INVALID ww; BOTH stages must LOUDLY REJECT at check time - * (#12 + #106; rob spec .ai/rob-12-106-spec.md). #9 wired the over-fill at the - * DECL position only; this extends checkarrlitfits to the RETURN + CALL-ARG - * positions and chases a TY_NAMED alias to its underlying [N]T at the top of - * checkarrlitfits, so EVERY caller is alias-aware by construction. - * - * WWSTAGE-ONLY fix — cstage already rejects all four shapes (type_assignable - * counts elements). Pre-fix wwstage divergences (the mutation-sanity targets): - * - #12a RETURN `fn f() [2]int = [1,2,3]` : ww silently ACCEPTED (exit 0) - * - #12b CALL-ARG `g([1,2,3])`, g(a:[2]int) : ww loud-but-LATE via cgen #271 - * - #106 alias-global `type A=[2]int; let g:A=[1,2,3]` : ww silently ACCEPTED - * (+ truncated DATA) - * - alias-RETURN `type A=[2]int; fn f() A=[1,2,3]` : proves the alias - * chase covers the return too - * - * The diagnostic TEXT may differ between stages ("over-fill" vs "not - * assignable") — that is byte-id-blind (stderr is not asm); both REJECT and - * emit no asm, so 990-997 byte-id is untouched. Do NOT chase message parity. - * - * neg row | shape | gate - * -----------------+---------------------------------------------+---------- - * ret_overlong | fn f() [2]int = [1,2,3] | build FAIL - * arg_overlong | fn g(a:[2]int)..; g([1,2,3]) | build FAIL - * alias_g_over | type A=[2]int; let g:A=[1,2,3] | build FAIL - * alias_ret_over | type A=[2]int; fn f() A = [1,2,3] | build FAIL - * - * pos row | shape | want - * -----------------+---------------------------------------------+------ - * ret_exact | fn f() [2]int = [1,2]; f()[1] | 2 - * arg_exact | fn g(a:[2]int) int = a[1]; g([10,20]) | 20 - * alias_exact | type A=[2]int; let g:A=[1,2]; g[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[] = { - { "ret_exact", - "package main;\n" - "fn f() [2]int = {\n" - "\treturn [1, 2];\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet r: [2]int = f();\n" - "\treturn r[1]: i32;\n" - "};\n", - 2 }, - - /* A bare array-LITERAL fixed-array arg is blocked on BOTH stages by - * cgen #271 (aggregate arg from unsupported source); the supported - * valid form passes via a variable. This control confirms the #12b - * desugarcallargs over-fill wiring (gated to `a.kind == N_ARRLIT`) - * leaves a normal variable call-arg untouched. */ - { "arg_exact", - "package main;\n" - "fn g(a: [2]int) int = {\n" - "\treturn a[1];\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet v: [2]int = [10, 20];\n" - "\treturn g(v): i32;\n" - "};\n", - 20 }, - - { "alias_exact", - "package main;\n" - "type A = [2]int;\n" - "let g: A = [1, 2];\n" - "export fn main() i32 = {\n" - "\treturn g[1]: i32;\n" - "};\n", - 2 }, -}; - -/* An overlong array literal — both stages must FAIL the build (loud checker - * diagnostic, not silent accept / DATA-truncate / late cgen loud). */ -static const char *neg[] = { - /* ret_overlong (#12a) */ - "package main;\n" - "fn f() [2]int = {\n" - "\treturn [1, 2, 3];\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet r: [2]int = f();\n" - "\treturn r[1]: i32;\n" - "};\n", - /* arg_overlong (#12b) */ - "package main;\n" - "fn g(a: [2]int) int = {\n" - "\treturn a[1];\n" - "};\n" - "export fn main() i32 = {\n" - "\treturn g([1, 2, 3]): i32;\n" - "};\n", - /* alias_g_over (#106) */ - "package main;\n" - "type A = [2]int;\n" - "let g: A = [1, 2, 3];\n" - "export fn main() i32 = {\n" - "\treturn g[1]: i32;\n" - "};\n", - /* alias_ret_over (alias + return) */ - "package main;\n" - "type A = [2]int;\n" - "fn f() A = {\n" - "\treturn [1, 2, 3];\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet r: A = f();\n" - "\treturn r[1]: i32;\n" - "};\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/oal_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/oal_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/oal_%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 array literal 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/oaln_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/oaln_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/oaln_%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); - - /* explicit -o inside tmpdir so the binary AND .sepwork both land - * in tmpdir and die with rm -rf (the stem follows the output, not the - * source). */ - 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 */ -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/oal_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/oal_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/oal_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "overlong_arrlit: 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, - "overlong_arrlit[%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, - "overlong_arrlit[%s][neg%d]: built ok, " - "expected a loud error\n", - drivers[d].name, i); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "overlong_arrlit: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("overlong_arrlit: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/829_slice_str_global_arg.c b/test/wcc/829_slice_str_global_arg.c deleted file mode 100644 index b08c4eac..00000000 --- a/test/wcc/829_slice_str_global_arg.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - * 829_slice_str_global_arg — a module-global SLICE or STR passed BY VALUE to - * a fn (`let s: str = "..."; take(s)`, `let g: []int = [...]; take(g)`) was - * silently miscompiled by wwstage only (task #151, the slice/str twin of the - * #150 struct-global arg): - * - * - cstage (cmd/w6c/cgen.c): node_isslice/node_isstr are TYPE-keyed - * (n->type), so a global slice/str ident still pushes all 3 header words - * {ptr,len,cap} — slice via its dedicated push arm (BX-base per-word, - * cgen.c:9124), str via cgexpr's cgslicehdr (CX-base AX/BX/CX, - * cgen.c:1866) then the node_isstr triple push. - * - wwstage (selfhost cgenutil.ww pushargsrev): nodeisslice/nodeisstr are - * LOCAL-keyed (localfindnode→nil for a global → return false), so a - * global slice/str ident fell past every header arm to the scalar - * single-PUSHQ default — ONE word (ptr), dropping len+cap. The callee - * then read garbage for .len/.cap (silent field-drop). - * - * The fix (ONE wwstage cgen commit, cstage UNTOUCHED → w6c md5 unchanged): - * add a global slice/str arm beside the #150 struct arm in pushargsrev's - * `lc == nil` branch, gated `(isletvar || deflookup)`. cstage emits a - * DIFFERENT per-type sequence, so each arm is hand-authored to MATCH: - * - slice: LEAQ name(SB),BX; MOVQ {16,8,0}(BX),AX; PUSHQ AX (BX-base) - * - str: LEAQ name(SB),CX; MOVQ (CX),AX / 8(CX),BX / 16(CX),CX; - * PUSHQ CX/BX/AX (cgslicehdr CX-base) - * Each `.s` is now byte-IDENTICAL to cstage per type. The LOCAL slice/str - * push path (off!=0) is untouched. - * - * row | shape | want - * ---------------+----------------------------------------------+------ - * str_glob_len | let s:str="abcd"; take reads x.len | 4 - * str_glob_ptr | let s:str="abcd"; take reads x[0] (uses ptr) | 97 - * slice_glob_len | let g:[]int=[10..50]; take reads x.len | 5 - * slice_glob_idx | let g:[]int=[10..50]; take reads x[2] | 30 - * def_str_len | def s:str="abcd"; take reads x.len (#21) | 4 - * def_str_ptr | def s:str="abcd"; take reads x[0] (#21) | 97 - * def_str_return | return ; take reads x.len (#21) | 5 - * ctrl_local_str | LOCAL str arg (off!=0, unchanged, byte-id) | 2 - * ctrl_local_sl | LOCAL []int arg (off!=0, unchanged, byte-id) | 8 - * - * The def_* rows are the #21 residual of this by-value-global family: a - * module-level `def s: str` (NO DATA symbol) passed BY VALUE. Pre-fix wwstage's - * nodeisstr was LOCAL-keyed for an N_IDENT (def→false), so the def-str fell to - * the scalar single-PUSHQ default and dropped len/cap; cstage's node_isstr is - * TYPE-keyed (n->type) and pushes all 3 words. A def rides cgident's const-fold - * (LEAQ _S_n, MOVQ $len) — NOT the let LEAQ name(SB) path — then the now - * type-keyed nodeisstr triple push. def_str_return is a SIBLING guard (a - * def-str flowed through a fn return, then pushed via nodeisstr's pre-existing - * N_CALL arm) — byte-id-identical pre/post-fix, no #21 teeth of its own. - * - * The *_glob_* rows are mutation-sane: pre-fix wwstage drops len/cap and the - * callee reads garbage; cstage is correct. ctrl_* guard the unchanged local - * path. A byte-id row pins cstage==wwstage `.s` for each source. - * - * NOTE — slice element type is []int, not []u8: a `let g: []u8 = [intlits]` - * module-global is rejected by the wwstage checker ("let: not assignable", - * the untyped-int→u8 array-lit coercion) while cstage accepts it — a - * SEPARATE pre-existing checker divergence (#10/#125 batch, "bare []u8 - * cs≠ww"), orthogonal to this caller-side push fix. []int exercises the - * identical TY_SLICE push arm. The str rows cover the []u8-header shape - * (str IS []u8), including a byte read via x[0]. - */ -#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[] = { - /* str_glob_len — THE regression pin (str header): global str by value, - * callee reads .len. Pre-fix wwstage drops len → garbage. */ - { "str_glob_len", - "package main;\n" - "let s: str = \"abcd\";\n" - "fn take(x: str) int = { return x.len: int; };\n" - "export fn main() i32 = { return take(s): i32; };\n", - 4 }, - - /* str_glob_ptr — global str by value, callee reads x[0] (proves the - * ptr word survived the push). 'a' == 97. */ - { "str_glob_ptr", - "package main;\n" - "let s: str = \"abcd\";\n" - "fn take(x: str) int = { return x[0]: int; };\n" - "export fn main() i32 = { return take(s): i32; };\n", - 97 }, - - /* slice_glob_len — global slice by value, callee reads .len. Pre-fix - * wwstage drops len/cap → garbage; the TY_SLICE BX-base arm. */ - { "slice_glob_len", - "package main;\n" - "let g: []int = [10, 20, 30, 40, 50];\n" - "fn take(x: []int) int = { return x.len: int; };\n" - "export fn main() i32 = { return take(g): i32; };\n", - 5 }, - - /* slice_glob_idx — global slice by value, callee reads x[2] (proves - * ptr survived the push). */ - { "slice_glob_idx", - "package main;\n" - "let g: []int = [10, 20, 30, 40, 50];\n" - "fn take(x: []int) int = { return x[2]; };\n" - "export fn main() i32 = { return take(g): i32; };\n", - 30 }, - - /* def_str_len — THE #21 regression pin: a module-level `def s: str` - * passed BY VALUE, callee reads x.len. Pre-fix wwstage drops len/cap - * (scalar single-PUSHQ default); cstage type-keyed pushes all 3 words. - * A def has NO DATA symbol — it rides cgident's const-fold (LEAQ _S_x, - * MOVQ $len BX, $len CX), then the type-keyed str triple push. */ - { "def_str_len", - "package main;\n" - "def s: str = \"abcd\";\n" - "fn take(x: str) int = { return x.len: int; };\n" - "export fn main() i32 = { return take(s): i32; };\n", - 4 }, - - /* def_str_ptr — def-str by value, callee reads x[0] (proves the ptr - * word survived the push). 'a' == 97. */ - { "def_str_ptr", - "package main;\n" - "def s: str = \"abcd\";\n" - "fn take(x: str) int = { return x[0]: int; };\n" - "export fn main() i32 = { return take(s): i32; };\n", - 97 }, - - /* def_str_return — a def-str flowed through a fn return: give()'s - * `return s` leaves the const-folded str header (AX=ptr/BX=len/CX=cap), - * and the outer `take(give())` arg rides nodeisstr's pre-existing N_CALL - * arm (give returns str) for the 3-word push. A #21 SIBLING, not the - * N_IDENT def arm — it is byte-id-IDENTICAL pre- and post-fix (no teeth - * of its own), kept as a convergence guard that a def-str surviving a fn - * return stays a full str header on both stages. */ - { "def_str_return", - "package main;\n" - "def s: str = \"hello\";\n" - "fn give() str = { return s; };\n" - "fn take(x: str) int = { return x.len: int; };\n" - "export fn main() i32 = { return take(give()): i32; };\n", - 5 }, - - /* ctrl_local_str — a LOCAL str arg (off!=0). cgen unchanged; pins the - * local-str push path stays byte-id (no regress). */ - { "ctrl_local_str", - "package main;\n" - "fn take(x: str) int = { return x.len: int; };\n" - "export fn main() i32 = {\n" - "\tlet s: str = \"ab\";\n" - "\treturn take(s): i32;\n" - "};\n", - 2 }, - - /* ctrl_local_sl — a LOCAL []int arg (off!=0). cgen unchanged; pins the - * local-slice push path stays byte-id (no regress). */ - { "ctrl_local_sl", - "package main;\n" - "fn take(x: []int) int = { return x[1]; };\n" - "export fn main() i32 = {\n" - "\tlet g: []int = [7, 8, 9];\n" - "\treturn take(g): i32;\n" - "};\n", - 8 }, -}; - -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/ssg_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/ssg_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/ssg_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/ssg_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/ssg_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/ssg_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "slice_str_global_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, - "slice_str_global_arg[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "slice_str_global_arg: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("slice_str_global_arg: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/830_cast_base_index.c b/test/wcc/830_cast_base_index.c deleted file mode 100644 index bd597289..00000000 --- a/test/wcc/830_cast_base_index.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 830_cast_base_index — indexing a DIRECT-CAST base `(e:*[N]T)[i]` must - * stride by the cast element type's size, not the 8B default (task - * #19old, .ai/rob-19old-spec.md). wwstage-only fix; cstage was already - * correct. - * - * The bug: cgindex (selfhost/cmd/wcc/cgenexpr.ww) derived the element - * stride/load-width `esz` only for N_DOT / N_UN-deref / N_INDEX base - * node-kinds (read off the stamped index-result tinfo n.type_). An - * N_CAST base matched NO arm, so esz stayed at the 8B default — for a - * narrow element (u32/u16/u8) the stride and load width were both - * wrong and the read returned 0/garbage. cstage reads it uniformly via - * idx_eff(base->type)->sub->size (cmd/w6c/cgen.c N_INDEX); the fix adds - * `|| base.kind == nkind.N_CAST` to the stamped-tinfo esz arm, which - * fixes stride + load width + signedness together (all read from dt). - * - * row | shape | stride | want - * ---------+-------------------------------+--------+------ - * u32_idx | [4]u32, ((&a):*[4]u32)[i] | 4 | 30 - * u16_idx | [4]u16, ((&a):*[4]u16)[i] | 2 | 30 - * u8_idx | [4]u8, ((&a):*[4]u8)[i] | 1 | 40 - * u64_ctl | [4]u64, ((&a):*[4]u64)[i] | 8 | 20 (control) - * - * The whitelist of base node-kinds was whack-a-mole (#19old residuals): - * N_CALL, N_SLICE and N_TYPEASSERT bases ALSO fell to the 8B default. The - * fix routes every non-ident, non-N_INDEX base through the stamped-n.type_ - * arm (mirrors cstage's uniform idx_eff, no node-kind gate). These rows - * pin the residual base kinds: - * - * call_idx | mk(&a)[i] (N_CALL *[4]u32) | 4 | 30 - * slc_idx | a[0:4][i] (N_SLICE []u16) | 2 | 30 - * asrt_idx | (v as *[4]u16)[i] (N_TYPEASSERT) | 2 | 30 - * - * The <8B rows are the mutation-sane rows: pre-fix wwstage strides by 8 - * and reads 0/garbage. u64_ctl pins no-regress for the already-correct - * 8B-stride case. A byte-id row pins cstage==wwstage `.s` for the - * cast-base index source (the convergence the fix delivers). - */ -#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[] = { - /* u32_idx — stride 4 / MOVL. THE bug: pre-fix wwstage strides by - * 8 and reads 0. */ - { "u32_idx", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u32 = [10u32, 20u32, 30u32, 40u32];\n" - "\tlet i: int = 2;\n" - "\treturn ((&a): *[4]u32)[i]: i32;\n" - "};\n", - 30 }, - - /* u16_idx — stride 2 / MOVW. */ - { "u16_idx", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u16 = [10u16, 20u16, 30u16, 40u16];\n" - "\tlet i: int = 2;\n" - "\treturn ((&a): *[4]u16)[i]: i32;\n" - "};\n", - 30 }, - - /* u8_idx — stride 1 / MOVB. */ - { "u8_idx", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u8 = [10u8, 20u8, 30u8, 40u8];\n" - "\tlet i: int = 3;\n" - "\treturn ((&a): *[4]u8)[i]: i32;\n" - "};\n", - 40 }, - - /* u64_ctl — stride 8 control: already correct (matches the 8B - * default), guards no-regress for the wide-element cast base. */ - { "u64_ctl", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u64 = [10u64, 20u64, 30u64, 40u64];\n" - "\tlet i: int = 1;\n" - "\treturn ((&a): *[4]u64)[i]: i32;\n" - "};\n", - 20 }, - - /* call_idx — N_CALL base mk(&a)[i], stride 4 / MOVL. #19old residual: - * a call-returning-pointer base also fell to the 8B default. */ - { "call_idx", - "package main;\n" - "fn mk(p: *[4]u32) *[4]u32 = { return p; };\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u32 = [10u32, 20u32, 30u32, 40u32];\n" - "\tlet i: int = 2;\n" - "\treturn mk(&a)[i]: i32;\n" - "};\n", - 30 }, - - /* slc_idx — N_SLICE base a[0:4][i], stride 2 / MOVZWQ. #19old - * residual: a slice-expression base also fell to the 8B default. */ - { "slc_idx", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u16 = [10u16, 20u16, 30u16, 40u16];\n" - "\tlet i: int = 2;\n" - "\treturn a[0:4][i]: i32;\n" - "};\n", - 30 }, - - /* asrt_idx — N_TYPEASSERT base (v as *[4]u16)[i], stride 2 / MOVZWQ. - * #19old residual: a type-assert base also fell to the 8B default. */ - { "asrt_idx", - "package main;\n" - "type IP = (*[4]u16 | int);\n" - "export fn main() i32 = {\n" - "\tlet a: [4]u16 = [10u16, 20u16, 30u16, 40u16];\n" - "\tlet v: IP = &a;\n" - "\tlet i: int = 2;\n" - "\treturn (v as *[4]u16)[i]: i32;\n" - "};\n", - 30 }, -}; - -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/cbi_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/cbi_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/cbi_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/cbi_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/cbi_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/cbi_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "cast_base_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, - "cast_base_index[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "cast_base_index: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("cast_base_index: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/831_match_field_inplace.c b/test/wcc/831_match_field_inplace.c deleted file mode 100644 index 27959578..00000000 --- a/test/wcc/831_match_field_inplace.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * 831_match_field_inplace — match on a tagged-union struct-FIELD - * scrutinee. wwstage cgmatch reads an addressable BP-relative field - * (`match (b.t)`, b a local VALUE struct) IN PLACE at base.off + - * field.offset, dispatching off the field's own slot — no @match_spill - * copy. Verbatim mirror of cstage N_MATCH's in-place arm - * (cmd/w6c/cgen.c:10241-10296). Task #25 (M1). - * - * Pre-fix (wwstage): the non-ident cgmatch branch unconditionally - * spilled ANY non-ident scrutinee — including an addressable field — - * into @match_spill (+16B scratch → frame TEXT $48). cstage read the - * field where it lives (TEXT $32). Both stages were runtime-correct - * (ken's battery), so this was a latent rule-10 leanness gap, not a - * miscompile; the fix aligns wwstage DOWN to cstage so both emit - * byte-identical asm. - * - * Predicate (copied from cstage): in-place iff the scrutinee is an - * N_DOT whose lhs is a BARE N_IDENT with a stamped type, that type - * chases to TY_STRUCT, and the field resolves by name. Everything else - * — a *ptr-field base (`match (h.t)`, h:*struct, chases to TY_PTR so - * the by-name scan misses), a plain-local tagged ident, a call-result - * scrutinee — keeps its existing (in-place-ident / spill) path, - * unchanged. - * - * What this table pins, per row, across BOTH driver stages plus a - * cstage-vs-wwstage asm byte-identity check: - * (a) local VALUE-struct tagged field [NEW in-place arm] - * (b) *ptr-struct tagged field [stays on spill — TY_PTR] - * (c) plain-local tagged ident [unchanged in-place-ident] - * (d) call-result tagged scrutinee [spill] - * (e) bool->int payload remap on a field [width: 1-word variants] - * (f) str-payload (24B box) on a field [width: 3-word variant] - * - * The boundary, not just the new arm, is the subject: a regression - * that mis-routed (b)/(c)/(d) into the in-place arm, or failed to route - * (a)/(e)/(f) there, would break either the runtime exit code or the - * byte-identity. The $48->$32 frame convergence itself is ken's - * authoritative byte-id bind; a runtime test cannot observe 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[] = { - /* (a) NEW in-place arm: local VALUE struct, field reassigned to - * the int variant, matched in place. Returns 42. */ - { "local_field_int_inplace", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "fn main() i32 = {\n" - " let b: box = box { t = 0, n = 5 };\n" - " b.t = 42;\n" - " match (b.t) {\n" - " case let n: int => return n: i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n", - 42 }, - /* (b) *ptr-struct field: base chases to TY_PTR, the by-name scan - * misses, scrutinee falls to the spill path — exactly as cstage, - * no separate deref guard. Returns 9. */ - { "ptr_field_stays_spill", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "fn pick(h: *box) i32 = {\n" - " match (h.t) {\n" - " case let n: int => return n: i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n" - "fn main() i32 = {\n" - " let b: box = box { t = 9, n = 5 };\n" - " return pick(&b);\n" - "};\n", - 9 }, - /* (c) plain-local tagged ident: unchanged in-place-ident path - * (not N_DOT). Returns 5. */ - { "plain_local_ident", - "package main;\n" - "fn main() i32 = {\n" - " let t: (int | bool) = 5;\n" - " match (t) {\n" - " case let n: int => return n: i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n", - 5 }, - /* (d) call-result tagged scrutinee: not N_DOT, keeps the spill - * path. Returns 7. */ - { "call_result_spill", - "package main;\n" - "fn mk(b: bool) (int | bool) = {\n" - " if (b) { return 7; };\n" - " return false;\n" - "};\n" - "fn main() i32 = {\n" - " match (mk(true)) {\n" - " case let n: int => return n: i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n", - 7 }, - /* (e) width: a field whose variant remaps from bool to int. The - * in-place arm reads tag + one payload word at the field slot; - * the int arm also reads a sibling field (b.n) to prove the - * surrounding struct frame is undisturbed. Returns 7 + 5 = 12. */ - { "field_bool_to_int_remap", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "fn main() i32 = {\n" - " let b: box = box { t = true, n = 5 };\n" - " b.t = 7;\n" - " match (b.t) {\n" - " case let n: int => return (n + b.n): i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n", - 12 }, - /* (f) width: a 24B str-payload variant read in place at the - * field slot (tag@+0, .ptr@+8, .len@+16). Returns len("hello") - * = 5. */ - { "field_str_payload_24B", - "package main;\n" - "type box = struct { t: (str | int), n: int };\n" - "fn main() i32 = {\n" - " let b: box = box { t = 0, n = 5 };\n" - " b.t = \"hello\";\n" - " match (b.t) {\n" - " case let s: str => return len(s): i32;\n" - " case int => return 99;\n" - " };\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/wcmfi_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcmfi_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcmfi_%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", 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; -} - -/* asm_byte_identical — the whole point of M1 is that wwstage's frame - * stops bloating for an addressable field scrutinee, so the cstage vs - * wwstage text output must match byte-for-byte on every row. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcmfi_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcmfi_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcmfi_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "match_field_inplace: 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_field_inplace[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "match_field_inplace: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("match_field_inplace: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/833_inferred_array_global.c b/test/wcc/833_inferred_array_global.c deleted file mode 100644 index 890151ab..00000000 --- a/test/wcc/833_inferred_array_global.c +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 833_inferred_array_global — an inferred-type (annotation-less) module-global - * `let xs = [10, 20, 30];` whose init is an ARRAY literal must compile and run - * exactly as the explicit-typed `let xs: [3]int = [10, 20, 30]` does. The - * array twin of #135/#150-B (inferred float / inferred scalar global). - * WWSTAGE-ONLY bug (cstage already correct post-#150-B). - * - * Root (selfhost/cmd/wcc/check.ww exprtype N_ARRLIT, ~3294): for an inferred - * array let, exprtype synthesizes the array type — an N_TARRAY whose lhs is - * the element TNAME and whose rhs is a fresh N_INTLIT count node — and stamps - * e.type_ and arr.type_, but NEVER the count node's type_. checkletassign - * plants this synthesized arr on the inferred let's n.lhs; the pass-3 - * asserttyped walker then descends arr.rhs (the count N_INTLIT, an expr kind) - * with type_ == nil and trips `asserttyped: int` — a HARD loud build failure. - * wwstage could not compile an inferred array global at all. An explicit - * `[3]int` works because resolvewalk stamps its parser-built count node. - * - * The fix (check.ww, checker-only): stamp the synthesized count node's type_. - * The element TNAME needs no stamp (N_TNAME is not an asserttyped expr kind). - * cgen reads arr.rhs.uval, so the stamp is byte-id-inert: once the checker - * accepts the inferred array identically to an explicit one, the existing - * [N]T-global cgen path emits the IDENTICAL `.s`. cstage is UNTOUCHED. - * - * row | shape | want - * -----------+---------------------------------------------+----- - * int_idx | let xs = [10,20,30]; xs[1] (the bug) | 20 - * int_len | let xs = [10,20,30]; xs.len | 3 - * u8_elem | let bs = [1u8,2u8,3u8]; bs[2] (narrow elem) | 3 - * ctrl_expl | let xs: [3]int = [10,20,30]; xs[1] (explicit)| 20 - * - * Pre-fix the inferred rows ran wwstage=LOUD (asserttyped exit 1) and - * cstage=correct (cs≠ww); post-fix all four run in both stages and the - * inferred rows' `.s` is byte-identical between stages (and to the explicit - * form). ctrl_expl guards the explicit-[N]T path didn't regress and is the - * byte-id reference the inferred rows must match. - */ -#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[] = { - /* int_idx — the bug: inferred array global indexed, cast to int. - * Pre-fix wwstage tripped asserttyped (exit 1); cstage 20. */ - { "int_idx", - "package main;\n" - "let xs = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn xs[1]: i32;\n" - "};\n", - 20 }, - - /* int_len — inferred array global .len pseudo-field read. */ - { "int_len", - "package main;\n" - "let xs = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn xs.len: i32;\n" - "};\n", - 3 }, - - /* u8_elem — narrow (u8-default) element; the synthesized element - * type defaults to u8 here, exercising a non-int stride. */ - { "u8_elem", - "package main;\n" - "let bs = [1u8, 2u8, 3u8];\n" - "export fn main() i32 = {\n" - "\treturn bs[2]: i32;\n" - "};\n", - 3 }, - - /* ctrl_expl — explicit [3]int annotation; already worked. The - * byte-id reference the inferred int rows must match. No-regress. */ - { "ctrl_expl", - "package main;\n" - "let xs: [3]int = [10, 20, 30];\n" - "export fn main() i32 = {\n" - "\treturn xs[1]: i32;\n" - "};\n", - 20 }, -}; - -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/iag_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/iag_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/iag_%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; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/iag_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/iag_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/iag_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "inferred_array_global: 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, - "inferred_array_global[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "inferred_array_global: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("inferred_array_global: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/841_match_global_field.c b/test/wcc/841_match_global_field.c deleted file mode 100644 index d800f166..00000000 --- a/test/wcc/841_match_global_field.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 841_match_global_field — match on a tagged-union struct-FIELD of a - * GLOBAL (module-level) VALUE struct: `match (g.t)`, g a `let g: box`. - * Task #29. - * - * GATE-BLIND both-wrong regression from M1 (#25). M1 added an in-place - * N_DOT match arm that points the dispatch slot at base.off + - * field.offset, computing base.off via localfind. For a LOCAL base that - * is the field's true frame slot ($32 in-place, correct). For a GLOBAL - * base localfind returns its 0/not-found sentinel, so 0 + field.offset - * lands in the saved-BP / return-addr region (BP+0/BP+8): both stages - * emit `MOVQ (BP),AX` and read garbage. Both stages were identical-wrong - * (byte-id GREEN), so only the RUNTIME oracle catches it — pre-fix - * match(g.t) returns 213 instead of 42. - * - * Fix (both stages, rule 10): gate the in-place arm on a confirmed-LOCAL - * base. A base is GLOBAL iff localfind(base)==0 && let_islet(base) - * (cstage) / isletvar(c, base) (wwstage) — the same idiom cstage uses at - * cgen.c:2000 (also 4707/4730/5393). A global base falls THROUGH to the - * existing spill `else`, which cgexprs the scrutinee and resolves g(SB) - * correctly. M1's LOCAL in-place path is unchanged (row (d) pins it). - * - * Rows pin the RUNTIME exit code on BOTH driver stages (cstage + - * wwstage) — the discriminator is the value, not byte-id, since both - * stages were identical-wrong. A cstage-vs-wwstage asm byte-identity - * check is kept per row so the symmetric (rule-10) property stays pinned - * after the guard lands in both stages. - * (a) global VALUE-struct int-variant field [spill, g(SB)] - * (b) global field bool->int payload remap [1-word variant] - * (c) global field str-payload (24B box) [3-word variant] - * (d) local VALUE-struct field, in place [M1 regression guard] - */ -#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) GLOBAL value struct, field set to the int variant in main, - * matched. Pre-fix the in-place arm read BP+0 (213); post-fix the - * spill path resolves g(SB) and returns 42. */ - { "global_field_int", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "let g: box = box { t = 0, n = 5 };\n" - "fn main() i32 = {\n" - " g.t = 42;\n" - " match (g.t) {\n" - " case let n: int => return n: i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n", - 42 }, - /* (b) GLOBAL field reassigned, then matched; the int arm also reads - * the sibling field g.n to prove the surrounding global is - * undisturbed. Returns 7 + 5 = 12. The static initializer uses the - * ZERO payload (t = 0): a nonzero tagged-field payload in a static - * struct initializer trips a SEPARATE, deferred static-init DATA - * divergence (task #19/#30 — wwstage drops the payload word to 0, - * cstage emits it) that is orthogonal to the #29 match-cgen path - * exercised here; a zero payload is byte-identical across stages. */ - { "global_field_reassign_remap", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "let g: box = box { t = 0, n = 5 };\n" - "fn main() i32 = {\n" - " g.t = 7;\n" - " match (g.t) {\n" - " case let n: int => return (n + g.n): i32;\n" - " case bool => return 1;\n" - " };\n" - "};\n", - 12 }, - /* (c) GLOBAL field, 24B str-payload variant (tag@+0, .ptr@+8, - * .len@+16). Returns len("hello") = 5. */ - { "global_field_str_payload_24B", - "package main;\n" - "type box = struct { t: (str | int), n: int };\n" - "let g: box = box { t = 0, n = 5 };\n" - "fn main() i32 = {\n" - " g.t = \"hello\";\n" - " match (g.t) {\n" - " case let s: str => return len(s): i32;\n" - " case int => return 99;\n" - " };\n" - "};\n", - 5 }, - /* (d) regression guard: a LOCAL value-struct field still reads in - * place (M1's $32 path stays). Returns 42. */ - { "local_field_inplace_guard", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "fn main() i32 = {\n" - " let b: box = box { t = 0, n = 5 };\n" - " b.t = 42;\n" - " match (b.t) {\n" - " case let n: int => return n: i32;\n" - " case bool => return 1;\n" - " };\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/wcmgf_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/wcmgf_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcmgf_%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", 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; -} - -/* asm_byte_identical — the guard lands in BOTH stages, so cstage vs - * wwstage text output must stay byte-for-byte identical on every row. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wcmgf_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/wcmgf_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/wcmgf_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "match_global_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, - "match_global_field[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, rows[i].want); - fail++; - } - } - } - - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, - "match_global_field: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("match_global_field: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/843_tagged_staticinit.c b/test/wcc/843_tagged_staticinit.c deleted file mode 100644 index 0a580cb2..00000000 --- a/test/wcc/843_tagged_staticinit.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * 843_tagged_staticinit — #19 option A (rob's EXTRACT ruling). A tagged- - * union VALUE nested in module-level static-init DATA (an array element or - * a struct field) now emits the correct (tag@+0, payload@+8) box, the same - * SSoT a SCALAR tagged global and a runtime LOCAL box use. Pre-fix the - * nested member fell to the INT emitter, which mis-folded the payload into - * the tag word — cstage (value, value), wwstage (value, 0); NEITHER is - * (tag, value). Both BUILT and ran WRONG. - * - * The fix extracts a shared raw-byte core emit_tagged_bytes / emittaggedbytes - * (tag@0, int payload@8, zero-pad) from the scalar emitter, and the array/ - * struct member emitters call it at the full slot stride. A WIDE (str/slice) - * payload nested in an aggregate needs a reloc the raw core cannot place - * mid-directive, and a struct/non-foldable payload has no scalar form — - * both LOUD-REJECT (deferred, task #30; mirror of the slice-of-tagged - * reject). Only zero + int payload is implemented, which is exactly what - * the corpus exercises. - * - * row | shape | kind - * ---------------------+----------------------------------------+------ - * array_nonzero | let gs:[2](int|bool)=[42,7]; gs[0] | RUN 42 - * array_bool_variant | let gs:[2](int|bool)=[7,true]; gs[1] | RUN 2 - * | (NON-tag-0: tag=1,payload=1 — pre-fix | - * | (1,0) read bool=false -> 3) | - * struct_nonzero | let g:box{t:(int|bool)}=box{t=55};g.t | RUN 55 - * array_zero_then_set | let gs:[2](int|bool)=[0,0]; gs[0]=42 | RUN 42 - * | (zero-placeholder idiom — byte-id- | - * | neutral build, mirrors 841/989) | - * struct_zero_then_set | let g:box{t=0}; g.t=42 | RUN 42 - * plain_struct_ok | non-tagged struct static-init | RUN 9 - * wide_array_str | let gs:[2](int|str)=["hi",0] | REJECT - * wide_struct_str | let g:sbox{s:(int|str)}=sbox{s="hi"} | REJECT - * - * DISCRIMINATION: pre-fix array_nonzero BUILT and match(gs[0]) returned 1 - * (the match fallthrough — the payload 42 sat in the TAG word, matching no - * variant); post-fix it returns 42. array_bool_variant pins the NON-tag-0 - * edge (tag != payload word): pre-fix (1,0) read the bool payload as 0 - * (false) -> 3; post-fix (1,1) -> true -> 2. RUN rows also assert cstage/ - * wwstage asm byte-id (rule 10). The reject diagnostic core text is shared - * across stages (cstage adds the harness "ww: " prefix). NNN<950, self- - * contained (/tmp, no imports). - */ -#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 REJECT */ - int want_exit; - const char *diag; /* reject core substring (NULL for RUN rows) */ -}; - -static const struct row rows[] = { - /* RUN — non-zero int payload in an array element: now correct. */ - { "array_nonzero", - "package main;\n" - "let gs: [2](int | bool) = [42, 7];\n" - "export fn main() int = {\n" - "\tmatch (gs[0]) {\n" - "\tcase let n: int => return n;\n" - "\tcase bool => return 99;\n" - "\t};\n" - "\treturn 1;\n" - "};\n", - 1, 42, NULL }, - - /* RUN — NON-tag-0 edge: a bool element (variant index 1, payload 1). - * Pre-fix the int-fold emitted (1, 0) so the bool payload read as 0 - * (false) -> 3; post-fix (1, 1) -> true -> 2. */ - { "array_bool_variant", - "package main;\n" - "let gs: [2](int | bool) = [7, true];\n" - "export fn main() int = {\n" - "\tmatch (gs[1]) {\n" - "\tcase let n: int => return 1;\n" - "\tcase let b: bool => { if (b) { return 2; }; return 3; };\n" - "\t};\n" - "\treturn 4;\n" - "};\n", - 1, 2, NULL }, - - /* RUN — non-zero int payload in a struct field: now correct. */ - { "struct_nonzero", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "let g: box = box { t = 55, n = 5 };\n" - "fn main() i32 = {\n" - "\tmatch (g.t) {\n" - "\tcase let v: int => return v: i32;\n" - "\tcase bool => return 7;\n" - "\t};\n" - "\treturn 1;\n" - "};\n", - 1, 55, NULL }, - - /* RUN — zero-placeholder array idiom (mirror 989_taggedglobalindex): - * static-init [0,0] is byte-id-neutral, runtime write then matches. */ - { "array_zero_then_set", - "package main;\n" - "let gs: [2](int | bool) = [0, 0];\n" - "export fn main() int = {\n" - "\tgs[0] = 42;\n" - "\tmatch (gs[0]) {\n" - "\tcase let n: int => return n;\n" - "\tcase bool => return 9;\n" - "\t};\n" - "\treturn 1;\n" - "};\n", - 1, 42, NULL }, - - /* RUN — zero-placeholder struct idiom (mirror 841): {t=0} byte-id- - * neutral, runtime write then matches. */ - { "struct_zero_then_set", - "package main;\n" - "type box = struct { t: (int | bool), n: int };\n" - "let g: box = box { t = 0, n = 5 };\n" - "fn main() i32 = {\n" - "\tg.t = 42;\n" - "\tmatch (g.t) {\n" - "\tcase let v: int => return v: i32;\n" - "\tcase bool => return 7;\n" - "\t};\n" - "\treturn 1;\n" - "};\n", - 1, 42, NULL }, - - /* RUN — non-tagged struct static-init still emits + runs. 9. */ - { "plain_struct_ok", - "package main;\n" - "type pt = struct { x: int, y: int };\n" - "let p: pt = pt { x = 5, y = 9 };\n" - "export fn main() int = {\n" - "\treturn p.y;\n" - "};\n", - 1, 9, NULL }, - - /* REJECT — wide (str) payload tagged element in an array static-init: - * the reloc-in-aggregate case is deferred (task #30). */ - { "wide_array_str", - "package main;\n" - "let gs: [2](int | str) = [\"hi\", 0];\n" - "export fn main() int = {\n" - "\treturn 0;\n" - "};\n", - 0, 0, - "tagged-union array element static-init needs a zero/int payload" }, - - /* REJECT — wide (str) payload tagged field in a struct static-init. */ - { "wide_struct_str", - "package main;\n" - "type sbox = struct { s: (int | str) };\n" - "let g: sbox = sbox { s = \"hi\" };\n" - "export fn main() int = {\n" - "\treturn 0;\n" - "};\n", - 0, 0, - "tagged-union struct-field" }, -}; - -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/tsi_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/tsi_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/tsi_%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 struct row *r, int i) -{ - char s[96], tmpdir[64], outbin[128], errf[112], rmcmd[160], cmd[1280]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/tsn_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(s, sizeof s, "%s/tsn_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/tsn_%d_%d", tmpdir, getpid(), i); - snprintf(errf, sizeof errf, "%s/tsn_%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(r->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, r->diag) != NULL); - } - - runwait(rmcmd); - - /* build must NOT succeed AND the shared diagnostic must appear. */ - return (rc != 0 && have_diag) ? 0 : -1; -} - -/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[64], cs[64], ws[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/tsi_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/tsi_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/tsi_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; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - int rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - 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[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, "tagged_staticinit: 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, "tagged_staticinit[%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], - 100 + i) != 0) { - fprintf(stderr, "tagged_staticinit[%s][%s]: " - "expected a loud reject with \"%s\"\n", - drivers[d].name, rows[i].label, rows[i].diag); - fail++; - } - } - } - } - - /* RUN rows must be cstage/wwstage asm byte-id (rule 10). */ - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (!rows[i].expect_build) continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, "tagged_staticinit: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("tagged_staticinit: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/913_def_mangle_run.c b/test/wcc/913_def_mangle_run.c deleted file mode 100644 index e4d83d8f..00000000 --- a/test/wcc/913_def_mangle_run.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 913_def_mangle_run — byte-id regression net for #127: top-level - * `def X: T = LITERAL;` must emit cs==ww identical DATA symbols. Pre- - * #127 the wwstage `emitdefconstants` (selfhost/cmd/wcc/cgen.ww:1483) - * used a separate `d.exported`/`d.nmod` gate at the DATA-emit site - * instead of the same `emitsymname` mangler used at LOAD/CALL sites. - * When a non-exported `def X` in package main collided with the - * imported os.PATH_MAX (whose exported def landed too via the - * combined.ww embedding), the wwstage's DATA emitted the os one - * unqualified (`PATH_MAX(SB)`) while cstage emitted it module- - * qualified (`main.PATH_MAX(SB)`) — a 1-line cs/ww divergence in - * selfhost/cmd/ww/main.combined.ww that the 990-997 corpus didn't - * include. - * - * Fix (rule-12 sea-of-stars consolidation): replace the 8 lines of - * duplicated d.exported/d.nmod logic in emitdefconstants with a - * single `emitsymname(c, d.str)` call — the SAME mangler used at - * every LOAD/CALL site. Removes the asymmetry by construction: - * DATA-emit and LOAD-emit now route through one path. Cstage twin - * cmd/w6c/cgen.c:8494 already uses `mod_mangle(c, d->str)` for - * exactly this purpose. - * - * The original PATH_MAX consumer was cleaned up in 90d31c5 (drew's - * task #32 commit-1 dropped the duplicate main-local def in favor - * of the imported os.PATH_MAX), so the divergence is bootstrap- - * NEUTRAL post-#127 — confirmed by all 5 tool combined.ww emitting - * cs==ww byte-identical asm. This probe pins the consolidation - * forward: a new caller introducing a collision would now produce - * the same symbol shape from both stages, by construction. - * - * Rows exercise: - * - exported_def: `export def X: i32 = 4096;` — verifies the - * unqualified emit path (modlookup returns empty for exported). - * - main_local_def: `def X: i32 = 4096;` in package main — verifies - * the qualified emit path (modlookup returns "main"; both stages - * emit `main.X(SB)`). - * - load_site_match: a function reading the def — asserts the - * LOAD-site symbol shape matches the DATA-emit symbol shape (the - * core consolidation invariant — both routes through emitsymname). - * - * Each row carries (a) cstage `ww build` + run asserting the exit - * code and (b) w6c vs w6c_ww `.s` cmp (rule-10 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; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* Exported i64 def: emitsymname → modlookup returns empty - * (exported defs aren't tracked in mod_map per collectmods's - * `if (d.exported == 0)` gate) → unqualified `X(SB)` in DATA. - * i64 type chosen to dodge a separate pre-existing wwstage - * cgident divergence on narrow signed defs (extra MOVSXD on - * LOAD); the DEF-emit invariant this row pins is the DATA-side - * symbol shape, byte-id with cstage. */ - { "exported_def_i64", - "package main;\n" - "export def X: i64 = 100i64;\n" - "export fn main() i32 = { return X: i32; };\n", 100 }, - /* Non-exported (package-private) i64 def: modlookup returns - * "main" → `main.X(SB)` qualified in DATA. LOAD site (cgident - * via emitsymname) also produces `main.X(SB)` — consolidation - * invariant: DATA and LOAD agree by construction. */ - { "main_local_def_i64", - "package main;\n" - "def X: i64 = 77i64;\n" - "export fn main() i32 = { return X: i32; };\n", 77 }, - /* u64 width — pin that emitsymname doesn't differ on width. */ - { "u64_def", - "package main;\n" - "def X: u64 = 200u64;\n" - "export fn main() i32 = { return X: i32; };\n", 200 }, - /* Multiple defs at package level — pin emitsymname is consistent - * across multiple DATA-emit calls (the surrounding loop walks - * file.list; per-def state should not leak). */ - { "multi_def", - "package main;\n" - "def A: u64 = 10u64;\n" - "def B: u64 = 30u64;\n" - "export def C: u64 = 60u64;\n" - "export fn main() i32 = { return (A + B + C): i32; };\n", 100 }, - { 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, "defmangle: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\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/wwdef_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - /* #8: source, binary, .sepwork scratch and the byte-id .s - * dumps all live under tmpdir; rm -rf on every exit path. - * Symbol mangling is package/import-derived, not entry-path - * derived, so the in-tmpdir source keeps the cs==ww .s identical. */ - char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160]; - snprintf(src, sizeof src, "%s/wwdef_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwdef_%d_%d", tmpdir, getpid(), i); - snprintf(cs_s, sizeof cs_s, "%s/cs.s", tmpdir); - snprintf(ws_s, sizeof ws_s, "%s/ww.s", tmpdir); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { runwait(rmcmd); fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - 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\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - 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 def-mangle tests failed\n", fail, n); - return 1; - } - printf("defmangle: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/920_array_init_acceptiffits_run.c b/test/wcc/920_array_init_acceptiffits_run.c deleted file mode 100644 index 81fe8f39..00000000 --- a/test/wcc/920_array_init_acceptiffits_run.c +++ /dev/null @@ -1,281 +0,0 @@ -/* - * 920_array_init_acceptiffits_run — cs==ww net for #130: module-level - * `let A: [N]T = [...]` array-init "accept-if-fits". - * - * Pre-#130 divergence (Drew-adjudicated B/E synthesis): - * - cstage REJECTED all bare-int array elements ("init not assignable") - * — its array assignability path didn't wire the untyped-int→narrow- - * element coercion its OWN scalar path has. - * - wwstage ACCEPTED everything (no per-element validation): bare-int - * in-range (correct), bare-int out-of-range (silent truncate — - * miscompile), str→u8 (silent garbage). Top-level lets weren't even - * run through checkletassign. - * - * #130 fix = accept-if-fits, BOTH stages converge: - * - foldable int literal element → range-check against T via - * def_cast_fits/defcastfits (type table, rule 13). In-range accepts; - * out-of-range REJECTS loud (rule-7 / Drew: Hare range-checks at the - * literal-value level, ref/harec/src/types.c:923 promote_flexible). - * - non-foldable element → type_assignable / isassignable to T - * (untyped-int→u8 ok; str→u8 rejected). - * - cstage: check.c arrlit_init_fits fallback after whole-array - * type_assignable fails. - * - wwstage: check.ww checkletassign array branch + top-level lets now - * routed through checkletassign (were missed). Merges #146 (the - * wwstage str→u8 over-accept). - * - * Scope: ARRAY-init only. Scalar `let X: u8 = 300` truncation is a - * pre-existing language-wide gap (#148), deferred. - * - * This test asserts the ACCEPT rows compile + run + cs==ww byte-id, and - * (via the cstage/wwstage exit-code probe) that the REJECT rows fail to - * build on BOTH stages. Reject rows use a separate build-must-fail check. - * - * Accept rows (size strata 1B/4B/8B, sign, typed-vs-bare, both stages): - * - bare_inrange `[4]u8 = [1,2,3,4]` → A[0]==1 - * - bare_u32 `[4]u32 = [10,20,30,40]` → A[0]==10 - * - bare_u64 `[2]u64 = [5,6]` → A[0]==5 - * - signed_inrange `[4]i32 = [-1,-2,-3,-4]` → A[0]==-1 (255) - * - typed_regress `[4]u8 = [1u8,2u8,3u8,4u8]` → A[0]==1 - * - boundary_max `[2]u8 = [255, 0]` → A[0]==255 - * - * Reject rows (must FAIL build on both stages): - * - over_range `[2]u8 = [300, 1]` - * - neg_for_unsigned `[2]u8 = [-1, 0]` - * - str_to_u8 `[2]u8 = ["x", "y"]` - */ -#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 arow { const char *label; const char *src; int want_exit; }; -struct rrow { const char *label; const char *src; }; - -static const struct arow accept_rows[] = { - { "bare_inrange", - "package main;\n" - "let A: [4]u8 = [1, 2, 3, 4];\n" - "export fn main() i32 = { return A[0]: i32; };\n", 1 }, - { "bare_u32", - "package main;\n" - "let A: [4]u32 = [10, 20, 30, 40];\n" - "export fn main() i32 = { return A[0]: i32; };\n", 10 }, - { "bare_u64", - "package main;\n" - "let A: [2]u64 = [5, 6];\n" - "export fn main() i32 = { return A[0]: i32; };\n", 5 }, - { "signed_inrange", - "package main;\n" - "let A: [4]i32 = [-1, -2, -3, -4];\n" - "export fn main() i32 = { return A[0]; };\n", 255 /* -1 */ }, - { "typed_regress", - "package main;\n" - "let A: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - "export fn main() i32 = { return A[0]: i32; };\n", 1 }, - { "boundary_max", - "package main;\n" - "let A: [2]u8 = [255, 0];\n" - "export fn main() i32 = { return A[0]: i32; };\n", 255 }, - /* i8 signed boundary: -128..127 both in-range. */ - { "i8_boundary", - "package main;\n" - "let A: [2]i8 = [127, -128];\n" - "export fn main() i32 = { return A[0]: i32; };\n", 127 }, - /* Non-foldable element of matching type — exercises the else - * branch (type_assignable / isassignable, not the fold path). - * Must be a FUNCTION-BODY array: a module-level array with a - * non-foldable (runtime-valued) element isn't statically - * emittable (no const fold → no DATA row). The checker else - * branch fires identically for body lets, where the stack slot - * takes the runtime value. cs==ww verified. */ - { "nonfold_match", - "package main;\n" - "export fn main() i32 = {\n" - " let u: u8 = 5u8;\n" - " let A: [2]u8 = [u, 0u8];\n" - " return A[0]: i32;\n" - "};\n", 5 }, - { NULL, NULL, 0 } -}; - -static const struct rrow reject_rows[] = { - { "over_range", - "package main;\n" - "let A: [2]u8 = [300, 1];\n" - "export fn main() i32 = { return A[0]: i32; };\n" }, - { "neg_for_unsigned", - "package main;\n" - "let A: [2]u8 = [-1, 0];\n" - "export fn main() i32 = { return 0; };\n" }, - { "str_to_u8", - "package main;\n" - "let A: [2]u8 = [\"x\", \"y\"];\n" - "export fn main() i32 = { return 0; };\n" }, - /* Just-over-256 — pins the u8 upper bound exactly. */ - { "over_256", - "package main;\n" - "let A: [2]u8 = [256, 0];\n" - "export fn main() i32 = { return 0; };\n" }, - /* i8 over-range (128 > 127). */ - { "i8_over", - "package main;\n" - "let A: [2]i8 = [128, 0];\n" - "export fn main() i32 = { return 0; };\n" }, - /* Non-foldable wider-runtime int → narrow element needs an - * explicit cast in Hare; reject both stages (the else-branch - * reject path, cs==ww verified). */ - { "nonfold_wider", - "package main;\n" - "let i: int = 5;\n" - "let A: [2]u8 = [i, 0u8];\n" - "export fn main() i32 = { return 0; };\n" }, - { NULL, NULL } -}; - -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, "arrfit: w6c_ww missing — cannot run the " - "cs==ww gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - - /* Accept rows: cstage build + run + cs==ww byte-id. */ - for (int i = 0; accept_rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwafit_%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/wwafit_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwafit_%d_%d", - tmpdir, getpid(), i); - snprintf(cs_s, sizeof cs_s, "%s/wwafit_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwafit_%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(accept_rows[i].src, f); - fclose(f); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "accept[%s]: cstage build failed\n", - accept_rows[i].label); - fail++; runwait(rmcmd); continue; - } - int got = runwait(outbin); - if (got != accept_rows[i].want_exit) { - fprintf(stderr, "accept[%s]: exit %d, want %d\n", - accept_rows[i].label, got, accept_rows[i].want_exit); - fail++; - } - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "accept[%s]: w6c failed\n", accept_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, "accept[%s]: w6c_ww failed\n", accept_rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "accept[%s]: cs/ww .s DIFFER (rule-10)\n", - accept_rows[i].label); - fail++; - } - runwait(rmcmd); - } - - /* Reject rows: BOTH stages must fail to build (loud reject). */ - for (int i = 0; reject_rows[i].src; i++, n++) { - char src[64]; - snprintf(src, sizeof src, "/tmp/wwrfit_%d_%d.ww", getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(reject_rows[i].src, f); - fclose(f); - - char cmd[2048], dst[80]; - snprintf(dst, sizeof dst, "/tmp/wwrfit_%d_%d.s", getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, dst, src); - int cs_rc = runwait(cmd); - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, dst, src); - int ww_rc = runwait(cmd); - - if (cs_rc == 0) { - fprintf(stderr, "reject[%s]: cstage ACCEPTED (want reject)\n", - reject_rows[i].label); - fail++; - } - if (ww_rc == 0) { - fprintf(stderr, "reject[%s]: wwstage ACCEPTED (want reject)\n", - reject_rows[i].label); - fail++; - } - unlink(src); unlink(dst); - } - - if (fail) { - fprintf(stderr, "%d/%d array-init-accept-if-fits tests failed\n", - fail, n); - return 1; - } - printf("arrfit: %d/%d ok (accept: run + cs==ww; reject: both-stage fail)\n", - n, n); - return 0; -} diff --git a/test/wcc/921_amp_def_global_run.c b/test/wcc/921_amp_def_global_run.c deleted file mode 100644 index 94cc1003..00000000 --- a/test/wcc/921_amp_def_global_run.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 921_amp_def_global_run — address-of a module-level global (#149) + - * scalar-def address-of (#147 consolidation). Regression net for the - * cgaddr def-symbol / module-qualified gap: A.2 (0ed0b39) + A.3 - * (9e3bc4e) widened the VALUE-LOAD path for struct/array defs but the - * ADDRESS-OF twin (cgexpr N_UN TK_AMP / cgun) never handled def-symbols - * nor module-qualified globals, so `&def` / `&mod.global` emitted an - * uninitialised AX (PUSHQ AX / POPQ DI / CALL — no LEAQ) → garbage - * pointer, despite the DATA symbol existing. cs+ww were BYTE-IDENTICAL - * on the bug (gate-blind), so each row carries BOTH dimensions: - * (a) `ww build` + run, asserting the exit code == the value reached - * THROUGH the pointer (a garbage pointer yields a wrong value, not - * a false exit-0 — the probe lesson). - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10). - * - * Two operand shapes, one class (cgaddr-doesn't-handle-defs): - * Shape 1 `&G` — N_IDENT, G a top-level def (struct/array/scalar) - * Shape 2 `&mod.G` — N_DOT, mod a module qualifier (kind-agnostic: - * cross-module let / def / scalar / fn) - * The fold-4 / γ-cleanup driver is the cross-module struct-def row - * (`&mod.def_struct`, the `&math::f64info` shape). - * - * Non-addressable defs (str def inlined; computed-rhs float like - * `def NAN = 0.0/0.0` — #147, no DATA symbol) are NOT silently dropped: - * `&them` is a LOUD build error in both stages (rule-7). Those rows set - * want_build_fail. - * - * NOTE: array-element reads THROUGH a `*[N]T` pointer param have a - * pre-existing cs!=ww divergence (narrow MOVSXD/MOVL + esz-stride), - * unrelated to #149 — so the def-array row reads element 0 via a - * `*[N]T -> *T` cast + plain deref, which isolates the `&A` LEAQ. - * - * Single-file multi-package form (like 953_f64crossmod_run): `package - * myf; ... package main; import myf; ...` so w6c/w6c_ww see the cross- - * module reference without -I plumbing. - */ -#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; - int want_build_fail; /* 1: w6c must reject (loud rule-7 error) */ -}; - -static const struct row rows[] = { - /* Shape 1 — same-pkg `&def`. */ - { "def_struct", - "package main;\n" - "type pt = struct { x: i32, y: i32 };\n" - "def P: pt = pt{x=7, y=11};\n" - "fn rd(p: *pt) i32 = { return p.x + p.y; };\n" - "export fn main() i32 = { return rd(&P); };\n", 18, 0 }, - { "def_array", - "package main;\n" - "def A: [3]i64 = [18i64, 11i64, 0i64];\n" - "fn rd(p: *[3]i64) i64 = { let q: *i64 = p: *i64; return *q; };\n" - "export fn main() i32 = { return rd(&A): i32; };\n", 18, 0 }, - { "def_scalar_int", - "package main;\n" - "def N: i32 = 42;\n" - "fn rd(p: *i32) i32 = { return *p; };\n" - "export fn main() i32 = { return rd(&N); };\n", 42, 0 }, - { "def_scalar_float", - "package main;\n" - "def D: f64 = 0.5;\n" - "fn rd(p: *f64) f64 = { return *p; };\n" - "export fn main() i32 = { return (rd(&D) * 100.0): i32; };\n", 50, 0 }, - - /* Shape 2 — cross-pkg `&mod.G` (kind-agnostic). */ - { "xmod_def_struct", /* the &math::f64info / fold-4 shape */ - "package myf;\n" - "export type pt = struct { x: i32, y: i32 };\n" - "export def P: pt = pt{x=7, y=11};\n" - "package main;\n" - "import myf;\n" - "fn rd(p: *myf.pt) i32 = { return p.x + p.y; };\n" - "export fn main() i32 = { return rd(&myf.P); };\n", 18, 0 }, - { "xmod_let_struct", - "package myf;\n" - "export type pt = struct { x: i32, y: i32 };\n" - "export let L: pt = pt{x=7, y=11};\n" - "package main;\n" - "import myf;\n" - "fn rd(p: *myf.pt) i32 = { return p.x + p.y; };\n" - "export fn main() i32 = { return rd(&myf.L); };\n", 18, 0 }, - { "xmod_scalar_let", - "package myf;\n" - "export let S: i32 = 42;\n" - "package main;\n" - "import myf;\n" - "fn rd(p: *i32) i32 = { return *p; };\n" - "export fn main() i32 = { return rd(&myf.S); };\n", 42, 0 }, - { "xmod_func", /* Shape 2 TY_FN leaf → LEAQ fn(SB) */ - "package myf;\n" - "export fn helper() i32 = { return 42; };\n" - "package main;\n" - "import myf;\n" - "fn take(p: *fn() i32) i32 = { return 7; };\n" - "export fn main() i32 = { return take(&myf.helper); };\n", 7, 0 }, - - /* Regressions — paths the fix must NOT disturb. */ - { "reg_let_struct", /* &let_struct worked same-pkg pre-#149 */ - "package main;\n" - "type pt = struct { x: i32, y: i32 };\n" - "let L: pt = pt{x=7, y=11};\n" - "fn rd(p: *pt) i32 = { return p.x + p.y; };\n" - "export fn main() i32 = { return rd(&L); };\n", 18, 0 }, - { "reg_amp_arr_idx", /* &local_arr[i] */ - "package main;\n" - "fn rd(p: *i64) i64 = { return *p; };\n" - "export fn main() i32 = {\n" - " let a: [3]i64 = [5i64, 18i64, 9i64];\n" - " let p: *i64 = &a[1];\n" - " return rd(p): i32; };\n", 18, 0 }, - - /* rule-7 LOUD: `&non-addressable def` (no DATA symbol). */ - { "fail_str_def", - "package main;\n" - "def MSG: str = \"hello\";\n" - "fn rd(p: *str) i32 = { return 7; };\n" - "export fn main() i32 = { return rd(&MSG); };\n", 0, 1 }, - { "fail_computed_float", /* #147 `def NAN = 0.0/0.0` shape */ - "package main;\n" - "def D: f64 = 1.0 / 4.0;\n" - "fn rd(p: *f64) f64 = { return *p; };\n" - "export fn main() i32 = { return (rd(&D) * 100.0): i32; };\n", 0, 1 }, - - { NULL, NULL, 0, 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, "amp_def_global: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64], rmcmd[80]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwamp_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - char src[128]; - snprintf(src, sizeof src, "%s/wwamp_%d_%d.ww", - tmpdir, getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { runwait(rmcmd); fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - if (rows[i].want_build_fail) { - /* rule-7 LOUD: both stages must REJECT (`&` of a non- - * addressable def has no DATA symbol). Build to .s and - * assert non-zero exit on each stage. */ - char cmd[2048], dump[128]; - snprintf(dump, sizeof dump, "%s/wwamp_%d_%d.s", - tmpdir, getpid(), i); - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, dump, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "row[%s]: w6c ACCEPTED &non-" - "addressable-def (want loud reject)\n", - rows[i].label); - fail++; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, dump, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "row[%s]: w6c_ww ACCEPTED &non-" - "addressable-def (want loud reject)\n", - rows[i].label); - fail++; - } - runwait(rmcmd); - continue; - } - - /* (a) cstage build + run; assert exit == value reached - * through the pointer. */ - char outbin[128]; - snprintf(outbin, sizeof outbin, "%s/wwamp_%d_%d", - tmpdir, getpid(), i); - - 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\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate. */ - char cs_s[128], ws_s[128]; - snprintf(cs_s, sizeof cs_s, "%s/wwamp_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwamp_%d_%d_ww.s", - tmpdir, getpid(), i); - - 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 amp-def-global tests failed\n", fail, n); - return 1; - } - printf("amp_def_global: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/930_struct_tuple_field_slot.c b/test/wcc/930_struct_tuple_field_slot.c deleted file mode 100644 index 299eb463..00000000 --- a/test/wcc/930_struct_tuple_field_slot.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 930_struct_tuple_field_slot — project #237: a tuple-typed struct field - * must contribute its real slot width to the enclosing struct's slotsize. - * - * wwstage's checker `fieldslotsize` (check.ww) summed struct field SLOT - * sizes to stamp a struct's tinfo.slotsize, but had no TY_TUPLE arm — a - * tuple field fell to the 8B default. So `S = struct { f: ([]u8,[]u8) }` - * stamped slotsize=8 while size=48 (the natural sum, correct). A `let s:S` - * slot is allocated off ti.slotsize (cgenutil.ww slotsize), so wwstage - * reserved an 8-byte frame slot for a 48-byte struct: a SILENT stack- - * corrupting miscompile. cstage has no size/slotsize split (it sizes the - * field at f->type->size=48 throughout), so the two stages DIVERGED on the - * emitted frame ($16 wwstage vs $64 cstage) — invisible to a cstage-only - * check, caught only by cs==ww byte-id (rule 10). The fix adds the TY_TUPLE - * arm (return the tuple's own slotsize), aligning fieldslotsize with the - * cgen-side cgenutil.ww fieldsize that already returns 48. - * - * GATE: pure cs==ww .s byte-id. No runtime — the divergence is a frame/slot - * SIZE, fully visible in the emitted assembly. The fixture takes &s.f so the - * struct slot is materialised; no over-cap call is involved (this isolates - * #237 from the #234 store-routing). A FAIL means the stages disagree on the - * struct's frame size (the #237 regression). - */ -#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); - int 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; }; - -static const struct row rows[] = { - /* one tuple field: slotsize must be 48, not the 8B default. */ - { "single_tuple_field", - "package main;\n" - "type S = struct { f: ([]u8, []u8) };\n" - "export fn main() i32 = {\n" - " let s: S;\n" - " let p: *int = (&s.f): *int;\n" - " p[1] = 1;\n" - " return 0;\n" - "};\n" }, - /* tuple field preceded by a scalar: foff!=0, slot still full-width. */ - { "hdr_then_tuple_field", - "package main;\n" - "type S = struct { hdr: int, f: ([]u8, []u8) };\n" - "export fn main() i32 = {\n" - " let s: S;\n" - " s.hdr = 9;\n" - " let p: *int = (&s.f): *int;\n" - " p[1] = 1;\n" - " return 0;\n" - "};\n" }, - { NULL, NULL }, -}; - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - 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 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, "struct_tuple_field_slot: 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 src[64], cs_s[64], ws_s[64], cmd[2048]; - snprintf(src, sizeof src, "/tmp/stfs_%d_%d.ww", getpid(), i); - snprintf(cs_s, sizeof cs_s, "/tmp/stfs_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/stfs_%d_%d_ww.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - 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++; unlink(src); 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++; unlink(src); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER " - "(#237 struct tuple-field slot-size regression)\n", - rows[i].label); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d struct-tuple-field-slot tests failed\n", - fail, n); - return 1; - } - printf("struct_tuple_field_slot: %d/%d ok (cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/944_peellint_gate.c b/test/wcc/944_peellint_gate.c deleted file mode 100644 index 25d47d98..00000000 --- a/test/wcc/944_peellint_gate.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 944_peellint_gate — teeth for tools/peellint (#5 alias-arc B7). - * - * The lint is the enforcement half of the close-by-construction - * contract: zero raw under-token reads in scope outside the annotated - * whitelist. A gate without negative validation can rot green (B4 - * precedent), so this test pins BOTH directions: - * - * 1. real tree at HEAD lints CLEAN (the closure proof itself); - * 2. a re-introduced raw peel REDS the lint — C `->under` ternary - * and ww `.under` if-peel, the four-times-burned spellings; - * 3. a corrupted whitelist annotation REDS the lint (token-bounded - * `peel-ok` matcher: `peel-okk-…` must NOT exempt); - * 4. regression rows that must stay GREEN: the check.ww:3683 - * "io.underread" prose (token bound), a code read of a longer - * field (`s.underread`), comment-quoted `.under`/`->under` - * prose (comment strip), and the already-landed `peellint-ok` - * sibling spelling (history is not re-spelled); - * 5. review-found evasion spellings REDS (B7 review probes E1-E6, - * every one compiles): `t -> under` spacing, `t->`/EOL + - * `under` next line (both stages' split), C deref-dot - * `(*t).under`, ww `t. under`, and a string literal containing - * a block-comment OPENER token that blinded the old regex - * comment-strip for the rest of the file. - * 6. RULE 2 (#101/#109) — bare primsize() in the ww stage is the - * alias-blind width shape aliasprimsize() supersedes. A bare - * `primsize(` REDS; the SSoT wrapper `aliasprimsize(` must NOT - * (left word boundary); the evasion spellings (space-before-paren, - * name-at-EOL line split, string-blind block-comment opener in a - * literal, paren-wrap `(primsize)(nm)`, function-value bind - * `let p = primsize` — the last two reviewer-109-found, both - * compile + run) all RED; a - * `primsize-ok` annotation exempts; a corrupted one does not; the - * primsize-ok and peel-ok windows are independent (neither blinds - * the other's shape); and a C-file `primsize(` is out of scope - * (the C stage chases via type_chase_named, no primsize symbol). - * - * Scratch trees live under /tmp and exercise the lint via its ROOT - * override (sizelint-style), so the real tree is never touched. - */ -#include -#include -#include -#include -#include -#include - -static char root[1024]; /* repo root (cwd when run via test/run) */ - -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 *body) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - fputs(body, f); - fclose(f); - return 0; -} - -/* lint_scratch — run tools/peellint over a one-file scratch tree and - * return its exit code. relpath selects the in-scope directory. */ -static int -lint_scratch(const char *scratch, const char *relpath, const char *body) -{ - char cmd[2048], path[1400]; - snprintf(cmd, sizeof cmd, "rm -rf %s", scratch); - runwait(cmd); - snprintf(path, sizeof path, "%s/%s", scratch, relpath); - char dir[1400]; - snprintf(dir, sizeof dir, "%s", path); - char *slash = strrchr(dir, '/'); - if (slash) *slash = '\0'; - snprintf(cmd, sizeof cmd, "mkdir -p %s", dir); - if (runwait(cmd) != 0) return -1; - if (write_file(path, body) != 0) return -1; - snprintf(cmd, sizeof cmd, - "ROOT=%s sh %s/tools/peellint >/dev/null 2>&1", scratch, root); - int rc = runwait(cmd); - snprintf(cmd, sizeof cmd, "rm -rf %s", scratch); - runwait(cmd); - return rc; -} - -struct lintrow { const char *label; const char *relpath; - const char *body; int wantexit; }; - -static const struct lintrow lintrows[] = { - { "reinject_c_peel", "cmd/w6c/x.c", - "static Type *f(Type *t) {\n" - "\tType *u = (t->kind == TY_NAMED) ? t->under : t;\n" - "\treturn u;\n" - "}\n", 1 }, - { "reinject_ww_peel", "selfhost/cmd/wcc/x.ww", - "fn f(t: *tinfo) *tinfo = {\n" - "\tif (t.kind == tykind.TY_NAMED) { return t.under; };\n" - "\treturn t;\n" - "};\n", 1 }, - { "annotated_c_peel_ok", "cmd/w6c/x.c", - "static Type *f(Type *t) {\n" - "\tType *u = (t->kind == TY_NAMED) ? t->under : t; " - "/* peel-ok: probe */\n" - "\treturn u;\n" - "}\n", 0 }, - { "corrupt_annotation", "cmd/w6c/x.c", - "static Type *f(Type *t) {\n" - "\tType *u = (t->kind == TY_NAMED) ? t->under : t; " - "/* peel-okk-corrupt: probe */\n" - "\treturn u;\n" - "}\n", 1 }, - { "peellint_ok_spelling", "selfhost/cmd/wcc/x.ww", - "fn f(t: *tinfo, u: *tinfo) void = {\n" - "\t// peellint-ok: construction\n" - "\tt.under = u;\n" - "};\n", 0 }, - /* check.ww:3683 regression: prose token "io.underread" must not - * trip the ww matcher (token bound), nor `.under` quoted in a - * line comment (comment strip). */ - { "io_underread_prose", "selfhost/cmd/wcc/x.ww", - "fn f(x: int) int = {\n" - "\t// #199 repro io.underread -> (size|io.eof|io.error)\n" - "\t// the NAMED.under chain stays terminating\n" - "\tlet v: int = x + 2; // io.underread again\n" - "\treturn v;\n" - "};\n", 0 }, - { "code_longer_field", "selfhost/cmd/wcc/x.ww", - "fn f(s: stream) int = { return s.underread; };\n", 0 }, - { "c_block_comment_prose", "cmd/wcc/x.c", - "/* walk the chain: a raw t->under read here\n" - " * would single-peel; t->under in prose only. */\n" - "int g(int x) { return x; }\n", 0 }, - { "lib_ww_in_scope", "lib/ww/x.ww", - "fn f(t: *tinfo) *tinfo = {\n" - "\tif (t.kind == tykind.TY_NAMED) { return t.under; };\n" - "\treturn t;\n" - "};\n", 1 }, - /* Review-found evasions (all compile; pre-amendment lint passed - * every one of them green): the matcher must red each. */ - { "evade_c_spacing", "cmd/w6c/x.c", - "static Type *f(Type *t) {\n" - "\treturn (t->kind == TY_NAMED) ? t -> under : t;\n" - "}\n", 1 }, - { "evade_c_linesplit", "cmd/w6c/x.c", - "static Type *f(Type *t) {\n" - "\treturn (t->kind == TY_NAMED) ? t->\n" - "\t under : t;\n" - "}\n", 1 }, - { "evade_c_derefdot", "cmd/w6c/x.c", - "static Type *f(Type *t) {\n" - "\treturn (t->kind == TY_NAMED) ? (*t).under : t;\n" - "}\n", 1 }, - { "evade_ww_dotspace", "selfhost/cmd/wcc/x.ww", - "fn f(t: *tinfo) *tinfo = {\n" - "\tif (t.kind == tykind.TY_NAMED) { return t. under; };\n" - "\treturn t;\n" - "};\n", 1 }, - { "evade_ww_linesplit", "selfhost/cmd/wcc/x.ww", - "fn f(t: *tinfo) *tinfo = {\n" - "\tlet u: *tinfo = t.\n" - "\t\tunder;\n" - "\treturn u;\n" - "};\n", 1 }, - { "evade_c_string_blind", "cmd/w6c/x.c", - "static const char *s = \"/*\";\n" - "static Type *f(Type *t) { return t->under; }\n", 1 }, - /* RULE 2 (#101/#109): bare primsize() outside the chase is the - * forbidden alias-blind width shape; aliasprimsize is the SSoT. */ - { "prim_bare", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\tlet z: i32 = primsize(nm);\n" - "\treturn z;\n" - "};\n", 1 }, - { "prim_evade_spacing", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\tlet z: i32 = primsize (nm);\n" - "\treturn z;\n" - "};\n", 1 }, - { "prim_evade_linesplit", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\tlet z: i32 = primsize\n" - "\t (nm);\n" - "\treturn z;\n" - "};\n", 1 }, - { "prim_evade_string_blind", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen) str = {\n" - "\tlet s: str = \"/*\";\n" - "\tlet z: i32 = primsize(s);\n" - "\treturn s;\n" - "};\n", 1 }, - /* review-109 evasions: both COMPILE + run (verified) yet slipped a - * `primsize(`-only matcher — the token rule reds them. */ - { "prim_evade_parenwrap", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\tlet z: i32 = (primsize)(nm);\n" - "\treturn z;\n" - "};\n", 1 }, - { "prim_evade_fnvalue", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\tlet p = primsize;\n" - "\treturn p(nm);\n" - "};\n", 1 }, - /* aliasprimsize() is the SSoT wrapper — its `primsize` suffix must - * NOT trip the left-word-bounded matcher (the central evasion). */ - { "prim_alias_wrapper_ok", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\tlet z: i32 = aliasprimsize(c, nm);\n" - "\treturn z;\n" - "};\n", 0 }, - { "prim_annotated_ok", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\t// primsize-ok: chase body\n" - "\tlet z: i32 = primsize(nm);\n" - "\treturn z;\n" - "};\n", 0 }, - { "prim_corrupt_annotation", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\t// primsize-okk-corrupt: nope\n" - "\tlet z: i32 = primsize(nm);\n" - "\treturn z;\n" - "};\n", 1 }, - /* the two exemption windows are independent: primsize-ok must not - * blind an under-token peel, nor peel-ok a bare primsize. */ - { "prim_window_no_cross_under", "selfhost/cmd/wcc/x.ww", - "fn f(t: *tinfo) *tinfo = {\n" - "\t// primsize-ok: must NOT exempt the under peel below\n" - "\treturn t.under;\n" - "};\n", 1 }, - { "peel_window_no_cross_prim", "selfhost/cmd/wcc/x.ww", - "fn f(c: *cgen, nm: str) i32 = {\n" - "\t// peel-ok: must NOT exempt the primsize below\n" - "\tlet z: i32 = primsize(nm);\n" - "\treturn z;\n" - "};\n", 1 }, - /* RULE 2 is ww-only: the C stage dealiases via type_chase_named and - * has no primsize symbol — a C `primsize(` is not in scope. */ - { "prim_c_file_out_of_scope", "cmd/w6c/x.c", - "static int primsize(const char *n) { return 0; }\n" - "int g(void) { return primsize(\"u8\"); }\n", 0 }, -}; - -int -main(void) -{ - if (getcwd(root, sizeof root) == NULL) return 1; - - int total = 0, fail = 0; - char cmd[2048], scratch[256]; - - /* 1. The closure proof: the real tree lints clean at HEAD. */ - total++; - snprintf(cmd, sizeof cmd, "sh %s/tools/peellint", root); - if (runwait(cmd) != 0) { - fprintf(stderr, "peellint_gate: real tree NOT clean\n"); - fail++; - } - - /* 2-4. Scratch rows: negative validation + matcher regressions. */ - int n = (int)(sizeof lintrows / sizeof lintrows[0]); - for (int i = 0; i < n; i++) { - total++; - snprintf(scratch, sizeof scratch, "/tmp/plint_%d_%d", - getpid(), i); - int got = lint_scratch(scratch, lintrows[i].relpath, - lintrows[i].body); - if (got != lintrows[i].wantexit) { - fprintf(stderr, "row[%s]: lint exit %d, want %d\n", - lintrows[i].label, got, lintrows[i].wantexit); - fail++; - } - } - - if (fail) { - fprintf(stderr, "peellint_gate: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("peellint_gate: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/949_errtype_compare.c b/test/wcc/949_errtype_compare.c deleted file mode 100644 index 1c9fbbe2..00000000 --- a/test/wcc/949_errtype_compare.c +++ /dev/null @@ -1,271 +0,0 @@ -/* - * 949_errtype_compare — #246: the wwstage checker must REJECT a - * comparison whose operand is an error type (`!T`) paired with a - * differing type, e.g. `strconv.invalid != i32`. cstage's cbinop - * (cmd/wcc/check.c:952) routes every comparison through unify_arith, - * which loud-rejects the differing-types pair; the wwstage checker's - * binoptype returned `bool` for comparisons WITHOUT any unify step, so - * it silently accepted a program cstage rejects — a rule-10 break. - * - * The fix (selfhost/cmd/wcc/check.ww binoptype) mirrors cstage DOWN - * (rule 10), scoped to the error-type operand so the broad - * differing-types diagnostic (typeeqast-vs-cstage-type_eq asymmetry - * risk) stays out of wwstage. - * - * K_BUILDERR rows: build FAILS with the differing-types diagnostic on - * BOTH drivers. K_RUN rows: build+run exit 0 on BOTH drivers AND the - * cs==ww .s is byte-identical — the positive controls pin that an - * error-type compared with itself, and a plain int compare, still pass - * (cstage allows both via type_eq). - * - * 940/945 precedent: ww_ww builds /tmp fixtures, so the selfhost-tree - * sibling-write race does not apply. - */ -#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 -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; -} - -static int -file_contains(const char *path, const char *needle) -{ - FILE *f = fopen(path, "rb"); - if (!f) return 0; - char buf[8192]; - size_t n = fread(buf, 1, sizeof buf - 1, f); - fclose(f); - buf[n] = '\0'; - return strstr(buf, needle) != NULL; -} - -#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */ -#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */ - -struct row { const char *label; const char *src; int kind; int want; - const char *experr; }; - -static const struct row rows[] = { - /* the headline #246 case: error type `invalid` (!i32) compared with - * a plain i32 via `!=`. cstage rejects; wwstage must now too. */ - { "neq_invalid_i32", - "package main;\n" - "type invalid = !i32;\n" - "export fn main() i32 = {\n" - " let e: invalid = 5: invalid;\n" - " let n: i32 = 3;\n" - " if (e != n) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* sibling op: `==` over the same mismatched error/int pair. */ - { "eq_invalid_i32", - "package main;\n" - "type invalid = !i32;\n" - "export fn main() i32 = {\n" - " let e: invalid = 5: invalid;\n" - " let n: i32 = 3;\n" - " if (e == n) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* ordered comparison shares cstage's unify_arith route; the error - * operand must reject there too (closes the comparison family, not - * just `!=`). */ - { "lt_invalid_i32", - "package main;\n" - "type invalid = !i32;\n" - "export fn main() i32 = {\n" - " let e: invalid = 5: invalid;\n" - " let n: i32 = 3;\n" - " if (e < n) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* positive control: error type compared with ITSELF. cstage's - * type_eq holds, so unify_arith accepts — wwstage must not - * over-reject (the fix is scoped to a DIFFERING pair). */ - { "neq_same_error", - "package main;\n" - "type invalid = !i32;\n" - "export fn main() i32 = {\n" - " let e: invalid = 5: invalid;\n" - " let f: invalid = 6: invalid;\n" - " if (e != f) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* positive control: plain int compare, untouched by the fix. */ - { "neq_int_int", - "package main;\n" - "export fn main() i32 = {\n" - " let a: i32 = 1;\n" - " let b: i32 = 2;\n" - " if (a != b) { return 0; };\n" - " return 1;\n" - "};\n", - K_RUN, 0, NULL }, -}; - -/* 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 src[128], tmpdir[96], errf[128], outbin[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/etc_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/etc_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/etc_%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, "%s build -o %s %s >/dev/null 2>%s", - driver, outbin, src, errf); - int brc = runwait(cmd); - - if (r->kind == K_BUILDERR) { - int ok = (brc != 0) - && (r->experr == NULL || file_contains(errf, r->experr)); - if (!ok) - fprintf(stderr, "row[%s]: %s expected loud #246 builderr " - "(brc=%d)\n", r->label, driver, brc); - runwait(rmcmd); - return ok ? 0 : 1; - } - - 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) for the K_RUN rows. */ -static int -byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i) -{ - char src[96], cs_s[96], ws_s[96], cmd[1024]; - snprintf(src, sizeof src, "/tmp/etc_bi_%d_%d.ww", getpid(), i); - snprintf(cs_s, sizeof cs_s, "/tmp/etc_bi_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/etc_bi_%d_%d_ww.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - int rc = 0; - 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", r->label); rc = 1; } - else { - 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", r->label); rc = 1; } - else if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER (#246)\n", - r->label); - rc = 1; - } - } - unlink(src); unlink(cs_s); unlink(ws_s); - return rc; -} - -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], w6c[640], w6c_ww[640]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - struct { const char *name; const char *path; 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].path, X_OK) != 0) { - fprintf(stderr, "errtype_compare: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < n; i++) { - total++; - if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++; - } - } - - if (access(w6c_ww, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].kind != K_RUN) continue; - total++; - if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++; - } - } - - if (fail) { - fprintf(stderr, "errtype_compare: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("errtype_compare: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/949_intbinop_mismatch.c b/test/wcc/949_intbinop_mismatch.c deleted file mode 100644 index a481dfb1..00000000 --- a/test/wcc/949_intbinop_mismatch.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * 949_intbinop_mismatch — #26: ww requires an explicit integer - * conversion in binary ops (Go-faithful, user-blessed). cstage's cbinop - * routes arithmetic/bitwise/comparison through unify_arith - * (cmd/wcc/check.c:1034), which loud-rejects a typed/typed operand - * mismatch (check.c:1068). The wwstage checker's unifyarith returned the - * lhs type for any mismatch (silent accept) AND the comparison arm never - * called it at all, so `int < len(s)` (len() = i32) and `int & i32` - * silently compiled where cstage rejects — a rule-10 break (and a latent - * signed/unsigned miscompile for `int < uint`). - * - * The fix (selfhost/cmd/wcc/check.ww) aligns wwstage DOWN: unifyarith - * loud-rejects a mismatched typed pair, keeping only cstage's one-sided - * alias-vs-base promotion (check.c:1060), and the ORDERED comparison arm - * now routes through unifyarith. A rune LITERAL stays assignable to an - * integer (cstage's untyped_rune, type.c:379), so `c - '0'` is unaffected. - * - * K_BUILDERR rows: build FAILS with the differing-types diagnostic on - * BOTH drivers. K_RUN rows: build+run exit 0 on BOTH drivers AND the - * cs==ww .s is byte-identical — the positive controls pin that matched - * pairs, an explicit cast, the alias-vs-base promotion (part (a), the - * LOAD-BEARING row), and a rune-literal arithmetic mix all still pass. - * - * 940/945 precedent: ww_ww builds /tmp fixtures, so the selfhost-tree - * sibling-write race does not apply. - */ -#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 -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; -} - -static int -file_contains(const char *path, const char *needle) -{ - FILE *f = fopen(path, "rb"); - if (!f) return 0; - char buf[8192]; - size_t n = fread(buf, 1, sizeof buf - 1, f); - fclose(f); - buf[n] = '\0'; - return strstr(buf, needle) != NULL; -} - -#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */ -#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */ - -struct row { const char *label; const char *src; int kind; int want; - const char *experr; }; - -static const struct row rows[] = { - /* headline #26: `int < len(s)` (len() returns i32) — the latent - * signed-mix comparison that wwstage silently accepted. */ - { "int_lt_len_i32", - "package main;\n" - "export fn main() i32 = {\n" - " let a: int = 1;\n" - " let s: []u8 = [];\n" - " if (a < len(s)) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* signed/unsigned mismatch — the miscompile-prone shape. */ - { "int_lt_uint", - "package main;\n" - "export fn main() i32 = {\n" - " let a: int = 1;\n" - " let b: uint = 2;\n" - " if (a < b) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* differing-width signed pair. */ - { "int_lt_i64", - "package main;\n" - "export fn main() i32 = {\n" - " let a: int = 1;\n" - " let b: i64 = 2i64;\n" - " if (a < b) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* bitwise feeds the same unifyarith path (check.ww :2598). */ - { "int_and_i32", - "package main;\n" - "export fn main() i32 = {\n" - " let a: int = 1;\n" - " let b: i32 = 2;\n" - " let r: int = a & b;\n" - " if (r < 0) { return 1; };\n" - " return 0;\n" - "};\n", - K_BUILDERR, 0, "operands have differing types" }, - /* positive control: matched int/int comparison. */ - { "int_lt_int", - "package main;\n" - "export fn main() i32 = {\n" - " let a: int = 1;\n" - " let b: int = 2;\n" - " if (a < b) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* positive control: matched i32/i32. */ - { "i32_lt_i32", - "package main;\n" - "export fn main() i32 = {\n" - " let a: i32 = 1;\n" - " let b: i32 = 2;\n" - " if (a < b) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* positive control: i32 < len(s) — both i32, the canonical loop. */ - { "i32_lt_len", - "package main;\n" - "export fn main() i32 = {\n" - " let s: []u8 = [];\n" - " let a: i32 = 0;\n" - " if (a < len(s)) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* positive control: explicit cast resolves the #26 mix. */ - { "int_cast_len", - "package main;\n" - "export fn main() i32 = {\n" - " let s: []u8 = [];\n" - " let a: int = 0;\n" - " if (a < len(s): int) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* LOAD-BEARING positive control for part (a): a one-sided alias vs - * its base promotes (cstage check.c:1060). Drops if the alias-chase - * arm is missing or mis-detects a primitive as a named type. */ - { "alias_vs_base", - "package main;\n" - "type myint = i32;\n" - "export fn main() i32 = {\n" - " let a: myint = 1: myint;\n" - " let b: i32 = 2;\n" - " if (a < b) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* positive control: a rune LITERAL stays assignable to an integer - * (cstage's untyped_rune, type.c:379); `c - '0'` must NOT trip the - * #26 reject. */ - { "rune_lit_arith", - "package main;\n" - "export fn main() i32 = {\n" - " let c: u8 = 53u8;\n" - " let r: u8 = c - '0';\n" - " if (r < 10u8) { return 0; };\n" - " return 0;\n" - "};\n", - K_RUN, 0, NULL }, - /* LOAD-BEARING positive control: a chained same-enum bitwise OR - * (`flag.A | flag.B | flag.C`, the w6l os.flag pattern). Both operands - * are the SAME enum type, which cstage's unify_arith accepts via - * type_eq's identity check (type.c:250) — wwstage must accept it too. - * Drops if typeeqast lacks the identity fast-path: the #26 reject then - * over-rejects a same-enum binop, breaking the w6l self-compile (994). */ - { "enum_flag_or", - "package main;\n" - "type flag = enum uint { A = 1, B = 2, C = 4 };\n" - "export fn main() i32 = {\n" - " let f: flag = flag.A | flag.B | flag.C;\n" - " return (f: i32) - 7;\n" - "};\n", - K_RUN, 0, NULL }, -}; - -/* 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[128], outbin[128], errf[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/ibm_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/ibm_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/ibm_%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, "%s build -o %s %s >/dev/null 2>%s", - driver, outbin, src, errf); - int brc = runwait(cmd); - - if (r->kind == K_BUILDERR) { - int ok = (brc != 0) - && (r->experr == NULL || file_contains(errf, r->experr)); - if (!ok) - fprintf(stderr, "row[%s]: %s expected loud #26 builderr " - "(brc=%d)\n", r->label, driver, brc); - runwait(rmcmd); - return ok ? 0 : 1; - } - - 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) for the K_RUN rows. */ -static int -byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i) -{ - char src[96], cs_s[96], ws_s[96], cmd[1024]; - snprintf(src, sizeof src, "/tmp/ibm_bi_%d_%d.ww", getpid(), i); - snprintf(cs_s, sizeof cs_s, "/tmp/ibm_bi_%d_%d_cs.s", getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/ibm_bi_%d_%d_ww.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - int rc = 0; - 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", r->label); rc = 1; } - else { - 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", r->label); rc = 1; } - else if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER (#26)\n", - r->label); - rc = 1; - } - } - unlink(src); unlink(cs_s); unlink(ws_s); - return rc; -} - -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], w6c[640], w6c_ww[640]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - struct { const char *name; const char *path; 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].path, X_OK) != 0) { - fprintf(stderr, "intbinop_mismatch: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < n; i++) { - total++; - if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++; - } - } - - if (access(w6c_ww, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].kind != K_RUN) continue; - total++; - if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++; - } - } - - if (fail) { - fprintf(stderr, "intbinop_mismatch: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("intbinop_mismatch: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/953_f64crossmod_run.c b/test/wcc/953_f64crossmod_run.c deleted file mode 100644 index a1abf63b..00000000 --- a/test/wcc/953_f64crossmod_run.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 953_f64crossmod_run — runtime + byte-id regression net for the - * cross-module f64-return codegen bug, one root with two symptoms: - * #101 — `mod.g(): i32` where g returns f64 must CVTTSD2SI X0->AX - * (f64->int convert), NOT MOVSXD (integer sign-extend). - * #98 — `dbl(mod.g())` forwarding an imported f64 call-result as an - * f64 arg must MOVSD-spill it, NOT integer PUSHQ AX / POPQ DI. - * - * Root: wwstage exprfloatkind's N_CALL arm only resolved an N_IDENT - * callee's return type; a module-qualified N_DOT callee (`mod.g()`) - * never reached fnretlookup and fell through to integer kind 0. cgcast - * (#101) and pushargsrev (#98) then both took the integer path for an - * imported f64-returning fn. Fixed in cgenutil.ww by routing the N_DOT - * callee through fnretlookupmod (cstage cg_isfloat reads the resolved - * call result type directly — cmd/w6c/cgen.c:117,155 — and is correct - * for both cases). - * - * THIS TEST MUST CATCH A WWSTAGE-ONLY REGRESSION. cstage is correct - * before and after the fix, so a cstage-only probe (like - * 951_f64cgen_run) is gate-blind to a wwstage-only divergence. Each row - * therefore carries BOTH dimensions: - * (a) cstage `ww build` + run, asserting the exit code — pins that - * the asm both stages converge on is the runtime-correct one. - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. On master - * 6f8b658 (pre-fix) this diverges (#101 ~2 lines MOVSXD vs - * CVTTSD2SI, #98 ~6 lines PUSHQ/POPQ vs MOVSD-spill); post-fix it - * is byte-identical. - * - * Single-file multi-package form (like 728_match_4arm_cross_module): - * `package myf; ... package main; import myf; ...` in one source, so - * w6c/w6c_ww see the cross-module call without -I path plumbing. - */ -#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[] = { - /* #101 basic: g() returns -7.0, cast to i32 -> -7, exit u8 249. */ - { "x101_neg", - "package myf;\n" - "export fn g() f64 = { return -7.0; };\n" - "package main;\n" - "import myf;\n" - "export fn main() i32 = { return myf.g(): i32; };\n", 249 }, - /* #101 truncation toward zero, positive: 3.9 -> 3. */ - { "x101_trunc_pos", - "package myf;\n" - "export fn g() f64 = { return 3.9; };\n" - "package main;\n" - "import myf;\n" - "export fn main() i32 = { return myf.g(): i32; };\n", 3 }, - /* #101 truncation toward zero, negative: -3.9 -> -3, exit u8 253. */ - { "x101_trunc_neg", - "package myf;\n" - "export fn g() f64 = { return -3.9; };\n" - "package main;\n" - "import myf;\n" - "export fn main() i32 = { return myf.g(): i32; };\n", 253 }, - /* #98 nested f64-call-as-f64-arg: dbl(myf.g()) = -7.0*2 = -14, - * cast to i32 -> -14, exit u8 242. The imported call-result is - * forwarded as an f64 arg, exercising pushargsrev's float spill. */ - { "x98_nested_arg", - "package myf;\n" - "export fn g() f64 = { return -7.0; };\n" - "package main;\n" - "import myf;\n" - "fn dbl(x: f64) f64 = { return x * 2.0; };\n" - "export fn main() i32 = { return dbl(myf.g()): i32; };\n", 242 }, - /* #98 XMM-pressure: myf.g() forwarded as the first of three f64 - * args; pushargsrev must MOVSD-spill the imported call-result into - * X2 while two more f64 args are live — a distinct path from the - * basic single-arg X0 spill above. sum3(-7,10.5,4.5)=8.0 -> i32 8. */ - { "x98_xmm_pressure", - "package myf;\n" - "export fn g() f64 = { return -7.0; };\n" - "package main;\n" - "import myf;\n" - "fn sum3(a: f64, b: f64, c: f64) f64 = { return a + b + c; };\n" - "export fn main() i32 = { return sum3(myf.g(), 10.5, 4.5): i32; };\n", 8 }, - { 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, "f64crossmod: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - /* src, the build output, and both .s files all live under one - * tmpdir so the compiler's .sepwork scratch (derived from the - * source path) stays inside it; a single rm -rf at the end of - * both phases reclaims everything. */ - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64x_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char src[128], outbin[128], rmcmd[160]; - snprintf(src, sizeof src, "%s/wwf64x_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwf64x_%d_%d", - 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\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. - * FAILS on the pre-fix wwstage divergence. */ - char cs_s[160], ws_s[160]; - snprintf(cs_s, sizeof cs_s, "%s/wwf64x_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwf64x_%d_%d_ww.s", - tmpdir, getpid(), i); - - 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 f64 cross-module tests failed\n", - fail, n); - return 1; - } - printf("f64crossmod: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/tools/peellint b/tools/peellint deleted file mode 100755 index d7c575c6..00000000 --- a/tools/peellint +++ /dev/null @@ -1,195 +0,0 @@ -#!/bin/sh -# tools/peellint — gate against two raw single-resolve shapes that -# bypass the alias-chase accessors. -# -# RULE 1 (raw TY_NAMED single-peel reads) — the #5 alias-arc -# close-by-construction contract (rob F2/B7 rulings): one chased -# accessor is the only spelled way to dealias — type_chase_named -# (cmd/wcc/type.c) on the C side, tichase (selfhost/cmd/wcc/cgenutil.ww) -# on the ww side. A raw `->under` / `.under` read peels exactly one -# NAMED layer; chain-of-aliases stacks two, so every kind-gated consumer -# downstream of a single peel falls to a scalar shape (the four-times- -# burned family: #60/#61/#62/#70…). ZERO raw under-token reads may -# exist in scope outside the annotated whitelist. -# -# RULE 2 (bare primsize() name-keyed width) — the #101/#109 close-by- -# construction contract. primsize(name) is the ww-stage primitive-width -# table; it is ALIAS-BLIND — a narrow alias (`type my32 = u32`) returns -# 0, defaulting strides/widths to 8 (the #101 struct-fill miscompile and -# the #109 is-primitive GUARD family). aliasprimsize(c, name) is the -# SSoT chase (primsize else aliaslookup-chase, cgenutil.ww). A bare -# primsize() outside the chase machinery is the forbidden shape: the -# size/guard sites route through aliasprimsize so an alias resolves. -# This rule is ww-only — the C side dealiases via type_chase_named and -# has no primsize symbol. ZERO bare primsize() calls may exist in the -# ww stage outside the annotated whitelist. -# -# Both rules land in the same commit that deletes the last raw shape and -# keep the class unwritable. -# -# What rule 1 does NOT close (stated honestly, per the f2-ruling): a -# consumer that never spells `under` at all — a switch on t->kind that -# simply never peels — has no token to see here. That NO-PEEL class is -# closed only where classification routes through the internalized -# chasing helpers, and contained elsewhere by the acceptance-commit- -# carries-tripwires doctrine. Rule 2 has the symmetric edge: a size -# computed by a hardcoded literal instead of primsize is caught by -# sizelint (rule 13), not here. -# -# Matcher: a character scan strips block/line comments and string/char -# literals first (a regex pass mis-nests `/*` inside a string — review -# probe E6), then the tokens are matched accessor-spelling-wide: -# rule 1: `->under`/`.under` in C (deref-dot `(*t).under` is the same -# peel), `.under` in ww, with optional whitespace after the operator -# and a line-split continuation (`t->` at EOL, `under` next line). -# rule 2 (ww only): the bare `primsize` token, LEFT+RIGHT word-bounded -# so the superstring `aliasprimsize` is NOT a hit. Matching the -# standalone token (not just `primsize(`) closes the call form -# `primsize(nm)`, the paren-wrap `(primsize)(nm)`, the function-value -# bind `let p = primsize`, and every line-split — all of which -# compile and reintroduce the alias-blind width (review probes). -# Right/left token bounds keep prose like "io.underread" (check.ww) and -# "aliasprimsize" out. -# -# Exemption: a line containing `peel-ok` (or the equivalent landed -# spelling `peellint-ok`) exempts rule-1 violations on itself and the -# following 9 lines; a line containing `primsize-ok` exempts rule-2 -# violations over the same window. The windows are kept separate so a -# rule-1 annotation cannot blind a rule-2 bug and vice versa. Wide -# enough that one annotation atop a short construction/chase body covers -# it, narrow enough that a stray shape can't hide behind a distant -# annotation. Reasons stay WHY-only (rule 8): construction, chase body, -# recursive chase, resolve-state probe, structural-by-design sizer, or a -# cited task. -# -# Scope: cmd/wcc cmd/w6c selfhost/cmd/wcc lib/ww (skip *.combined.ww). -# lib/ww/typ.ww is in scope deliberately — it is type.c's ww mirror, -# the accessor/classifier layer itself (B7 ruling: excluding it leaves -# an unwatched file where the forbidden shape could be written). -# Exit code: 0 if clean, 1 with one diagnostic per violation. - -set -u - -ROOT=${ROOT:-$(cd "$(dirname "$0")/.." && pwd)} -cd "$ROOT" - -dirs= -for d in cmd/wcc cmd/w6c selfhost/cmd/wcc lib/ww; do - [ -d "$d" ] && dirs="$dirs $d" -done -[ -z "$dirs" ] && exit 0 - -files=$(find $dirs \ - \( -type f \( -name '*.c' -o -name '*.h' -o -name '*.ww' \) \ - ! -name '*.combined.ww' -print \) ) -[ -z "$files" ] && exit 0 - -exec awk -v sq="'" ' -BEGIN { nviol = 0 } - -FNR == 1 { - cur_file = FILENAME - is_c = (cur_file ~ /\.(c|h)$/) - und_exempt_until = 0 - prim_exempt_until = 0 - in_block = 0 - pending_und = 0 -} - -# Whitelist annotations: arm the exemption windows on the RAW line so an -# annotation inside a comment still counts. `peellint-ok` is the -# already-landed sibling spelling (check.ww construction) — accepted -# as-is, history is not re-spelled. The two windows are independent. -tolower($0) ~ /peel(lint)?-ok([^a-z0-9_]|$)/ { - if (FNR + 9 > und_exempt_until) und_exempt_until = FNR + 9 -} -tolower($0) ~ /primsize-ok([^a-z0-9_]|$)/ { - if (FNR + 9 > prim_exempt_until) prim_exempt_until = FNR + 9 -} - -# Strip comments and string/char literals by character scan: a comment -# opener inside a string is not a comment (E6), and literal text is -# never code. in_block carries across lines; strings/chars do not. -{ - code = "" - n = length($0) - i = 1 - in_str = 0; in_chr = 0 - while (i <= n) { - c = substr($0, i, 1) - c2 = substr($0, i, 2) - if (in_block) { - if (c2 == "*/") { in_block = 0; i += 2 } else i++ - continue - } - if (in_str) { - if (c == "\\") i += 2 - else { if (c == "\"") in_str = 0; i++ } - continue - } - if (in_chr) { - if (c == "\\") i += 2 - else { if (c == sq) in_chr = 0; i++ } - continue - } - if (c2 == "//") break - if (c2 == "/*") { in_block = 1; i += 2; continue } - if (c == "\"") { in_str = 1; i++; continue } - if (c == sq) { in_chr = 1; i++; continue } - code = code c - i++ - } -} - -{ - und_exempt = (FNR <= und_exempt_until) - prim_exempt = (FNR <= prim_exempt_until) - blank = (code ~ /^[ \t]*$/) - - # RULE 1 — raw under-token peel. Both C spellings peel: - # p->under and (*p).under / v.under. - if (!und_exempt) { - if (is_c) - ure = "(->|\\.)[ \t]*under([^A-Za-z0-9_]|$)" - else - ure = "\\.[ \t]*under([^A-Za-z0-9_]|$)" - uhit = (code ~ ure) - # Line-split continuation: operator at EOL, token opening the - # next code line. Comment-only lines keep the pend alive. - if (!uhit && pending_und && code ~ /^[ \t]*under([^A-Za-z0-9_]|$)/) - uhit = 1 - if (uhit) { - printf("%s:%d: raw under-token read outside the chase accessor; " \ - "route via type_chase_named (C) / tichase (ww), or annotate " \ - "peel-ok: \n", cur_file, FNR) - nviol++ - } - } - - # RULE 2 — bare primsize token (ww only). LEFT+RIGHT word bounds - # so the superstring aliasprimsize() is never a hit and a longer - # identifier with a primsize prefix is not matched. Matching the - # token (not `primsize(`) catches the paren-wrap `(primsize)(nm)`, - # the function-value bind `let p = primsize`, and every line-split — - # all compile and reintroduce the alias-blind width (review probes). - if (!is_c && !prim_exempt) { - if (code ~ /(^|[^A-Za-z0-9_])primsize([^A-Za-z0-9_]|$)/) { - printf("%s:%d: bare primsize outside the chase accessor; " \ - "route via aliasprimsize (ww), or annotate " \ - "primsize-ok: \n", cur_file, FNR) - nviol++ - } - } - - # Pending updates. An exempt line resets its pend (an annotated - # operator/name must not carry into a non-exempt next line); a blank - # or comment-only line keeps the pend alive; otherwise re-derive. - if (und_exempt) pending_und = 0 - else if (!blank) { - if (is_c) pending_und = (code ~ /(->|\.)[ \t]*$/) - else pending_und = (code ~ /\.[ \t]*$/) - } -} - -END { exit (nviol > 0 ? 1 : 0) } -' $files diff --git a/tools/sizelint b/tools/sizelint deleted file mode 100755 index 91126894..00000000 --- a/tools/sizelint +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/sh -# tools/sizelint — gate against hardcoded size literals in size-computation -# contexts. Per Drew's framing of Hare's discipline and ww task #64: -# every byte size that names a type's footprint must route through the -# type table (tinfo.size / Type.size / ty_*->size / size(T)). Bare -# numerics encode the layout twice and silently desync (#1, #43, #60, -# #65 sweeps caught one site at a time post-hoc). -# -# Patterns are matched in two tiers: -# 1) Always-on strong signals — direct writes to a type's size / -# slotsize / align field, the `prim(...,size,align)` factory call, -# and any literal in the rhs of `*->size = ` style assignments. -# 2) Context-gated literals — bare 16/24/32 (suffix-tagged or not) only -# inside files or functions whose name matches one of: -# size|slot|elem|field|stride|paramfield|tinfo|primtype|slotsize| -# letemit|tagged -# -# Exempt a single line with an end-of-line `// sizelint-ok: ` -# comment. Use sparingly and cite a task or structural reason. -# -# Scope: cmd/ selfhost/ lib/ (skip ref/ out/ bootstrap/ .combined.ww). -# Exit code: 0 if clean, 1 with one diagnostic per violation. - -set -u - -ROOT=${ROOT:-$(cd "$(dirname "$0")/.." && pwd)} -cd "$ROOT" - -files=$(find cmd selfhost lib \ - \( -type d -name out -prune \) -o \ - \( -type d -name bootstrap -prune \) -o \ - \( -type d -name ref -prune \) -o \ - \( -type d -name .git -prune \) -o \ - \( -type d -name .claude -prune \) -o \ - \( -type d -name .ai -prune \) -o \ - \( -type d -name '*.sepwork' -prune \) -o \ - \( -type f \( -name '*.c' -o -name '*.h' -o -name '*.ww' \) \ - ! -name '*.combined.ww' -print \) ) - -exec awk ' -BEGIN { - ctx_re = "size|slot|elem|field|stride|paramfield|tinfo|primtype|slotsize|letemit|tagged" - tagged_lit_re = "(16|24|32)(u64|i64)" - return_lit_re = "return[ \t]+(16|24|32)[ \t]*[;}]" - # Strong signal: assignment to .size / ->size / .slotsize / ->slotsize. - # Value 8 is the natural pointer/scalar size — leave un-flagged so - # typeptr/typechan stay quiet without an allow-list per assignment. - size_assign_re = "(\\.|->)[ \t]*(size|slotsize)[ \t]*=[ \t]*(16|24|32)([^0-9]|$)" - # Strong signal: type-table factory `prim(arena, kind, "name", SIZE, ALIGN)`. - prim_call_re = "\\]+\"[ \t]*,[ \t]*(16|24|32)" - nviol = 0 -} - -FNR == 1 { - cur_file = FILENAME - file_in_ctx = (tolower(cur_file) ~ ctx_re) - is_c = (cur_file ~ /\.(c|h)$/) - fn_name = "" - fn_in_ctx = 0 -} - -# Track function context (ww + C styles). -{ - if (match($0, /^[ \t]*(export[ \t]+)?fn[ \t]+[A-Za-z_][A-Za-z0-9_]*/)) { - s = substr($0, RSTART, RLENGTH) - sub(/^[ \t]*(export[ \t]+)?fn[ \t]+/, "", s) - fn_name = s - fn_in_ctx = (tolower(fn_name) ~ ctx_re) - } else if (is_c && match($0, /^[A-Za-z_][A-Za-z0-9_]*\(/)) { - s = substr($0, RSTART, RLENGTH - 1) - fn_name = s - fn_in_ctx = (tolower(fn_name) ~ ctx_re) - } -} - -# Allow-list (case-insensitive on the keyword) — check the raw line so a -# doc comment can still exempt itself. Accepts both `// sizelint-ok:` (ww -# and C99) and `/* sizelint-ok: ... */` (Plan 9 C style). -tolower($0) ~ /sizelint-ok:/ { next } - -# Strip line and block comments before pattern matching — a comment that -# quotes a hardcoded size in prose is not a violation. Single-line block -# comments only; multi-line `/* ... */` spans rarely contain assignments -# we care about. -{ - code = $0 - sub(/\/\/.*$/, "", code) - gsub(/\/\*[^*]*\*+([^/*][^*]*\*+)*\//, "", code) -} - -# Always-on strong-signal patterns. -{ - if (match(code, size_assign_re)) { - report(substr(code, RSTART, RLENGTH), - "assignment to .size/.slotsize with literal; route via type SSoT (tinfo.size / ty_*->size)") - } - if (match(code, prim_call_re)) { - report(substr(code, RSTART, RLENGTH), - "literal in prim(...) size slot; type-table is SSoT (cmd/wcc/type.c, lib/ww/typ.ww)") - } -} - -# Context-gated patterns. -file_in_ctx || fn_in_ctx { - line = code - while (match(line, tagged_lit_re)) { - report(substr(line, RSTART, RLENGTH), - "size literal in size-context; route via size(T) / primtypesize / tyslicesize SSoT") - line = substr(line, RSTART + RLENGTH) - } - if (match(code, return_lit_re)) { - report(substr(code, RSTART, RLENGTH), - "return of bare size literal in size-context; route via type SSoT") - } -} - -function report(snip, msg) { - # Trim trailing newline / extra whitespace from snippet. - gsub(/[ \t]+$/, "", snip) - printf("%s:%d: %s; suggest: %s\n", cur_file, FNR, snip, msg) - nviol++ -} - -END { exit (nviol > 0 ? 1 : 0) } -' $files