Repository navigation
cgen: let the C preprocessor pick <intrin.h>/<dbghelp.h> in Windows snapshots (makev.bat cannot bootstrap with the bundled tcc) - #29147
Conversation
…napshots
`vc/v_win.c` is generated with `-cross -os windows -cc msvc` (gen_vc_ci.yml)
and then built by makev.bat with the bundled TinyCC, clang or gcc - not with
MSVC. Choosing those two headers at generation time, from `g.ccompiler`, baked
`#include <intrin.h>` and `#include <dbghelp.h>` into every Windows snapshot as
plain text, and the bundled tcc ships neither, so the bootstrap died on
./vc/v_win.c:1169: error: include file 'intrin.h' not found
before compiling a line of V. Only MSVC has both headers, and every use site is
already `#if defined(_MSC_VER)` (bits__mul_64, bits__trailing_zeros_32/64) or
`$if msvc` (print_backtrace_skipping_top_frames_msvc), so a preprocessor guard is
both correct and sufficient. This is the same shape the neighbouring __linux__ /
__APPLE__ blocks and the stdatomic block already use, and the same reasoning
atomic_builtin_compat_decls() records: "Generated Windows C can be compiled by
TCC even when the selected compiler recorded in `g.ccompiler` is different
(including portable snapshots). The preprocessor must make the final choice."
The C-compiler-agnostic job only skips `makev.bat`, so nothing caught this. The
two new `system_libc_headers` tests and the two new cross-output tests all fail
on the old code and pass on the new.
Fixes vlang#29146.
This is the V-side root cause fix; vlang/tccbin#96 (amd64) and vlang#98 (i386) add
the two headers to the bundled tcc instead. They are complementary, not
alternatives: vc is bot-generated, so the snapshot published today still has the
unconditional include and tccbin#96 is what unblocks Windows users until vc
regenerates from this. Once it does, tcc no longer needs the shims for V's
output, though they still help any other consumer and any other unguarded
MSVC-only include.
🧙 Built with [WOZCODE](https://wozcode.com)
medvednikov
left a comment
There was a problem hiding this comment.
Reviewed 30e70b9200784aad1edc4df06f8884c4b67ca57f, excluding CI status.
I did not find a blocking correctness issue in the production change. The _MSC_VER guard remains nested inside _WIN32, and moving this decision to the consuming C preprocessor is consistent with the compiler-specific intrinsic/backtrace use sites.
Non-blocking: strengthen the end-to-end guard assertion (vlib/v/compiler_tests/cross_output_codegen_test.v, test_cross_windows_output_guards_the_msvc_only_headers and enclosing_guards_at).
c_code.index(header) checks only the first occurrence. Consequently, output shaped like this still satisfies the first-occurrence guard assertion, the pinned-block assertion, and the include-membership comparison, despite including both headers without an MSVC guard later:
#ifdef _WIN32
#if defined(_MSC_VER)
#include <intrin.h>
#include <dbghelp.h>
#endif
#include <intrin.h>
#include <dbghelp.h>
#endifThis matters for the stated goal of catching headers added elsewhere in the generated output: a later unguarded duplicate would reintroduce the missing-header failure without these checks detecting it. Also, guards.any(it.contains('_MSC_VER')) accepts #ifndef _MSC_VER and the #else of a positive MSVC condition, while the helper does not account for #elif.
Consider checking every occurrence and requiring an effective positive MSVC-only condition, with negative fixtures for these cases; alternatively, use a focused preprocessing test with _MSC_VER both defined and undefined.
Validation: the isolated emitted guard passed eight GCC/Clang preprocessing cases covering all defined/undefined combinations of _WIN32 and _MSC_VER, using stub headers. I also confirmed the false positives above using a direct Python translation of the new assertions and GCC preprocessing. These were isolated checks, not execution of the V test suites or a native Windows bootstrap; those were not run in this environment.
|
@codex review |
medvednikov
left a comment
There was a problem hiding this comment.
Re-reviewed 990f07e1bea6a596871e65c353bf10397e41e1ad, excluding CI status.
The regression-test gap from my previous review is addressed. msvc_only_header_guard_error() now iterates over every matching include instead of checking only the first occurrence, and requires an explicit positive MSVC branch rather than merely finding _MSC_VER in a condition. enclosing_guards_at() replaces the active branch on #elif and no longer accepts a subsequent #else as positive. The new fixtures cover later unguarded duplicates, negative conditions, #else/#elif transitions, permissive OR conditions, closed guards, and missing headers. The strengthened check also runs for each of the four C-compiler variants in the header-comparison test.
I found no new blocking correctness issues in this follow-up. The production include-guard change is unchanged, and I consider my earlier non-blocking finding resolved.
Validation: A direct Python translation of the updated helpers rejected all 12 negative fixtures and accepted all 4 positive fixtures. Independent GCC/Clang preprocessing with stub headers confirmed the fixture behavior with _MSC_VER defined and undefined, plus all _WIN32/_MSC_VER combinations for the production include block: 72 isolated preprocessing checks passed in total. These checks did not execute the V helpers or test suites. The V test suites and a native Windows bootstrap were not run in this environment.
|
Thanks! |
The squash of v#28973 holds what the head of the pull request held (436aebb), which the experiment already has and extends: the 11 files it conflicts in keep the experiment's version. Git applied two hunks of the pull request outside the conflicts (the answers to the questions of VLS in driver.v, the exported and markused declarations of a library in checker.v) at their old place, next to the experiment's own copies, and they were dropped with the rest; every other line of the pull request that the experiment lacks is one that it changed later. From master: the Windows snapshots' headers in cleanc.v (vlang#29147) and ROADMAP.md.
Problem
vc/v_win.ccannot be built by the bundled TinyCC, somakev.batfails on Windows:vc/v_win.cis generated by.github/workflows/gen_vc_ci.ymlwith-cross -os windows -cc msvc, and is then built bymakev.batwith tcc, clang or gcc — never MSVC. Butsystem_libc_headers()decided at generation time which headers to emit:so the two includes got baked into the snapshot as plain text. The bundled tcc ships neither header, and the bootstrap died before compiling a line of V.
Fix
Only MSVC has these two headers, and every use site is already conditionally compiled:
_umul128inbits__mul_64#if defined(_MSC_VER)_BitScanForwardinbits__trailing_zeros_32#if defined(_MSC_VER)_BitScanForward64inbits__trailing_zeros_64#if defined(_MSC_VER)SymInitialize/SymFromAddrinprint_backtrace_skipping_top_frames_msvc$if msvcSo the choice belongs to the C preprocessor, not to the compiler V happened to be targeting.
#if defined(_MSC_VER)is also the shape the neighbouring__linux__/__APPLE__blocks and thestdatomicblock already use, and it is the reasoningatomic_builtin_compat_decls()already records a few hundred lines below:The MSVC path is unchanged:
cl.exedefines_MSC_VER, so it still gets both headers, andmakev.bat:503already linksdbghelp.libfor that bootstrap.Why nothing caught it
The C-compiler-agnostic CI jobs never run
makev.bat, andCI Windows TCConly reaches this line oncevchas been regenerated. This is the same class as #28615 (winsock2.h), #28631 (bcrypt.h) and #28650 (aclapi.h): a generated-C portability gap that is invisible until the snapshot is compiled by a second C compiler.Tests
Two unit tests in
vlib/v/gen/c/preamble_test.vand two end-to-end tests invlib/v/compiler_tests/cross_output_codegen_test.v. All four fail on the old code and pass on the new:test_system_libc_headers_do_not_depend_on_the_c_compiler_v_was_generated_forandtest_cross_windows_output_includes_the_same_headers_for_every_c_compilerare the general form of the regression: a snapshot's header set must not depend on the-ccthat generated it, since the C compiler that compiles it is chosen at build time. The e2e version checks the real-cross -os windowsoutput, so it also covers anything the backend adds on top of the preamble.Verified locally:
v test vlib/v/gen/c/preamble_test.v— OKv test vlib/v/compiler_tests/cross_output_codegen_test.v— OKv test vlib/v/gen/c/— 37 passed, 1 pre-existing failure (import_cache_reuse_test.v, fails identically on unpatched master)v fmt -verifyon all three files — cleanvc/v_win.cand ranmakev.bat's exact command line-cc msvcthe header set is now byte-identical to-cc gcc/-cc clang/-cc tcc;<intrin.h>/<dbghelp.h>appear as#ifdef _WIN32 && #if defined(_MSC_VER), and every header the bundled tcc lacks is otherwise behind a guard that is false for it.Not tested: a full
-cc msvcbuild ofcmd/von Windows (no MSVC here). MSVC only ever includes the two real headers, so that path is unchanged.vcis bot-generated — this needs agen_vcrun beforemakev.batpicks it up.Relationship to vlang/tccbin#96 / #98
Those add
intrin.h(an empty shim) anddbghelp.hto the bundled tcc, which makes the current snapshot buildable. That is the right fix for the bundle, and it is complementary rather than alternative:vcis bot-generated, so the published snapshot still carries the unconditional include — tccbin#96 is what unblocks Windows users today, untilvcregenerates from this.intrin.hshim is deliberately an empty no-op, so a future unguarded MSVC-only include would then be satisfied by a header that declares nothing.Fixes #29146.
🧙 Built with WOZCODE