port: add MSVC600 CPython compatibility layer

This commit is contained in:
RayPals
2026-08-17 17:35:32 +00:00
parent 3f15bfa5f7
commit a7a9560e5c
13 changed files with 2652 additions and 35 deletions
+869 -23
View File
@@ -5,6 +5,36 @@ Baseline: CPython `v3.11.16`
Compiler: MSVC 6.0 (`cl.exe` 12.00.8804), from external `MSVC600`
Host test environment: Linux + Wine
## Files changed in this revision (`pylifecycle.c` VC6 PyRuntime section slice)
All still uncommitted; nothing in this list has been pushed anywhere.
- `cpython/Python/pylifecycle.c` — narrow VC6-only compatibility edit for
the first `Python/pylifecycle.c` blocker: keep the modern Windows
`#pragma section("PyRuntime", read, write)` +
`__declspec(allocate("PyRuntime"))` path unchanged for non-VC6 compilers,
but use VC6's supported `#pragma data_seg("PyRuntime")` /
`#pragma data_seg()` spelling around the initialized `_PyRuntime` object
when `_MSC_VER < 1300`. The `_PyRuntimeState_INIT` initializer remains
exactly at the definition site.
- `compat/msvc600/cpython-3.11.16-vc6-headers.patch` — regenerated in full
from `git -C cpython diff`; now 767 lines and includes the
`Python/pylifecycle.c` hunk alongside previous uncommitted compatibility
edits.
- `PORT_STATUS.md` — this revision.
**Command results (all reproduced in this revision):**
| Command | Result |
|---|---|
| `bash scripts/vc6-probe.test.sh` | exit 0, `ALL TESTS PASSED` — 6/6 checks `ok` |
| `bash scripts/vc6-probe-core.test.sh` | exit 0, `ALL TESTS PASSED` — 27/27 checks `ok`; includes `Modules/main.c` compile-only clean check |
| `bash scripts/vc6-probe-core.sh cpython/Python/pylifecycle.c` | exit 2 — gets past the prior VC6-incompatible `PyRuntime` section pragma/declaration pair; no `unknown pragma` warning and no `C2341` segment error remain. New first blocker is `cpython\Python\pylifecycle.c(112) : error C2059: syntax error : '.'` in `_PyRuntimeState_INIT`, followed by more C99 compound-literal/designated-initializer and C89 mixed-declaration errors; the run later hits `fatal error C1003` at line 1232. |
**Caveat:** all probe results here are compile-only (`/c`) results. No link,
no `pythoncore`/`python.exe` build, no Wine launch, and no Windows 98 runtime
execution was attempted or claimed.
## Verified
- CPython source checkout is at the requested `v3.11.16` baseline.
@@ -19,33 +49,778 @@ Host test environment: Linux + Wine
- A direct compile probe reaches CPython 3.11 headers. The first unmodified
probe fails before linking, proving the compiler is being invoked against the
intended baseline.
- A project-local compatibility `inttypes.h` has been added under
`compat/msvc600/` as the first isolated shim. It is not yet wired into
CPython source or declared sufficient for the port.
- Project-local compatibility headers have been added under
`compat/msvc600/`: `inttypes.h` (first isolated shim) and, as of this
revision, `stdbool.h` (see "stdbool slice" below). Neither is declared
sufficient for the port on its own.
- **`scripts/vc6-probe.sh` was fixed and is now truthful.** The script piped
`cl.exe` output through `grep`/`sed` to drop Wine's `:err:` debug noise and
to shorten absolute host paths to repo-relative ones. The path-shortening
step was silently broken: it built a `sed` pattern directly from a
Windows-style path (e.g. `Z:\home\ubuntu\...\`), and both `sed` BRE and bash
glob patterns treat a bare `\` in a *pattern* as an escape character, so
`\h`, `\u`, `\p`, etc. were consumed as (mostly no-op) escapes instead of
literal characters and the substitution never matched anything — diagnostics
always showed full absolute Wine paths instead of the intended
repo-relative ones. Fixed by matching the prefix as a literal string via a
quoted bash `${line//"$prefix"/}` expansion (quoting forces literal, not
glob, matching) instead of an unescaped regex. The pipeline's exit-status
capture via `PIPESTATUS[0]` was already correct (verified with a
deliberately broken translation unit: `cl.exe`'s real nonzero exit code
propagated correctly both before and after this fix); it is now covered by
a regression test so it cannot regress silently.
A new regression test, `scripts/vc6-probe.test.sh`, exercises both the
path-stripping helper directly and — when wine + MSVC600 are available, as
they are in this environment — the real script against a deliberately
invalid `.c` file, asserting a nonzero exit code, a surfaced `error C`
diagnostic, and no leaked absolute Wine path. Run: `bash
scripts/vc6-probe.test.sh`. Last run: all 6 checks `ok`.
- **`Include/internal/pycore_atomic.h`'s VC6/`intrin.h` blocker is now
resolved for 32-bit x86 (`_M_IX86`) and verified; see "Atomics slice"
below for full detail and honesty notes on what is and is not covered.
- **`Include/internal/pycore_interp.h`'s VC6/`<stdbool.h>` blocker is now
resolved and verified; see "stdbool slice" below for full detail.
- **The VC6 C89-vs-C99 mixed-declarations blocker in four
`Include/internal/pycore_*.h` headers (`pycore_code.h`, `pycore_dict.h`,
`pycore_list.h`, `pycore_call.h`) reachable from `Modules/main.c` is now
resolved and verified; see "Mixed-declarations header slice" below for
full detail.
- **The same C89-vs-C99 mixed-declarations pattern, pervasive within
`Modules/main.c`'s own body (29 error sites across roughly a dozen
functions), is now also resolved and verified; see "Modules/main.c
declaration-order slice" below for full detail.** With that fix applied,
`Modules/main.c` hits a new, unrelated, and out-of-scope-for-that-slice
blocker: several C99 compound-literal/designated-initializer macros
(`_PyStatus_OK()`, `_PyCompilerFlags_INIT`, and the `_PyArgv`-style struct
literals in `Py_Main()`/`Py_BytesMain()`) that VC6 does not support at
all, independent of declaration position.
- **That compound-literal/designated-initializer blocker is now also
resolved and verified for all 8 sites in `Modules/main.c`; see
"Compound-literal / designated-initializer slice" below for full
detail.** With that fix applied, `cpython/Modules/main.c` compiles with
`cl.exe` exit status 0 under `scripts/vc6-probe-core.sh` — no errors, no
warnings. **This is a compile-only (`/c`) result for one translation
unit; it is not a link, not a `pythoncore`/`python.exe` build, and not a
runtime claim** (`vc6-probe-core.sh` never attempts to link). Probing
the next core translation unit in `Modules/main.c`'s own call chain,
`Python/pylifecycle.c`, hits the same two already-solved blocker classes
again (recurring, not novel) plus enough additional errors to exceed
the compiler's 100-error cap before the whole file is even seen; see
"Next blocker: `Python/pylifecycle.c`" below. No link or runtime success
is claimed anywhere in this document.
## `pycore_object.h` VC6 `__func__` / GC helper declaration-order slice
**Scope: one internal header only, as requested.** The real
`Python/pylifecycle.c` core probe previously stopped first in
`Include/internal/pycore_object.h`: VC6 reported `__func__` undeclared at
the `_PyObject_ASSERT_FROM(..., __func__)` calls in `_PyObject_GC_TRACK()`,
then cascaded into C89 mixed-declaration errors for the local `PyGC_Head *`
and `PyInterpreterState *` declarations in `_PyObject_GC_TRACK()` and
`_PyObject_GC_UNTRACK()`.
The header now has a VC6-only fallback:
```c
#if defined(_MSC_VER) && _MSC_VER < 1300 && !defined(__func__)
# define __func__ "<unknown>"
#endif
```
A quick VC6 smoke probe showed `__FUNCTION__` is also unavailable in VC6, so
there was no exact function-name extension to preserve. This mapping only
degrades debug/fatal diagnostic function-name text on VC6; C99 compilers and
newer MSVC keep their normal behavior. The only declaration-order edits are
the mechanical C89 transform in `_PyObject_GC_TRACK()` and
`_PyObject_GC_UNTRACK()`: declarations at block start, assignments left at
the original evaluation points.
Verification: `bash scripts/vc6-probe-core.sh cpython/Python/pylifecycle.c`
now gets past `pycore_object.h`; the new first blocker is the Windows
section-allocation pragma pair in `Python/pylifecycle.c` (see "Next
blocker" below).
## Atomics slice (`pycore_atomic.h`, VC6 / `_M_IX86`)
**Scope: 32-bit x86 only, matching this port's only target (Windows 9x has
no x64 edition).** `Include/internal/pycore_atomic.h` previously included
`<intrin.h>` and used `_Interlocked*` compiler intrinsics unconditionally
for any `_MSC_VER`-defined compiler; VC6 (`_MSC_VER` 1200) has neither. The
now-applied, still-uncommitted `compat/msvc600/cpython-3.11.16-vc6-headers.patch`
adds two things to this header, both gated strictly to
`defined(_MSC_VER) && _MSC_VER < 1300 && defined(_M_IX86)` (a new
`_Py_ATOMIC_VC6_X86` macro) so every other compiler/version — including
modern MSVC on `_M_X64`/ARM and the GCC/clang/C11 paths — keeps its exact
prior behavior, byte-for-byte (verified: `git diff` on the non-VC6 branches
shows only their line numbers moving under the new `#elif`, no content
changes):
1. The top-of-file `<intrin.h>`/`<immintrin.h>` include is skipped for
VC6/`_M_IX86`; `<windows.h>` is included instead, to declare
`InterlockedExchange`/`InterlockedCompareExchange`.
2. A new `#if defined(_Py_ATOMIC_VC6_X86)` branch (ahead of the existing
`_M_IX86 || _M_X64` branch, now `#elif`) implements
`_Py_atomic_store_explicit`/`_Py_atomic_load_explicit` for
`_Py_atomic_int`/`_Py_atomic_address` using the Win32
`InterlockedExchange`/`InterlockedCompareExchange` API — present in
`KERNEL32.DLL` since Windows 98 (confirmed against this project's actual
MSVC600 VC98 SDK headers, `MSVC600/VC98/Include/WINBASE.H`, which declare
both for `_M_IX86` outside the MRx000/Alpha-only intrinsic path). Both
give a full memory barrier on x86, same as the intrinsics they replace,
so — as the original comment already notes for the HLE-unavailable case
— every `_Py_memory_order` maps to the same fully-fenced operation; VC6
has no HLE (`_HLEAcquire`/`_HLERelease`, VS2013+) intrinsics or hardware
support to preserve here regardless.
**64-bit atomics are explicitly NOT implemented, and this is not a gap in
practice for this port.** `_Py_atomic_address` (`uintptr_t`) and
`_Py_atomic_int` (`int`) are both 4 bytes on 32-bit x86, so the existing
`sizeof(...) == 8` branches that select the 64-bit store/load paths in the
pre-existing `_M_IX86 || _M_X64` code are already unreachable dead code on
`_M_IX86` upstream, before this patch — VC6 never defines `_M_X64`. The VC6
branch added here therefore has no 64-bit path to implement, and a
`#error` guards the (unreachable-under-VC6) case where `_M_X64` were ever
also defined, so a future misconfiguration fails loudly at compile time
instead of silently truncating a 64-bit atomic. No `CMPXCHG8B` inline
assembly was written, and none is required for this header; if some other
part of the port needs genuine 64-bit interlocked ops on VC6 later, that is
a separate, not-yet-encountered problem, not something papered over here.
**Verified with two independent probes**, both under
`scripts/vc6-probe-core.sh` (new; extends `scripts/vc6-probe.sh`'s approach
to `Py_BUILD_CORE` translation units — `/D Py_BUILD_CORE` plus
`Include/internal` on the include path):
- `scripts/fixtures/vc6_atomic_smoke.c` (new) — a minimal, isolated
translation unit that includes only `pycore_atomic.h` (via `<inttypes.h>`
for `uintptr_t`, which real callers get from `Python.h`/`pyport.h`) and
exercises `_Py_atomic_store`/`_Py_atomic_load` and their `_relaxed`
variants on both `_Py_atomic_int` and `_Py_atomic_address`:
```text
$ bash scripts/vc6-probe-core.sh scripts/fixtures/vc6_atomic_smoke.c
== MSVC600 Py_BUILD_CORE compile probe ==
source : scripts/fixtures/vc6_atomic_smoke.c
...
vc6_atomic_smoke.c
== cl.exe exit status: 0 ==
```
No errors, no warnings.
- `cpython/Modules/main.c`, the same real core translation unit identified
as the next blocker previously. It now compiles *past*
`pycore_atomic.h` (reached transitively via `pycore_runtime.h` before
`pycore_interp.h`) and hits a new, different, and unrelated blocker:
```text
$ bash scripts/vc6-probe-core.sh
== MSVC600 Py_BUILD_CORE compile probe ==
source : cpython/Modules/main.c
...
cpython\Include\internal\pycore_interp.h(11) : fatal error C1083:
Cannot open include file: 'stdbool.h': No such file or directory
== cl.exe exit status: 2 ==
```
`Include/internal/pycore_interp.h` line 11 does `#include <stdbool.h>`
before its own `#include "pycore_atomic.h"` on line 13 — a C99 standard
header VC6 does not ship, unrelated to atomics. This is the next blocker
for this port and is left unaddressed here, out of scope for this slice.
Both probes are covered by a new regression test,
`scripts/vc6-probe-core.test.sh`, which additionally asserts (so this
result cannot regress silently) that a deliberately broken translation
unit still fails with a surfaced `error C` diagnostic, and that
`Modules/main.c`'s current failure is specifically *not* at
`pycore_atomic.h`/`intrin.h` (i.e. that blocker has not resurfaced). Run:
`bash scripts/vc6-probe-core.test.sh`. At the time this atomics slice was
written, this was all 6 checks `ok`; the stdbool slice below added 5 more
checks to the same file (now 11; see that section for the current count
and last-run result). `scripts/vc6-probe.test.sh` was re-run after the
atomics change and still passed (all 6 checks `ok`), confirming the
public-header probe was unaffected.
## stdbool slice (`pycore_interp.h`, VC6 / C99 `<stdbool.h>`)
**`Include/internal/pycore_interp.h` line 11 does `#include <stdbool.h>`**
before its own `#include "pycore_atomic.h"` on line 13 — a C99 standard
header VC6 (`_MSC_VER` 1200, which predates C99 entirely) does not ship.
In VC6's C mode, `bool`/`true`/`false` are not keywords at all (they are
C++-only there), and there is no standard-library `<stdbool.h>` to fall
back on.
The now-added, still-uncommitted `compat/msvc600/stdbool.h` is a
project-local fallback for this single header, **not a patch to CPython
source.** It is picked up purely through include-path ordering: both
`scripts/vc6-probe.sh` and `scripts/vc6-probe-core.sh` already put
`compat/msvc600` first on the VC6 `/I` include path (needed earlier for
`compat/msvc600/inttypes.h`), so VC6's `#include <stdbool.h>` resolves to
this project-local header before it would ever reach a (nonexistent)
system one. Nothing under `cpython/` changes for this slice, so
`compat/msvc600/cpython-3.11.16-vc6-headers.patch` is unchanged. Every
other compiler is unaffected for the same reason a browser's `/I` search
order doesn't affect other browsers: `compat/msvc600` is never added to
any other compiler's include path, so a conforming C99/C11 compiler still
gets its own real `<stdbool.h>` (or built-in `_Bool`/`bool`), untouched.
As defence in depth (not because it is expected to be reachable), the
header itself also `#error`s out if included under anything but
`_MSC_VER < 1300`.
The shim defines the same four names as the standard C99 header —
`bool` (as `int`, since VC6 has no `_Bool` built-in type to alias),
`true`, `false`, and `__bool_true_false_are_defined` — as plain
`#define` macros rather than a typedef, matching the standard's
requirement that a conforming program be able to `#undef`/redefine
`bool`/`true`/`false`.
**Verified with the same two-probe approach used for the atomics slice:**
- `scripts/fixtures/vc6_stdbool_smoke.c` (new) — a minimal, isolated
translation unit that includes only `<stdbool.h>` and exercises
`bool`/`true`/`false`/`__bool_true_false_are_defined`:
```text
$ bash scripts/vc6-probe-core.sh scripts/fixtures/vc6_stdbool_smoke.c
== MSVC600 Py_BUILD_CORE compile probe ==
source : scripts/fixtures/vc6_stdbool_smoke.c
...
vc6_stdbool_smoke.c
== cl.exe exit status: 0 ==
```
No errors, no warnings.
- `cpython/Modules/main.c`, the same real core translation unit used to
verify the atomics slice. It now compiles *past* `pycore_interp.h`
line 11 (reached the same way as before, transitively via
`pycore_runtime.h`) — the fatal `stdbool.h`/C1083 error is gone — and
proceeds through `pycore_atomic.h` and `pycore_ast_state.h` before
hitting a new, unrelated, and considerably larger blocker; see "Current
first-blocker evidence" below for the full detail.
Both probes are covered by 5 new checks added to the existing
`scripts/vc6-probe-core.test.sh` (positive isolated-fixture compile,
no-errors check, a negative deliberately-broken-fixture check, its
error-surfaced check, and a check that `Modules/main.c`'s output no
longer contains `Cannot open include file: 'stdbool.h'`), bringing that
file to 11 checks total. Run: `bash scripts/vc6-probe-core.test.sh`. Last
run: all 11 checks `ok` (wine + MSVC600 available in this environment).
`scripts/vc6-probe.test.sh` was re-run after this change and still passes
(all 6 checks `ok`), confirming the public-header probe is unaffected.
## Mixed-declarations header slice (`pycore_code.h`, `pycore_dict.h`, `pycore_list.h`, `pycore_call.h`)
**Scope: the four `Include/internal/pycore_*.h` headers that the
`Modules/main.c` `#include` chain directly reaches with VC6 C89
mixed-declaration violations, and no others.** This is a bounded slice of
the "Compiler language gap" blocker recorded below, not an attempt at the
whole gap: it fixes exactly the sites the real core probe (`Modules/main.c`
via `scripts/vc6-probe-core.sh`) hits inside CPython's own headers, and
stops there. `Modules/main.c` itself has the same pattern pervasively
throughout its own function bodies; that is **explicitly out of scope for
this slice** and is now the next recorded blocker (see "Current
first-blocker evidence" below) rather than something papered over here.
Six `static inline` functions across the four headers declared a variable
after a preceding statement (typically an `assert(...)`), which C89 (VC6's
only supported dialect, give or take Microsoft extensions) does not allow
inside a block — only C99 does. VC6's parser, on hitting the declaration
where it expects a statement, fails to recognize it as a declaration at
all and instead cascades into unrelated-looking `C2065`/`C2109`/`C2106`
errors for the rest of the block, exactly as described for the first
instance (`pycore_code.h:154`) in earlier revisions of this document.
The now-applied, still-uncommitted
`compat/msvc600/cpython-3.11.16-vc6-headers.patch` fixes each site the
same way: hoist the declaration (no initializer) to the top of its
enclosing block, and turn the original `TYPE name = expr;` into a plain
assignment (`name = expr;`) at its original position. This changes no
evaluation order and no semantics — it is the standard, mechanical C89
transform for this exact pattern — and is applied only to the six
functions that actually needed it, not the surrounding file:
- `Include/internal/pycore_code.h`: `_PyLocals_GetKind()`,
`_PyLocals_SetKind()` (both hoist a `char *ptr`), and
`adaptive_counter_backoff()` (hoists `unsigned int value`).
- `Include/internal/pycore_dict.h`: `_PyDictValues_AddToInsertionOrder()`
(hoists `uint8_t *size_ptr` and `int size`).
- `Include/internal/pycore_list.h`: `_PyList_AppendTakeRef()` (hoists
`Py_ssize_t len` and `Py_ssize_t allocated`).
- `Include/internal/pycore_call.h`: `_PyVectorcall_FunctionInline()`
(hoists `PyTypeObject *tp`, `Py_ssize_t offset`, and
`vectorcallfunc ptr`).
Every other compiler is unaffected: these are plain C source edits (no
`#ifdef`/`_MSC_VER` gating, unlike the atomics slice), and hoisting a
declaration without an initializer, then assigning immediately after the
same statements that used to compute the initializer, is behavior-identical
under C89, C99, C11, and C++ alike — no code path, value, or evaluation
order changes for any compiler already accepting the original source.
**Verified with the same two-probe approach used for the atomics and
stdbool slices:**
- `scripts/fixtures/vc6_mixed_decls_smoke.c` (new) — includes the real
`Python.h` (these four headers assume public types like `PyObject`,
`PyCodeObject`, `PyListObject`, `PyDictValues`, and `vectorcallfunc`
already declared, the same way real CPython `.c` sources reach them, so
full isolation without `Python.h` is not meaningful here unlike the
atomics/stdbool fixtures) plus all four headers, and calls all six fixed
functions:
```text
$ bash scripts/vc6-probe-core.sh scripts/fixtures/vc6_mixed_decls_smoke.c
== MSVC600 Py_BUILD_CORE compile probe ==
source : scripts/fixtures/vc6_mixed_decls_smoke.c
...
vc6_mixed_decls_smoke.c
...
== cl.exe exit status: 0 ==
```
No errors, no warnings.
- `cpython/Modules/main.c`, the same real core translation unit used for
the earlier slices. All four headers now compile clean — no
`pycore_code.h`/`pycore_dict.h`/`pycore_list.h`/`pycore_call.h` error
appears anywhere in the probe output — and the compiler proceeds
entirely into `Modules/main.c`'s own body, where a new, considerably
larger, and out-of-scope-for-this-slice blocker is hit; see "Current
first-blocker evidence" below.
Both probes are covered by 5 new checks added to
`scripts/vc6-probe-core.test.sh` (positive isolated-fixture compile,
no-errors check, a negative deliberately-broken-fixture check, its
error-surfaced check, and a check that `Modules/main.c`'s output no longer
contains any `pycore_code.h`/`pycore_dict.h`/`pycore_list.h`/
`pycore_call.h` error line), bringing that file to 16 checks total. Run:
`bash scripts/vc6-probe-core.test.sh`. Last run: all 16 checks `ok` (wine +
MSVC600 available in this environment). `scripts/vc6-probe.test.sh` was
re-run after this change and still passes (all 6 checks `ok`), confirming
the public-header probe is unaffected. The patch's reproducibility was
also re-verified directly: `git apply --check --directory=cpython
compat/msvc600/cpython-3.11.16-vc6-headers.patch` against a freshly
stashed (pristine `v3.11.16`) `cpython/` working tree exits 0.
## Modules/main.c declaration-order slice
**Scope: the C89-vs-C99 mixed-declarations pattern inside
`Modules/main.c`'s own function bodies only — declaration *position*, not
any other C99 feature. No other file was touched, and no whole-tree
mechanical rewrite was attempted.** This is the follow-on to the
"Mixed-declarations header slice" above: the same pattern (a block-scope
declaration appearing after a preceding statement, which C89's parser
cannot recognize as a declaration), but recurring roughly a dozen times
across `Modules/main.c`'s own ~740 lines rather than in a handful of small
header functions.
The now-applied, still-uncommitted
`compat/msvc600/cpython-3.11.16-vc6-headers.patch` fixes every site the
same mechanical way used for the header slice: hoist the bare declaration
(no initializer) to the top of its enclosing block, and turn the original
`TYPE name = expr;` into a plain assignment (`name = expr;`) at its
original position. This changes no evaluation order and no semantics for
any compiler, VC6 or otherwise. Two variants of this same transform were
needed, both already implicit in the mechanical rule but worth calling out
explicitly:
- **A declaration whose original initializer is a side-effecting call**
(e.g. `FILE *fp = _Py_fopen_obj(filename, "rb");` in
`pymain_run_file_obj()`) is hoisted bare and the call is kept as an
assignment at its *original* position, specifically so the call still
happens after the same preceding statements (an audit-hook check, in
this case) instead of moving earlier and changing behavior.
- **A platform-conditional declaration with the same name but a different
type per branch** (`pymain_run_startup()`'s `env`: `const wchar_t *env`
under `#ifdef MS_WINDOWS`, `const char *env` under `#else`) is hoisted
under the identical `#ifdef`/`#else`/`#endif` structure at the top of the
function, so each platform still only ever sees its own declaration.
Functions touched (8 total, 20 hoisted declarations, all in
`cpython/Modules/main.c`): `pymain_init()` (`preconfig`, `config`),
`pymain_import_readline()` (`mod`), `pymain_run_command()` (`cf`),
`pymain_run_file_obj()` (`fp`, `sb`, `cf`, `run`), `pymain_run_file()`
(`filename`, `program_name`, `res`), `pymain_run_startup()` (`startup`,
`env`, `fp`, `cf`), `pymain_run_stdin()` (a nested-block `exitcode`, plus
outer-scope `cf`, `run`), and `pymain_repl()` (`cf`, `res`).
`pymain_get_importer()`,
`pymain_sys_path_add_path0()`, `pymain_run_module()`,
`pymain_run_interactive_hook()`, `pymain_run_python()`, `Py_RunMain()`,
and `pymain_main()` were inspected and found to already declare every
local before the first statement in their block (including two
back-to-back declarations in `pymain_run_python()`'s `else if
(!config->safe_path)` block, which is valid C89 since no statement
separates them) — left untouched, consistent with "only the sites that
actually need it."
**A note on scope, since it matters for what this slice does and does not
claim:** several of the hoisted declarations are of type `PyCompilerFlags`
or are assigned via `_PyStatus_OK()`, both of which are initialized through
C99 compound-literal macros (`_PyCompilerFlags_INIT`, `_PyStatus_OK()`).
Hoisting the *declaration* is still the correct, necessary C89 fix for the
mixed-declaration violation at each of those sites — it eliminates that
specific error class everywhere it appeared in `main.c`, as confirmed
below — but the compound-literal *initializer itself* is a separate C99
feature VC6 does not support at any declaration position. That is a new,
distinct blocker, not a residue of this slice's fix; see "Current
first-blocker evidence" below, where it is documented rather than
papered over.
**Verified with the same two-probe approach used for the earlier slices:**
- `scripts/fixtures/vc6_decl_order_smoke.c` (new) — a minimal,
self-contained fixture (no CPython headers) that exercises, in
isolation, the shape of every hoist pattern used in `main.c`: a bare
hoist above an early return, a side-effecting-initializer hoist, a
hoisted pair where fixing the first declaration would otherwise turn an
immediately-following second declaration into a new violation, a
nested-block hoist, and the platform-conditional same-name/different-type
hoist. Kept separate from `main.c` itself so it keeps guarding the
general transformation shape even as `main.c` changes upstream:
```text
$ bash scripts/vc6-probe-core.sh scripts/fixtures/vc6_decl_order_smoke.c
== MSVC600 Py_BUILD_CORE compile probe ==
source : scripts/fixtures/vc6_decl_order_smoke.c
...
vc6_decl_order_smoke.c
== cl.exe exit status: 0 ==
```
No errors, no warnings.
- `cpython/Modules/main.c` itself, the same real core translation unit
used throughout this document. Every C2146/C2065/C2275
declaration-position error is gone; the previous 29-error-line run is
now down to 8 error lines, all a different error code (`C2059`) at a
different root cause (compound literals / designated initializers, not
declaration position — see "Current first-blocker evidence" below for
the full transcript).
Both probes are covered by 5 new checks added to
`scripts/vc6-probe-core.test.sh` (positive isolated-fixture compile,
no-errors check, a negative deliberately-broken-fixture check, its
error-surfaced check, and a check that `Modules/main.c`'s output no longer
contains any declaration-position error — `C2146`, an "undeclared
identifier" `C2065`, or a type-as-expression `C2275` — anywhere in its own
body), bringing that file to 21 checks total. Run: `bash
scripts/vc6-probe-core.test.sh`. Last run: all 21 checks `ok` (wine +
MSVC600 available in this environment). `scripts/vc6-probe.test.sh` was
re-run after this change and still passes (all 6 checks `ok`), confirming
the public-header probe is unaffected. The patch's reproducibility was
re-verified against a genuinely pristine tree (a fresh `cp -a cpython
/tmp/cpython_pristine_check && git -C /tmp/cpython_pristine_check checkout
-- .`, not a stash of the working checkout): `git apply --check` from
inside that pristine copy exits 0, and after applying, `diff -rq` between
the patched pristine copy and this project's actual `cpython/` working
tree (excluding `.git`) reports no differences. The temporary copy was
removed afterward; nothing under `cpython/` or `compat/` was affected by
this verification step.
## Compound-literal / designated-initializer slice
**Scope: the 8 C99 compound-literal/designated-initializer call sites
inside `Modules/main.c`'s own body only — no header-level macro change,
and no other file touched.** This is the follow-on to the "Modules/main.c
declaration-order slice" above, addressing exactly the blocker that slice
left recorded as out of scope: VC6 (a pre-C99 compiler) supports neither
C99 compound literals (`(PyStatus){...}`) nor C99 designated initializers
(`{ .field = value, ... }`), at any position in a block, regardless of
where the destination variable is declared.
Three distinct patterns were responsible, each fixed a different way, all
still uncommitted in `compat/msvc600/cpython-3.11.16-vc6-headers.patch`:
- **`status = _PyStatus_OK();`** (1 site, `pymain_init()`). Rather than
reimplementing the macro's field assignment by hand, this is replaced
with **`status = PyStatus_Ok();`** — the existing public API function
declared in `Include/cpython/initconfig.h` (`PyAPI_FUNC(PyStatus)
PyStatus_Ok(void);`) and defined in `Python/initconfig.c` as `PyStatus
PyStatus_Ok(void) { return _PyStatus_OK(); }` — i.e. it returns the
exact value the macro would have produced, by construction, not by
re-derivation. This needed no header change: the function was already
declared and part of the public ABI; `main.c` just wasn't calling it.
Behavior is identical for every compiler, VC6 or otherwise — `main.c`
now calls a function that itself still uses the compound-literal macro
internally in `Python/initconfig.c`, which is fine because
`Python/initconfig.c` is not part of this slice's compile-only probe (no
link is attempted or claimed) and is explicitly the next kind of file
this port will need to work through, not something silently skipped.
- **`cf = _PyCompilerFlags_INIT;`** (5 sites: `pymain_run_command()`,
`pymain_run_file_obj()`, `pymain_run_startup()`, `pymain_run_stdin()`,
`pymain_repl()`). No public-API equivalent exists for this one (unlike
`PyStatus_Ok()`), so each site is replaced with the field-by-field
assignment the macro itself expands to: `cf.cf_flags = 0;
cf.cf_feature_version = PY_MINOR_VERSION;`. `PyCompilerFlags` is a
two-field plain struct (`Include/cpython/compile.h`), and `cf` was
already declared (bare, no initializer) at the top of its enclosing
block by the prior declaration-order slice, so this is a pure
compound-literal-to-assignment substitution with no interaction with
declaration position.
- **`_PyArgv args = { .argc = argc, ... };`** (2 sites: `Py_Main()`,
`Py_BytesMain()`). No macro is involved here, just a plain C99
designated-initializer aggregate initializer on a local variable
declaration. Replaced with a bare declaration followed by one assignment
per field (`args.argc = argc; args.use_bytes_argv = 0; ...`); field
order in the replacement does not matter, unlike the initializer list it
replaces, since each is now an independent assignment statement.
Every other compiler is unaffected: `PyStatus_Ok()` is a normal function
call (no macro expansion difference for any compiler), and hoisting a
designated-initializer aggregate into field-by-field assignment, or a
compound-literal assignment into field-by-field assignment, changes no
value, no evaluation order, and no code path for any compiler that already
accepted the C99 form — it is strictly a syntax substitution for an
identical runtime effect.
**Verified with the same two-probe approach used for the earlier slices:**
- `scripts/fixtures/vc6_compound_literal_smoke.c` (new) — includes the
real `Python.h`/`pycore_initconfig.h` (like
`vc6_mixed_decls_smoke.c`, since `PyStatus`, `PyCompilerFlags`, and
`_PyArgv` are real CPython types, not reproducible in isolation the way
the atomics/stdbool fixtures are) and exercises all three replacement
patterns:
```text
$ bash scripts/vc6-probe-core.sh scripts/fixtures/vc6_compound_literal_smoke.c
== MSVC600 Py_BUILD_CORE compile probe ==
source : scripts/fixtures/vc6_compound_literal_smoke.c
...
vc6_compound_literal_smoke.c
== cl.exe exit status: 0 ==
```
No errors, no warnings.
- `cpython/Modules/main.c` itself, the same real core translation unit
used throughout this document. Every `C2059` compound-literal/
designated-initializer error is gone — the previous 8-error-line run is
now **0 error lines**, and the probe reports `cl.exe exit status: 0`:
```text
$ bash scripts/vc6-probe-core.sh
== MSVC600 Py_BUILD_CORE compile probe ==
source : cpython/Modules/main.c
prefix : /home/ubuntu/.wine-win9xport
Setting environment for using Microsoft Visual C++ tools.
main.c
...
== cl.exe exit status: 0 ==
```
(The only other output is MSVC600's standard informational `NOTE:` about
`WINVER` 0x0500 being a beta-era SDK combination, present on every probe
run in this environment, not new to this slice and not an error or
warning.) **This is a compile-only (`/c`) result for one translation
unit** — `scripts/vc6-probe-core.sh` never invokes the linker (see its
own header comment) — **not a claim that `Modules/main.c` links, that
`pythoncore`/`python.exe` builds, or that anything runs on Windows 9x.**
Both probes are covered by 6 new checks added to
`scripts/vc6-probe-core.test.sh` (positive isolated-fixture compile,
no-errors check, a negative deliberately-broken-fixture check, its
error-surfaced check, a check that `Modules/main.c`'s output no longer
contains any `C2059` compound-literal/designated-initializer error
anywhere in its own body, and a new end-to-end check that the
`Modules/main.c` probe itself now exits 0), bringing that file to 27
checks total. Run: `bash scripts/vc6-probe-core.test.sh`. Last run: all 27
checks `ok` (wine + MSVC600 available in this environment).
`scripts/vc6-probe.test.sh` was re-run after this change and still passes
(all 6 checks `ok`), confirming the public-header probe is unaffected. The
patch's reproducibility was re-verified against a genuinely pristine tree
(a fresh `cp -a cpython /tmp/cpython_pristine_check && git -C
/tmp/cpython_pristine_check checkout -- .`, not a stash of the working
checkout): `git apply --check` from inside that pristine copy exits 0, and
after applying, `diff -rq` between the patched pristine copy and this
project's actual `cpython/` working tree (excluding `.git`) reports no
differences. The temporary copy was removed afterward; nothing under
`cpython/` or `compat/` was affected by this verification step.
## Next blocker: `Python/pylifecycle.c`
After the PyRuntime section slice, the same real core probe was re-run:
```text
$ bash scripts/vc6-probe-core.sh cpython/Python/pylifecycle.c
== MSVC600 Py_BUILD_CORE compile probe ==
source : cpython/Python/pylifecycle.c
...
cpython\Python\pylifecycle.c(112) : error C2059: syntax error : '.'
cpython\Python\pylifecycle.c(112) : error C2059: syntax error : ','
cpython\Python\pylifecycle.c(130) : error C2059: syntax error : '{'
cpython\Python\pylifecycle.c(189) : error C2143: syntax error : missing ';' before 'type'
cpython\Python\pylifecycle.c(190) : error C2143: syntax error : missing ';' before 'type'
...
cpython\Python\pylifecycle.c(1232) : fatal error C1003: error count exceeds 100; stopping compilation
== cl.exe exit status: 2 ==
```
The prior first blocker at `Python/pylifecycle.c:91-92` is gone: VC6 no
longer reports `warning C4068: unknown pragma` for `#pragma section`, and no
longer rejects `__declspec(allocate("PyRuntime"))` with `C2341`. The new exact
first blocker is now the `_PyRuntimeState_INIT` designated-initializer macro
used by `_PyRuntime` itself (`C2059: syntax error : '.'` at the definition
line, now line 112 after the VC6 guard was added). After that, the file still
reaches the previously known broader C99 compound-literal/designated-initializer
and C89 mixed-declaration wall, eventually hitting the compiler's 100-error
cap. This slice did not attempt those broader `pylifecycle.c` rewrites.
## Current first-blocker evidence
The probe command was equivalent to:
**The previously recorded `long long` / C99 integer-suffix blocker is now
resolved and verified.** The uncommitted
`compat/msvc600/cpython-3.11.16-vc6-headers.patch` (already applied to the
`cpython/` working tree) replaces bare `long long` with the existing
`PY_LONG_LONG` macro in `Include/longobject.h`, `Include/pythread.h`, and
`Include/pyexpat.h`, and introduces a `PY_LL`/`PY_ULL` literal-suffix macro
pair (spelled `i64`/`ui64` for `_MSC_VER < 1300`, i.e. VC6) in `PC/pyconfig.h`
and `Include/pyport.h`, used in place of raw `123LL`/`123ULL` literals in
`Include/pythread.h`. With this patch applied, the full public header stack
compiles cleanly:
```text
cl /nologo /I compat/msvc600 /I cpython/Include /I cpython/PC \
/c cpython/Programs/python.c
$ bash scripts/vc6-probe.sh
== MSVC600 compile probe ==
source : cpython/Programs/python.c
prefix : /home/ubuntu/.wine-win9xport
Setting environment for using Microsoft Visual C++ tools.
python.c
== cl.exe exit status: 0 ==
```
The next errors include:
`cpython/Programs/python.c` (`#include "Python.h"` plus a `wmain`/`main`
stub) now compiles to `build/vc6-probe/python.obj` (verified present,
7115 bytes) with exit status 0. This exercises the entire public
`Include/*.h` surface reachable from `Python.h`, not just `pyport.h`.
- `Py_uintptr_t` / `Py_intptr_t` configuration failures in `Include/pyport.h`
- unsupported C `inline` syntax in `Include/object.h` and other public headers
- unsupported `__declspec(deprecated)` annotations
- additional C99/C99-era declarations and compatibility assumptions
**The `pycore_atomic.h`/`intrin.h` blocker, the `pycore_interp.h`/
`<stdbool.h>` blocker, and the mixed-declarations blocker in
`pycore_code.h`/`pycore_dict.h`/`pycore_list.h`/`pycore_call.h` described
in earlier revisions of this document are all now resolved; see "Atomics
slice", "stdbool slice", and "Mixed-declarations header slice" above for
full detail.** Probing the same real core translation unit
(`cpython/Modules/main.c`, which needs `Include/internal` on the include
path and `Py_BUILD_CORE` defined — both required unconditionally by every
`pycore_*.h` header) now gets past all of CPython's internal headers
reachable from its `#include` chain with no header-level error at all, and
the compiler proceeds into `Modules/main.c`'s own body — where it hits a
new wall, spread across nearly the entire 743-line file:
The compatibility header now supplies the pointer-sized typedefs required by
`pyport.h`; the current first compiler diagnostics after applying the saved
`compat/msvc600/cpython-3.11.16-vc6-headers.patch` are VC6's inability to parse
`long long` declarations in `Include/longobject.h` and C99-style integer
literal suffixes in `Include/pythread.h`. The shim and patch resolve only the
missing-header, pointer-type, inline, and deprecation layers; they are
intentionally not presented as a complete fix.
```text
$ bash scripts/vc6-probe-core.sh
== MSVC600 Py_BUILD_CORE compile probe ==
source : cpython/Modules/main.c
...
cpython\Modules\main.c(44) : error C2275: 'PyPreConfig' : illegal use of this type as an expression
cpython\Modules\main.c(44) : error C2146: syntax error : missing ';' before identifier 'preconfig'
cpython\Modules\main.c(44) : error C2065: 'preconfig' : undeclared identifier
...
cpython\Modules\main.c(52) : error C2275: 'PyConfig' : illegal use of this type as an expression
...
cpython\Modules\main.c(215) : error C2275: 'PyObject' : illegal use of this type as an expression
...
cpython\Modules\main.c(253) : error C2275: 'PyCompilerFlags' : illegal use of this type as an expression
...
== cl.exe exit status: 2 ==
```
(29 distinct error source lines total in that run, spanning lines 44
through 734 of `main.c` — effectively the whole file — across roughly a
dozen separate functions/blocks.)
**This mixed-declarations pattern inside `Modules/main.c`'s own body is now
resolved and verified; see "Modules/main.c declaration-order slice" above
for full detail.** With that fix applied, the same probe now gets past
every declaration-position error and hits a new, different, and
out-of-scope-for-that-slice blocker:
```text
$ bash scripts/vc6-probe-core.sh
== MSVC600 Py_BUILD_CORE compile probe ==
source : cpython/Modules/main.c
...
cpython\Modules\main.c(71) : error C2059: syntax error : '{'
cpython\Modules\main.c(256) : error C2059: syntax error : '{'
cpython\Modules\main.c(366) : error C2059: syntax error : '{'
cpython\Modules\main.c(448) : error C2059: syntax error : '{'
cpython\Modules\main.c(531) : error C2059: syntax error : '{'
cpython\Modules\main.c(564) : error C2059: syntax error : '{'
cpython\Modules\main.c(748) : error C2059: syntax error : '.'
cpython\Modules\main.c(760) : error C2059: syntax error : '.'
== cl.exe exit status: 2 ==
```
Down from 29 error lines to 8. Every one of these is the *same* new root
cause, unrelated to declaration position: a **C99 compound literal**,
optionally with **designated initializers**, neither of which VC6 (a
pre-C99 compiler) supports at all, at any position in a block. Two
distinct macros/patterns are responsible:
- `main.c:71`, `256`, `366`, `448`, `531`, `564` — six sites assigning
`PyCompilerFlags cf = _PyCompilerFlags_INIT;` (now `cf =
_PyCompilerFlags_INIT;` after the declaration-order fix) or `status =
_PyStatus_OK();`. `_PyCompilerFlags_INIT` and `_PyStatus_OK()` are
defined in `Include/cpython/compile.h` and
`Include/internal/pycore_initconfig.h` respectively as C99 compound
literals with designated initializers, e.g.:
```c
#define _PyStatus_OK() \
(PyStatus){._type = _PyStatus_TYPE_OK,}
#define _PyCompilerFlags_INIT \
(PyCompilerFlags){.cf_flags = 0, .cf_feature_version = PY_MINOR_VERSION}
```
`(PyStatus){...}` is a compound literal (C99 §6.5.2.5); `._type = ...`
inside it is a designated initializer (C99 §6.7.8). VC6 recognizes
neither construct and reports the same `C2059: syntax error : '{'` for
every one, regardless of whether the assignment is the first statement
in its block or not — hoisting the declaration (already done) cannot fix
this, because the problem is the initializer expression's syntax, not
where the variable is declared.
- `main.c:748`, `760` — the two `_PyArgv args = { .argc = argc, ... };`
aggregate initializations in `Py_Main()`/`Py_BytesMain()`, which use
plain C99 designated-initializer syntax (no compound-literal macro
involved, just `{ .field = value, ... }` directly in a local variable's
initializer) for the same reason: VC6 predates C99 designated
initializers entirely.
This is a **distinct C99 language-feature gap from mixed declarations**,
narrower in one sense (it doesn't force auditing every block in the file
the way declaration-position did) but not fixable by moving code around:
the fix, when attempted, will need to replace each compound-literal
initializer with explicit field-by-field assignment statements (e.g. `cf.cf_flags
= 0; cf.cf_feature_version = PY_MINOR_VERSION;`) or an equivalent
non-compound-literal C89 construct, and — for `_PyStatus_OK()` /
`_PyCompilerFlags_INIT` specifically — probably wants a header-level macro
change (in `pycore_initconfig.h` / `cpython/compile.h`) analogous to the
`_Py_ATOMIC_VC6_X86` gating already used for `pycore_atomic.h`, rather than
a per-call-site rewrite in every `.c` file that uses these macros, since
they are used far more broadly than just `Modules/main.c`. **No such fix is
attempted here; it is out of scope for this narrowly-scoped
declaration-order pass and is recorded as the next blocker.** No link or
runtime success is claimed at this or any earlier point in this document.
**Update: this blocker (all 8 `C2059` sites above) is now resolved for
`Modules/main.c`, via a per-call-site rewrite rather than the header-level
macro change speculated above** — see "Compound-literal /
designated-initializer slice" above for what was actually done and why
the per-call-site approach (not the header macro change) was chosen for
this narrow slice, and "Next blocker: `Python/pylifecycle.c`" above for
where the same two blocker classes (mixed declarations and compound
literals/designated initializers) recur next. This "Current first-blocker
evidence" section is kept as a historical record of how the diagnosis
proceeded; it is no longer the current state.
## Larger blockers to plan for
@@ -55,6 +830,35 @@ intentionally not presented as a complete fix.
2. **Compiler language gap:** CPython 3.11 source and headers use C99-era and
later MSVC features that VC6 does not implement. Compatibility macros and
carefully scoped source backports are required.
- VC6's C89-only parser rejects CPython's pervasive C99-style mixed
declarations-and-code. The instances of this pattern in four
`Include/internal/pycore_*.h` headers reachable from `Modules/main.c`
are fixed (see "Mixed-declarations header slice" above), and the
same pattern recurring throughout `Modules/main.c`'s own body (29
error sites across roughly a dozen functions) is also now fixed (see
"Modules/main.c declaration-order slice" above). Whether this pattern
recurs in CPython's other `.c` sources has not yet been surveyed;
each file would need its own probe-and-fix pass the same way
`main.c` did, not a whole-tree sweep in one change.
- **VC6 does not support C99 compound literals or designated
initializers at all**, independent of declaration position. The 8
sites of this pattern in `Modules/main.c` itself (`_PyStatus_OK()`,
`_PyCompilerFlags_INIT`, and inline `{ .field = value }`
initialization in `Py_Main()`/`Py_BytesMain()`) are now fixed via a
per-call-site rewrite, not a header-level macro change (see
"Compound-literal / designated-initializer slice" above for why: a
public-API function already existed for `_PyStatus_OK()`, and the
other two patterns are cheap to rewrite in place without touching
shared headers used far more broadly than `main.c`). `_PyStatus_OK()`
and `_PyCompilerFlags_INIT` are still used, unmodified, at many other
call sites across CPython outside `main.c` — this fix is scoped to
`main.c` only, as directed; a header-level VC6 branch (mirroring
`_Py_ATOMIC_VC6_X86`) remains a live option if a future slice finds
the per-call-site approach doesn't scale to the rest of the codebase.
The same two patterns (mixed declarations and compound literals) both
recur in `Python/pylifecycle.c`, the next core translation unit in
`main.c`'s own call chain; see "Next blocker: `Python/pylifecycle.c`"
above. Not yet fixed there.
3. **Win9x API gap:** CPython 3.11's Windows layer assumes NT-family Unicode,
synchronization, process, filesystem, and networking APIs. Windows 98 has
no wide-character file APIs and lacks multiple newer kernel APIs.
@@ -67,11 +871,53 @@ intentionally not presented as a complete fix.
## Recommended next implementation slice
Do not attempt the complete CPython solution yet. First create a minimal VC6
build target for `pythoncore` plus `Programs/python.c`, then make the public
headers compile under VC6 through a clearly isolated compatibility layer. Keep
all changes small and testable. After the core links, address the Win9x API
surface one subsystem at a time.
Do not attempt the complete CPython solution yet. The public header surface
compiles; `Include/internal/pycore_atomic.h` compiles for VC6/`_M_IX86`
(see "Atomics slice" above); `Include/internal/pycore_interp.h`'s
`<stdbool.h>` include now resolves via the project-local
`compat/msvc600/stdbool.h` shim (see "stdbool slice" above); the C99
mixed-declarations pattern in the four `pycore_*.h` headers reachable from
`Modules/main.c` is fixed (see "Mixed-declarations header slice" above);
that same pattern inside `Modules/main.c`'s own body is fixed (see
"Modules/main.c declaration-order slice" above); and the C99
compound-literal/designated-initializer pattern inside `Modules/main.c`'s
own body is now also fixed (see "Compound-literal /
designated-initializer slice" above). **`cpython/Modules/main.c` now
compiles with `cl.exe` exit status 0** under `scripts/vc6-probe-core.sh`
— no errors, no warnings — a compile-only (`/c`) result for this one
translation unit; no link or runtime claim is made.
The next blocker is still `Python/pylifecycle.c`, the next core translation
unit in `main.c`'s own call chain (via `Py_InitializeFromConfig()`), but it
has advanced past the VC6-incompatible PyRuntime section pragma. See "Next
blocker: `Python/pylifecycle.c`" above for the current probe transcript. It
now hits the same two already-solved blocker classes recurring at greater
scale (mixed declarations, compound literals/designated initializers), plus
enough sites of both to hit the compiler's 100-error cap before the whole
file is seen, so the true scope is not yet known. Recommended next steps:
1. Continue with `Python/pylifecycle.c` by applying the same two mechanical
transforms already validated on `Modules/main.c` — hoist mid-block
declarations (declaration-order slice) and replace compound-literal/
designated-initializer assignments with field-by-field assignment or,
where a public API function already exists (as `PyStatus_Ok()` did for
`_PyStatus_OK()`), a function call (compound-literal slice) — fixing
what the compiler surfaces in batches of roughly 100 (its per-run error
cap) and re-probing between batches, since the full error count for
this file is not yet known.
2. Re-evaluate the header-level-macro-vs-per-call-site tradeoff noted in
"Larger blockers to plan for" above once `_PyStatus_OK()`/
`_PyCompilerFlags_INIT`-style macros have been rewritten by hand in two
files (`main.c` and `pylifecycle.c`): if the same macros keep recurring
across many more `.c` files beyond these two, a header-level VC6 branch
(mirroring `_Py_ATOMIC_VC6_X86` in "Atomics slice" above) becomes more
attractive than continuing per-call-site rewrites file by file.
3. Only after enough core translation units compile, and a minimal
`pythoncore`/`Programs/python.c` link is attempted, should work move to
the Win9x API surface one subsystem at a time.
Validate incrementally with `scripts/vc6-probe-core.sh` against each file
being worked on, the same way this and prior slices did.
## Scope boundary
+10 -9
View File
@@ -11,17 +11,18 @@ The CPython baseline is kept in `cpython/` at tag `v3.11.16`. The MSVC600
bundle is kept in `MSVC600/` as a local external prerequisite and is not part of
the distributable project source.
The first VC6 compiler probe succeeds under Wine. A direct compile of
`cpython/Programs/python.c` currently fails in CPython headers because VC6
lacks or does not understand several facilities used by CPython 3.11:
The public-header probe and a real `Py_BUILD_CORE` translation unit now compile
under MSVC600 through the project compatibility layer:
- C99 `inttypes.h` and related integer-format definitions
- C99 `inline` syntax in C mode
- newer `__declspec(deprecated)` annotations
- modern pointer-width/type configuration expected by `pyport.h`
- the CPython 3.11 Visual Studio/MSBuild project model
- `cpython/Programs/python.c` compiles successfully.
- `cpython/Modules/main.c` compiles successfully.
- `cpython/Python/pylifecycle.c` is the current larger-core probe and remains
blocked by CPython 3.11 C99 initializers, mixed declarations, and additional
legacy-compiler/platform differences.
These are expected porting blockers and are recorded in `PORT_STATUS.md`.
This is still an experimental porting workspace, not a completed Windows 98
build. Exact verified milestones and blockers are recorded in
`PORT_STATUS.md`.
## Toolchain policy
@@ -1,8 +1,356 @@
diff --git a/Include/internal/pycore_atomic.h b/Include/internal/pycore_atomic.h
index 425d69f..748de28 100644
--- a/Include/internal/pycore_atomic.h
+++ b/Include/internal/pycore_atomic.h
@@ -16,7 +16,21 @@ extern "C" {
#endif
-#if defined(_MSC_VER)
+/* Visual C++ 6.0 (_MSC_VER 1200) predates <intrin.h> and the _Interlocked*
+ * compiler intrinsics (both introduced in Visual Studio 2005, _MSC_VER
+ * 1400) as well as <immintrin.h>. This port only targets 32-bit x86 under
+ * VC6, so gate the modern-MSVC include on that combination and give VC6/x86
+ * a project-local branch below (_Py_ATOMIC_VC6_X86) built on the Win32
+ * Interlocked* API instead. Every other compiler/version keeps the
+ * unmodified upstream behavior.
+ */
+#if defined(_MSC_VER) && _MSC_VER < 1300 && defined(_M_IX86)
+# define _Py_ATOMIC_VC6_X86 1
+ /* Declares InterlockedExchange()/InterlockedCompareExchange(), used by
+ * the _Py_ATOMIC_VC6_X86 branch below in place of the _Interlocked*
+ * intrinsics the modern-MSVC branch gets from <intrin.h>. */
+# include <windows.h>
+#elif defined(_MSC_VER)
#include <intrin.h>
#if defined(_M_IX86) || defined(_M_X64)
# include <immintrin.h>
@@ -238,7 +252,87 @@ _Py_ANNOTATE_MEMORY_ORDER(const volatile void *address, _Py_memory_order order)
This might affect performance but likely only in some very specific and
hard to measure scenario.
*/
-#if defined(_M_IX86) || defined(_M_X64)
+#if defined(_Py_ATOMIC_VC6_X86)
+/* VC6 / Windows 9x, 32-bit x86 only (_M_X64 is never defined by VC6).
+ VC6 has neither <intrin.h>/_Interlocked* compiler intrinsics (Visual
+ Studio 2005+) nor the _HLEAcquire/_HLERelease hardware-lock-elision
+ variants (Visual Studio 2013+) used by the modern-MSVC branch below.
+ Substitute the plain Win32 InterlockedExchange/InterlockedCompareExchange
+ functions, which are exported by KERNEL32.DLL on Windows 98 (this VC6
+ SDK's WINBASE.H declares them for _M_IX86 as compiler-intrinsic-backed
+ APIs, not the newer MRx000/Alpha-only intrinsic path above them in that
+ header). Like the modern-MSVC intrinsics, these give a full memory
+ barrier, so -- as with the HLE fallback described above -- every
+ _Py_memory_order maps to the same fully-fenced operation; there is no
+ separate acquire/release-only path to take.
+
+ _Py_atomic_address (uintptr_t) and _Py_atomic_int (int) are both 4
+ bytes on this 32-bit-only target, so the 64-bit store/load paths used
+ by the modern-MSVC branch below (guarded there by `sizeof(...) == 8`)
+ are unreachable here and are intentionally not implemented: this port
+ has no Win9x-era substitute for CMPXCHG8B-based 64-bit atomics. Fail
+ loudly at compile time rather than silently mishandling a 64-bit
+ target if this header is ever pulled into a VC6 build configured for
+ one (VC6 itself never defines _M_X64, so this should not trigger).
+*/
+#if defined(_M_X64)
+#error "VC6 64-bit atomics are not implemented; this port only targets 32-bit x86 (_M_IX86)."
+#endif
+
+typedef enum _Py_memory_order {
+ _Py_memory_order_relaxed,
+ _Py_memory_order_acquire,
+ _Py_memory_order_release,
+ _Py_memory_order_acq_rel,
+ _Py_memory_order_seq_cst
+} _Py_memory_order;
+
+typedef struct _Py_atomic_address {
+ volatile uintptr_t _value;
+} _Py_atomic_address;
+
+typedef struct _Py_atomic_int {
+ volatile int _value;
+} _Py_atomic_int;
+
+static __inline long
+_Py_vc6_InterlockedExchange(volatile long *target, long value)
+{
+ return InterlockedExchange((LPLONG)target, value);
+}
+
+static __inline long
+_Py_vc6_InterlockedCompareExchange(volatile long *destination, long exchange, long comperand)
+{
+ return (long)InterlockedCompareExchange(
+ (PVOID *)destination, (PVOID)exchange, (PVOID)comperand);
+}
+
+#define _Py_atomic_store_32bit(ATOMIC_VAL, NEW_VAL, ORDER) \
+ ((void)_Py_vc6_InterlockedExchange( \
+ (volatile long *)&((ATOMIC_VAL)->_value), (long)(NEW_VAL)))
+
+static __inline int
+_Py_atomic_load_32bit_impl(volatile int *value, int order)
+{
+ long old;
+ (void)order;
+ do {
+ old = *(volatile long *)value;
+ } while (_Py_vc6_InterlockedCompareExchange((volatile long *)value, old, old) != old);
+ return (int)old;
+}
+
+#define _Py_atomic_load_32bit(ATOMIC_VAL, ORDER) \
+ _Py_atomic_load_32bit_impl((volatile int *)&((ATOMIC_VAL)->_value), (ORDER))
+
+#define _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, ORDER) \
+ _Py_atomic_store_32bit((ATOMIC_VAL), (NEW_VAL), (ORDER))
+
+#define _Py_atomic_load_explicit(ATOMIC_VAL, ORDER) \
+ _Py_atomic_load_32bit((ATOMIC_VAL), (ORDER))
+
+#elif defined(_M_IX86) || defined(_M_X64)
typedef enum _Py_memory_order {
_Py_memory_order_relaxed,
_Py_memory_order_acquire,
diff --git a/Include/internal/pycore_call.h b/Include/internal/pycore_call.h
index 3ccacfa..3098e1e 100644
--- a/Include/internal/pycore_call.h
+++ b/Include/internal/pycore_call.h
@@ -38,18 +38,21 @@ extern PyObject * _PyObject_CallMethodFormat(
static inline vectorcallfunc
_PyVectorcall_FunctionInline(PyObject *callable)
{
+ PyTypeObject *tp;
+ Py_ssize_t offset;
+ vectorcallfunc ptr;
+
assert(callable != NULL);
- PyTypeObject *tp = Py_TYPE(callable);
+ tp = Py_TYPE(callable);
if (!PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL)) {
return NULL;
}
assert(PyCallable_Check(callable));
- Py_ssize_t offset = tp->tp_vectorcall_offset;
+ offset = tp->tp_vectorcall_offset;
assert(offset > 0);
- vectorcallfunc ptr;
memcpy(&ptr, (char *) callable + offset, sizeof(ptr));
return ptr;
}
diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 3a24a65..4db8abc 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -149,18 +149,20 @@ typedef unsigned char _PyLocals_Kind;
static inline _PyLocals_Kind
_PyLocals_GetKind(PyObject *kinds, int i)
{
+ char *ptr;
assert(PyBytes_Check(kinds));
assert(0 <= i && i < PyBytes_GET_SIZE(kinds));
- char *ptr = PyBytes_AS_STRING(kinds);
+ ptr = PyBytes_AS_STRING(kinds);
return (_PyLocals_Kind)(ptr[i]);
}
static inline void
_PyLocals_SetKind(PyObject *kinds, int i, _PyLocals_Kind kind)
{
+ char *ptr;
assert(PyBytes_Check(kinds));
assert(0 <= i && i < PyBytes_GET_SIZE(kinds));
- char *ptr = PyBytes_AS_STRING(kinds);
+ ptr = PyBytes_AS_STRING(kinds);
ptr[i] = (char) kind;
}
@@ -520,11 +522,12 @@ adaptive_counter_start(void) {
static inline uint16_t
adaptive_counter_backoff(uint16_t counter) {
unsigned int backoff = counter & ((1<<ADAPTIVE_BACKOFF_BITS)-1);
+ unsigned int value;
backoff++;
if (backoff > MAX_BACKOFF_VALUE) {
backoff = MAX_BACKOFF_VALUE;
}
- unsigned int value = (1 << backoff) - 1;
+ value = (1 << backoff) - 1;
return adaptive_counter_bits(value, backoff);
}
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index dc308fe..16feaf3 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -163,9 +163,11 @@ extern PyObject *_PyDict_FromItems(
static inline void
_PyDictValues_AddToInsertionOrder(PyDictValues *values, Py_ssize_t ix)
{
+ uint8_t *size_ptr;
+ int size;
assert(ix < SHARED_KEYS_MAX_SIZE);
- uint8_t *size_ptr = ((uint8_t *)values)-2;
- int size = *size_ptr;
+ size_ptr = ((uint8_t *)values)-2;
+ size = *size_ptr;
assert(size+2 < ((uint8_t *)values)[-1]);
size++;
size_ptr[-size] = (uint8_t)ix;
diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h
index 860dce1..da38ee6 100644
--- a/Include/internal/pycore_list.h
+++ b/Include/internal/pycore_list.h
@@ -43,10 +43,12 @@ _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem);
static inline int
_PyList_AppendTakeRef(PyListObject *self, PyObject *newitem)
{
+ Py_ssize_t len;
+ Py_ssize_t allocated;
assert(self != NULL && newitem != NULL);
assert(PyList_Check(self));
- Py_ssize_t len = PyList_GET_SIZE(self);
- Py_ssize_t allocated = self->allocated;
+ len = PyList_GET_SIZE(self);
+ allocated = self->allocated;
assert((size_t)len + 1 < PY_SSIZE_T_MAX);
if (allocated > len) {
PyList_SET_ITEM(self, len, newitem);
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index f022f82..d1c55ae 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -14,6 +14,14 @@ extern "C" {
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_runtime.h" // _PyRuntime
+/* Visual C++ 6.0 predates C99's __func__ predefined identifier. It also
+ lacks MSVC's later __FUNCTION__ extension, so provide a VC6-only fallback
+ for debug/fatal diagnostics in this header without changing any other
+ compiler's function-name reporting. */
+#if defined(_MSC_VER) && _MSC_VER < 1300 && !defined(__func__)
+# define __func__ "<unknown>"
+#endif
+
#define _PyObject_IMMORTAL_INIT(type) \
{ \
.ob_refcnt = 999999999, \
@@ -125,19 +133,24 @@ static inline void _PyObject_GC_TRACK(
#endif
PyObject *op)
{
+ PyGC_Head *gc;
+ PyInterpreterState *interp;
+ PyGC_Head *generation0;
+ PyGC_Head *last;
+
_PyObject_ASSERT_FROM(op, !_PyObject_GC_IS_TRACKED(op),
"object already tracked by the garbage collector",
filename, lineno, __func__);
- PyGC_Head *gc = _Py_AS_GC(op);
+ gc = _Py_AS_GC(op);
_PyObject_ASSERT_FROM(op,
(gc->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0,
"object is in generation which is garbage collected",
filename, lineno, __func__);
- PyInterpreterState *interp = _PyInterpreterState_GET();
- PyGC_Head *generation0 = interp->gc.generation0;
- PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
+ interp = _PyInterpreterState_GET();
+ generation0 = interp->gc.generation0;
+ last = (PyGC_Head*)(generation0->_gc_prev);
_PyGCHead_SET_NEXT(last, gc);
_PyGCHead_SET_PREV(gc, last);
_PyGCHead_SET_NEXT(gc, generation0);
@@ -161,13 +174,17 @@ static inline void _PyObject_GC_UNTRACK(
#endif
PyObject *op)
{
+ PyGC_Head *gc;
+ PyGC_Head *prev;
+ PyGC_Head *next;
+
_PyObject_ASSERT_FROM(op, _PyObject_GC_IS_TRACKED(op),
"object not tracked by the garbage collector",
filename, lineno, __func__);
- PyGC_Head *gc = _Py_AS_GC(op);
- PyGC_Head *prev = _PyGCHead_PREV(gc);
- PyGC_Head *next = _PyGCHead_NEXT(gc);
+ gc = _Py_AS_GC(op);
+ prev = _PyGCHead_PREV(gc);
+ next = _PyGCHead_NEXT(gc);
_PyGCHead_SET_NEXT(prev, next);
_PyGCHead_SET_PREV(next, prev);
gc->_gc_next = 0;
diff --git a/Include/longobject.h b/Include/longobject.h
index 778cb62..d25edf7 100644
--- a/Include/longobject.h
+++ b/Include/longobject.h
@@ -81,12 +81,12 @@ PyAPI_FUNC(double) PyLong_AsDouble(PyObject *);
PyAPI_FUNC(PyObject *) PyLong_FromVoidPtr(void *);
PyAPI_FUNC(void *) PyLong_AsVoidPtr(PyObject *);
-PyAPI_FUNC(PyObject *) PyLong_FromLongLong(long long);
-PyAPI_FUNC(PyObject *) PyLong_FromUnsignedLongLong(unsigned long long);
-PyAPI_FUNC(long long) PyLong_AsLongLong(PyObject *);
-PyAPI_FUNC(unsigned long long) PyLong_AsUnsignedLongLong(PyObject *);
-PyAPI_FUNC(unsigned long long) PyLong_AsUnsignedLongLongMask(PyObject *);
-PyAPI_FUNC(long long) PyLong_AsLongLongAndOverflow(PyObject *, int *);
+PyAPI_FUNC(PyObject *) PyLong_FromLongLong(PY_LONG_LONG);
+PyAPI_FUNC(PyObject *) PyLong_FromUnsignedLongLong(unsigned PY_LONG_LONG);
+PyAPI_FUNC(PY_LONG_LONG) PyLong_AsLongLong(PyObject *);
+PyAPI_FUNC(unsigned PY_LONG_LONG) PyLong_AsUnsignedLongLong(PyObject *);
+PyAPI_FUNC(unsigned PY_LONG_LONG) PyLong_AsUnsignedLongLongMask(PyObject *);
+PyAPI_FUNC(PY_LONG_LONG) PyLong_AsLongLongAndOverflow(PyObject *, int *);
PyAPI_FUNC(PyObject *) PyLong_FromString(const char *, char **, int);
diff --git a/Include/pyexpat.h b/Include/pyexpat.h
index a676e16..017e86d 100644
--- a/Include/pyexpat.h
+++ b/Include/pyexpat.h
@@ -54,12 +54,12 @@ struct PyExpat_CAPI
XML_Bool (*SetReparseDeferralEnabled)(XML_Parser parser, XML_Bool enabled);
/* might be NULL for expat < 2.7.2 */
XML_Bool (*SetAllocTrackerActivationThreshold)(
- XML_Parser parser, unsigned long long activationThresholdBytes);
+ XML_Parser parser, unsigned PY_LONG_LONG activationThresholdBytes);
XML_Bool (*SetAllocTrackerMaximumAmplification)(
XML_Parser parser, float maxAmplificationFactor);
/* might be NULL for expat < 2.4.0 */
XML_Bool (*SetBillionLaughsAttackProtectionActivationThreshold)(
- XML_Parser parser, unsigned long long activationThresholdBytes);
+ XML_Parser parser, unsigned PY_LONG_LONG activationThresholdBytes);
XML_Bool (*SetBillionLaughsAttackProtectionMaximumAmplification)(
XML_Parser parser, float maxAmplificationFactor);
/* might be NULL for expat < 2.8.0 */
diff --git a/Include/pyport.h b/Include/pyport.h
index b3b8b6f..4da45ef 100644
index b3b8b6f..ae930fc 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -334,9 +334,12 @@ extern "C" {
@@ -97,6 +97,13 @@ Used in: Py_SAFE_DOWNCAST
#define PY_ULLONG_MAX ULLONG_MAX
#endif
+/* Spelling of 64 bit integer literal suffixes; see PC/pyconfig.h for the
+ compilers that need something other than the C99 LL/ULL suffixes. */
+#ifndef PY_LL
+#define PY_LL(x) x##LL
+#define PY_ULL(x) x##ULL
+#endif
+
#define PY_UINT32_T uint32_t
#define PY_UINT64_T uint64_t
@@ -334,9 +341,12 @@ extern "C" {
#if defined(__GNUC__) \
&& ((__GNUC__ >= 4) || (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define Py_DEPRECATED(VERSION_UNUSED) __attribute__((__deprecated__))
@@ -16,7 +364,7 @@ index b3b8b6f..4da45ef 100644
#else
#define Py_DEPRECATED(VERSION_UNUSED)
#endif
@@ -352,10 +355,14 @@ extern "C" {
@@ -352,10 +362,14 @@ extern "C" {
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#define _Py_COMP_DIAG_POP _Pragma("GCC diagnostic pop")
@@ -32,3 +380,388 @@ index b3b8b6f..4da45ef 100644
#else
#define _Py_COMP_DIAG_PUSH
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS
diff --git a/Include/pythread.h b/Include/pythread.h
index a483290..defdaf1 100644
--- a/Include/pythread.h
+++ b/Include/pythread.h
@@ -40,23 +40,23 @@ PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int);
module exposes a higher-level API, with timeouts expressed in seconds
and floating-point numbers allowed.
*/
-#define PY_TIMEOUT_T long long
+#define PY_TIMEOUT_T PY_LONG_LONG
#if defined(_POSIX_THREADS)
/* PyThread_acquire_lock_timed() uses _PyTime_FromNanoseconds(us * 1000),
convert microseconds to nanoseconds. */
-# define PY_TIMEOUT_MAX (LLONG_MAX / 1000)
+# define PY_TIMEOUT_MAX (PY_LLONG_MAX / 1000)
#elif defined (NT_THREADS)
// WaitForSingleObject() accepts timeout in milliseconds in the range
// [0; 0xFFFFFFFE] (DWORD type). INFINITE value (0xFFFFFFFF) means no
// timeout. 0xFFFFFFFE milliseconds is around 49.7 days.
-# if 0xFFFFFFFELL * 1000 < LLONG_MAX
-# define PY_TIMEOUT_MAX (0xFFFFFFFELL * 1000)
+# if PY_LL(0xFFFFFFFE) * 1000 < PY_LLONG_MAX
+# define PY_TIMEOUT_MAX (PY_LL(0xFFFFFFFE) * 1000)
# else
-# define PY_TIMEOUT_MAX LLONG_MAX
+# define PY_TIMEOUT_MAX PY_LLONG_MAX
# endif
#else
-# define PY_TIMEOUT_MAX LLONG_MAX
+# define PY_TIMEOUT_MAX PY_LLONG_MAX
#endif
diff --git a/Modules/main.c b/Modules/main.c
index f6dade1..011ae59 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -35,13 +35,14 @@ static PyStatus
pymain_init(const _PyArgv *args)
{
PyStatus status;
+ PyPreConfig preconfig;
+ PyConfig config;
status = _PyRuntime_Initialize();
if (_PyStatus_EXCEPTION(status)) {
return status;
}
- PyPreConfig preconfig;
PyPreConfig_InitPythonConfig(&preconfig);
status = _Py_PreInitializeFromPyArgv(&preconfig, args);
@@ -49,7 +50,6 @@ pymain_init(const _PyArgv *args)
return status;
}
- PyConfig config;
PyConfig_InitPythonConfig(&config);
/* pass NULL as the config: config is read from command line arguments,
@@ -68,7 +68,13 @@ pymain_init(const _PyArgv *args)
if (_PyStatus_EXCEPTION(status)) {
goto done;
}
- status = _PyStatus_OK();
+ /* VC6 (MSVC600) has no C99 compound literals, so _PyStatus_OK() (a
+ compound literal macro) cannot be used here; PyStatus_Ok() is the
+ public API function that returns the identical value
+ (Python/initconfig.c: `PyStatus PyStatus_Ok(void) { return
+ _PyStatus_OK(); }`), so this is not a behavior change for any
+ compiler. */
+ status = PyStatus_Ok();
done:
PyConfig_Clear(&config);
@@ -202,6 +208,8 @@ pymain_header(const PyConfig *config)
static void
pymain_import_readline(const PyConfig *config)
{
+ PyObject *mod;
+
if (config->isolated) {
return;
}
@@ -212,7 +220,7 @@ pymain_import_readline(const PyConfig *config)
return;
}
- PyObject *mod = PyImport_ImportModule("readline");
+ mod = PyImport_ImportModule("readline");
if (mod == NULL) {
PyErr_Clear();
}
@@ -234,6 +242,7 @@ pymain_run_command(wchar_t *command)
{
PyObject *unicode, *bytes;
int ret;
+ PyCompilerFlags cf;
unicode = PyUnicode_FromWideChar(command, -1);
if (unicode == NULL) {
@@ -250,7 +259,12 @@ pymain_run_command(wchar_t *command)
goto error;
}
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
+ /* VC6 has no C99 compound literals/designated initializers, so
+ _PyCompilerFlags_INIT (`(PyCompilerFlags){.cf_flags = 0,
+ .cf_feature_version = PY_MINOR_VERSION}`) cannot be used as an
+ initializer here; assign the same two fields explicitly instead. */
+ cf.cf_flags = 0;
+ cf.cf_feature_version = PY_MINOR_VERSION;
cf.cf_flags |= PyCF_IGNORE_COOKIE;
ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), &cf);
Py_DECREF(bytes);
@@ -317,11 +331,16 @@ static int
pymain_run_file_obj(PyObject *program_name, PyObject *filename,
int skip_source_first_line)
{
+ FILE *fp;
+ struct _Py_stat_struct sb;
+ PyCompilerFlags cf;
+ int run;
+
if (PySys_Audit("cpython.run_file", "O", filename) < 0) {
return pymain_exit_err_print();
}
- FILE *fp = _Py_fopen_obj(filename, "rb");
+ fp = _Py_fopen_obj(filename, "rb");
if (fp == NULL) {
// Ignore the OSError
PyErr_Clear();
@@ -341,7 +360,6 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename,
}
}
- struct _Py_stat_struct sb;
if (_Py_fstat_noraise(fileno(fp), &sb) == 0 && S_ISDIR(sb.st_mode)) {
PySys_FormatStderr("%S: %R is a directory, cannot continue\n",
program_name, filename);
@@ -356,28 +374,35 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename,
}
/* PyRun_AnyFileExFlags(closeit=1) calls fclose(fp) before running code */
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
- int run = _PyRun_AnyFileObject(fp, filename, 1, &cf);
+ /* See pymain_run_command() above for why this is not
+ _PyCompilerFlags_INIT (VC6 has no compound literals). */
+ cf.cf_flags = 0;
+ cf.cf_feature_version = PY_MINOR_VERSION;
+ run = _PyRun_AnyFileObject(fp, filename, 1, &cf);
return (run != 0);
}
static int
pymain_run_file(const PyConfig *config)
{
- PyObject *filename = PyUnicode_FromWideChar(config->run_filename, -1);
+ PyObject *filename;
+ PyObject *program_name;
+ int res;
+
+ filename = PyUnicode_FromWideChar(config->run_filename, -1);
if (filename == NULL) {
PyErr_Print();
return -1;
}
- PyObject *program_name = PyUnicode_FromWideChar(config->program_name, -1);
+ program_name = PyUnicode_FromWideChar(config->program_name, -1);
if (program_name == NULL) {
Py_DECREF(filename);
PyErr_Print();
return -1;
}
- int res = pymain_run_file_obj(program_name, filename,
- config->skip_source_first_line);
+ res = pymain_run_file_obj(program_name, filename,
+ config->skip_source_first_line);
Py_DECREF(filename);
Py_DECREF(program_name);
return res;
@@ -388,12 +413,20 @@ static int
pymain_run_startup(PyConfig *config, int *exitcode)
{
int ret;
+ PyObject *startup = NULL;
+#ifdef MS_WINDOWS
+ const wchar_t *env;
+#else
+ const char *env;
+#endif
+ FILE *fp;
+ PyCompilerFlags cf;
+
if (!config->use_environment) {
return 0;
}
- PyObject *startup = NULL;
#ifdef MS_WINDOWS
- const wchar_t *env = _wgetenv(L"PYTHONSTARTUP");
+ env = _wgetenv(L"PYTHONSTARTUP");
if (env == NULL || env[0] == L'\0') {
return 0;
}
@@ -402,7 +435,7 @@ pymain_run_startup(PyConfig *config, int *exitcode)
goto error;
}
#else
- const char *env = _Py_GetEnv(config->use_environment, "PYTHONSTARTUP");
+ env = _Py_GetEnv(config->use_environment, "PYTHONSTARTUP");
if (env == NULL) {
return 0;
}
@@ -415,7 +448,7 @@ pymain_run_startup(PyConfig *config, int *exitcode)
goto error;
}
- FILE *fp = _Py_fopen_obj(startup, "r");
+ fp = _Py_fopen_obj(startup, "r");
if (fp == NULL) {
int save_errno = errno;
PyErr_Clear();
@@ -426,7 +459,10 @@ pymain_run_startup(PyConfig *config, int *exitcode)
goto error;
}
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
+ /* See pymain_run_command() above for why this is not
+ _PyCompilerFlags_INIT (VC6 has no compound literals). */
+ cf.cf_flags = 0;
+ cf.cf_feature_version = PY_MINOR_VERSION;
(void) _PyRun_SimpleFileObject(fp, startup, 0, &cf);
PyErr_Clear();
fclose(fp);
@@ -482,11 +518,15 @@ pymain_run_interactive_hook(int *exitcode)
static int
pymain_run_stdin(PyConfig *config)
{
+ PyCompilerFlags cf;
+ int run;
+
if (stdin_is_interactive(config)) {
+ int exitcode;
+
config->inspect = 0;
Py_InspectFlag = 0; /* do exit on SystemExit */
- int exitcode;
if (pymain_run_startup(config, &exitcode)) {
return exitcode;
}
@@ -505,8 +545,11 @@ pymain_run_stdin(PyConfig *config)
return pymain_exit_err_print();
}
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
- int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
+ /* See pymain_run_command() above for why this is not
+ _PyCompilerFlags_INIT (VC6 has no compound literals). */
+ cf.cf_flags = 0;
+ cf.cf_feature_version = PY_MINOR_VERSION;
+ run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
return (run != 0);
}
@@ -514,6 +557,9 @@ pymain_run_stdin(PyConfig *config)
static void
pymain_repl(PyConfig *config, int *exitcode)
{
+ PyCompilerFlags cf;
+ int res;
+
/* Check this environment variable at the end, to give programs the
opportunity to set it from Python. */
if (!config->inspect && _Py_GetEnv(config->use_environment, "PYTHONINSPECT")) {
@@ -535,8 +581,11 @@ pymain_repl(PyConfig *config, int *exitcode)
return;
}
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
- int res = PyRun_AnyFileFlags(stdin, "<stdin>", &cf);
+ /* See pymain_run_command() above for why this is not
+ _PyCompilerFlags_INIT (VC6 has no compound literals). */
+ cf.cf_flags = 0;
+ cf.cf_feature_version = PY_MINOR_VERSION;
+ res = PyRun_AnyFileFlags(stdin, "<stdin>", &cf);
*exitcode = (res != 0);
}
@@ -718,11 +767,15 @@ pymain_main(_PyArgv *args)
int
Py_Main(int argc, wchar_t **argv)
{
- _PyArgv args = {
- .argc = argc,
- .use_bytes_argv = 0,
- .bytes_argv = NULL,
- .wchar_argv = argv};
+ /* VC6 has no C99 designated initializers, so the aggregate initializer
+ below cannot be used; assign each field explicitly instead (field
+ order does not matter for plain assignment, unlike the initializer
+ list it replaces). */
+ _PyArgv args;
+ args.argc = argc;
+ args.use_bytes_argv = 0;
+ args.bytes_argv = NULL;
+ args.wchar_argv = argv;
return pymain_main(&args);
}
@@ -730,11 +783,12 @@ Py_Main(int argc, wchar_t **argv)
int
Py_BytesMain(int argc, char **argv)
{
- _PyArgv args = {
- .argc = argc,
- .use_bytes_argv = 1,
- .bytes_argv = argv,
- .wchar_argv = NULL};
+ /* See Py_Main() above: VC6 has no C99 designated initializers. */
+ _PyArgv args;
+ args.argc = argc;
+ args.use_bytes_argv = 1;
+ args.bytes_argv = argv;
+ args.wchar_argv = NULL;
return pymain_main(&args);
}
diff --git a/PC/pyconfig.h b/PC/pyconfig.h
index 959b851..6d8ce84 100644
--- a/PC/pyconfig.h
+++ b/PC/pyconfig.h
@@ -256,6 +256,19 @@ typedef int pid_t;
# define PY_ULLONG_MAX _UI64_MAX
#endif
+/* Spelling of 64 bit integer literal suffixes. MSVC before Visual C++ .NET
+ (_MSC_VER < 1300, i.e. Visual C++ 6.0 and earlier) rejects the C99 LL/ULL
+ suffixes and spells them i64/ui64 instead. */
+#ifndef PY_LL
+# if defined(_MSC_VER) && _MSC_VER < 1300
+# define PY_LL(x) x##i64
+# define PY_ULL(x) x##ui64
+# else
+# define PY_LL(x) x##LL
+# define PY_ULL(x) x##ULL
+# endif
+#endif
+
/* For Windows the Python core is in a DLL by default. Test
Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
#if !defined(MS_NO_COREDLL) && !defined(Py_NO_ENABLE_SHARED)
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 9248e97..a2220e9 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -88,8 +88,14 @@ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
#if defined(MS_WINDOWS)
+# if defined(_MSC_VER) && _MSC_VER < 1300
+/* VC6 does not support #pragma section plus __declspec(allocate()).
+ Use its older data_seg spelling for this initialized object instead. */
+# pragma data_seg("PyRuntime")
+# else
#pragma section("PyRuntime", read, write)
__declspec(allocate("PyRuntime"))
+# endif
#elif defined(__APPLE__)
@@ -104,6 +110,9 @@ _PyRuntimeState _PyRuntime
__attribute__ ((section (".PyRuntime")))
#endif
= _PyRuntimeState_INIT;
+#if defined(MS_WINDOWS) && defined(_MSC_VER) && _MSC_VER < 1300
+# pragma data_seg()
+#endif
_Py_COMP_DIAG_POP
static int runtime_initialized = 0;
+35
View File
@@ -0,0 +1,35 @@
#ifndef PYTHON_WIN9X_MSVC600_STDBOOL_H
#define PYTHON_WIN9X_MSVC600_STDBOOL_H
/* VC6-only fallback for the C99 <stdbool.h> header.
*
* Visual C++ 6.0 (_MSC_VER 1200) predates C99 and ships no <stdbool.h>;
* in VC6's C mode `bool`/`true`/`false` are not keywords at all (they are
* C++-only there). This project-local header is found ahead of any system
* <stdbool.h> only because compat/msvc600 is placed first on the VC6 probe
* scripts' include path (scripts/vc6-probe.sh, scripts/vc6-probe-core.sh);
* it is never installed anywhere a standards-conforming compiler's own
* <stdbool.h> would be found instead, so it never shadows a real one. The
* _MSC_VER guard below is defence in depth for that same reason, and keeps
* this file inert (and diagnosable, via the #error) if it is ever reached
* by a compiler other than the VC6 this port targets.
*
* Modeled on the standard C99 <stdbool.h> contract: defines the macros
* bool, true, false, and __bool_true_false_are_defined, and (matching
* C99, which requires a program be able to #undef/redefine bool/true/
* false) leaves them as plain macros rather than a typedef, so existing
* C99-style `#undef bool` idioms, were CPython to use one, would still
* work.
*/
#if !defined(_MSC_VER) || _MSC_VER >= 1300
#error "compat/msvc600/stdbool.h is a VC6-only (_MSC_VER < 1300) fallback; a standard <stdbool.h> should be used instead."
#endif
#define bool int
#define true 1
#define false 0
#define __bool_true_false_are_defined 1
#endif /* PYTHON_WIN9X_MSVC600_STDBOOL_H */
+36
View File
@@ -0,0 +1,36 @@
/* Focused, isolated compile smoke test for Include/internal/pycore_atomic.h
* under VC6/_M_IX86.
*
* Deliberately does NOT go through Python.h or any other pycore_*.h header:
* pycore_interp.h (reached transitively from a real core translation unit
* like Modules/main.c) additionally requires <stdbool.h>, a separate,
* unrelated VC6 gap tracked in PORT_STATUS.md. Isolating pycore_atomic.h
* here keeps this probe's result attributable to the atomics compatibility
* work specifically.
*
* Py_BUILD_CORE is supplied by the probe script (/D Py_BUILD_CORE), the
* same way it is for a real core translation unit. <inttypes.h> (resolved
* to compat/msvc600/inttypes.h for VC6 via the include path) provides
* uintptr_t, which pycore_atomic.h needs but does not itself include; in
* a real build this comes from Python.h/pyport.h, included ahead of any
* pycore_*.h header.
*/
#include <inttypes.h>
#include "pycore_atomic.h"
static _Py_atomic_int atomic_int_value;
static _Py_atomic_address atomic_addr_value;
int
vc6_atomic_smoke(void)
{
_Py_atomic_store(&atomic_int_value, 1);
_Py_atomic_store_relaxed(&atomic_int_value, 2);
_Py_atomic_store(&atomic_addr_value, (uintptr_t)0);
_Py_atomic_store_relaxed(&atomic_addr_value, (uintptr_t)1);
return _Py_atomic_load(&atomic_int_value)
+ _Py_atomic_load_relaxed(&atomic_int_value)
+ (int)_Py_atomic_load(&atomic_addr_value)
+ (int)_Py_atomic_load_relaxed(&atomic_addr_value);
}
@@ -0,0 +1,64 @@
/* Focused compile smoke test for the VC6 C99 compound-literal /
* designated-initializer fixes applied to cpython/Modules/main.c.
*
* VC6 (a pre-C99 compiler) supports neither C99 compound literals
* (`(PyStatus){...}`) nor C99 designated initializers (`{ .field = value,
* ... }`), at any position in a block. Modules/main.c relied on both via
* three distinct patterns:
*
* 1. `status = _PyStatus_OK();` -- a compound-literal macro used as a
* plain assignment's right-hand side. Fixed by calling the existing
* public API function PyStatus_Ok() instead, which
* Python/initconfig.c defines as `return _PyStatus_OK();` -- the
* identical value, no header change needed.
* 2. `cf = _PyCompilerFlags_INIT;` -- likewise a compound-literal macro
* used as a plain assignment. Fixed by assigning PyCompilerFlags's
* two fields explicitly (cf_flags, cf_feature_version), matching the
* macro's own definition field-for-field.
* 3. `_PyArgv args = { .argc = argc, ... };` -- a designated-initializer
* aggregate initializer with no macro involved. Fixed by declaring
* the variable bare and assigning each field explicitly; field order
* does not matter for plain assignment, unlike an initializer list.
*
* See PORT_STATUS.md ("Compound-literal/designated-initializer slice") for
* full detail. Like vc6_mixed_decls_smoke.c, this fixture includes the
* real "Python.h" (Py_BUILD_CORE is supplied by the probe script) because
* PyStatus, PyCompilerFlags, and _PyArgv are real CPython types pulled in
* through the normal public/internal header chain, the same way
* Modules/main.c itself reaches them.
*/
#include "Python.h"
#include "pycore_initconfig.h" // _PyArgv, _PyStatus_OK()
PyStatus
vc6_status_ok_smoke(void)
{
PyStatus status;
/* pattern 1: was `status = _PyStatus_OK();` */
status = PyStatus_Ok();
return status;
}
PyCompilerFlags
vc6_compiler_flags_smoke(void)
{
PyCompilerFlags cf;
/* pattern 2: was `cf = _PyCompilerFlags_INIT;` */
cf.cf_flags = 0;
cf.cf_feature_version = PY_MINOR_VERSION;
return cf;
}
Py_ssize_t
vc6_argv_designated_init_smoke(int argc, wchar_t **argv)
{
/* pattern 3: was `_PyArgv args = { .argc = argc, ... };` */
_PyArgv args;
args.argc = argc;
args.use_bytes_argv = 0;
args.bytes_argv = NULL;
args.wchar_argv = argv;
return args.argc;
}
+162
View File
@@ -0,0 +1,162 @@
/* Focused compile smoke test for the VC6 C89-mixed-declarations fixes
* applied to cpython/Modules/main.c.
*
* VC6 is a C89-only compiler (plus Microsoft extensions): block-scope
* declarations must precede all statements in that block. Modules/main.c
* declared many locals mid-function (typically right before first use,
* C99 style); the fix hoists each declaration to the top of its enclosing
* block, without initializer, and turns the original `TYPE name = expr;`
* into a plain `name = expr;` assignment at its original position. This
* changes no evaluation order and no semantics. See PORT_STATUS.md
* ("Modules/main.c mixed-declarations slice") for full detail.
*
* This fixture is deliberately self-contained (no CPython headers) so it
* exercises the *shape* of every hoist pattern used in main.c, independent
* of main.c's own evolution:
*
* 1. a bare declaration hoisted above an early-return statement
* (pymain_init's `preconfig`/`config`);
* 2. a declaration whose initializer is a side-effecting call, hoisted
* bare and assigned at its original site so the call itself still
* happens after the same preceding statements
* (pymain_import_readline's `mod`, pymain_run_file_obj's `fp`);
* 3. two declarations in a row where hoisting the first turns its site
* into an assignment statement, which would otherwise make the
* second declaration (immediately following) a new violation too
* (pymain_run_file_obj's `cf`/`run`);
* 4. a declaration inside a nested block (its own scope), after
* statements already executed in that block (pymain_run_stdin's
* nested `exitcode`);
* 5. a platform-conditional declaration with the same name but a
* different type per branch, hoisted under the same #ifdef structure
* at the top of the function (pymain_run_startup's `env`).
*/
#define TARGET_WINDOWS 1
/* --- pattern 1: bare declaration hoisted above an early return --- */
static int
decl_after_early_return(int flag)
{
int status;
int extra;
status = flag ? 0 : -1;
if (status != 0) {
return status;
}
extra = 7;
return extra;
}
/* --- pattern 2: side-effecting-initializer declaration hoisted bare,
* assigned at its original position so the call still happens after the
* same preceding statements --- */
static int
side_effect(int *counter)
{
(*counter)++;
return *counter;
}
static int
decl_with_side_effecting_init(int *counter)
{
int guard;
int value;
guard = (*counter < 0);
if (guard) {
return -1;
}
value = side_effect(counter);
return value;
}
/* --- pattern 3: two declarations in a row, first one hoisted becomes an
* assignment, so the second (immediately following) must hoist too --- */
static int
decl_pair_after_hoist(int *counter)
{
int guard;
int a;
int b;
guard = (*counter < 0);
if (guard) {
return -1;
}
a = side_effect(counter);
b = side_effect(counter);
return a + b;
}
/* --- pattern 4: nested-block declaration after statements already
* executed within that same nested block --- */
static int
decl_after_statement_in_nested_block(int interactive)
{
int outer_result = 0;
if (interactive) {
int inner_value;
outer_result += 1;
outer_result += 2;
inner_value = outer_result * 10;
return inner_value;
}
return outer_result;
}
/* --- pattern 5: platform-conditional declaration, same name, different
* type per branch, hoisted under the same #ifdef at the top --- */
static long
decl_platform_conditional(void)
{
int ret;
#ifdef TARGET_WINDOWS
const char *env;
#else
const long *env;
#endif
ret = 1;
if (!ret) {
return 0;
}
#ifdef TARGET_WINDOWS
env = "PYTHONSTARTUP";
if (env == 0) {
return 0;
}
return (long)env[0];
#else
env = 0;
if (env == 0) {
return 0;
}
return *env;
#endif
}
int
main(void)
{
int counter = 0;
int total = 0;
total += decl_after_early_return(1);
total += decl_with_side_effecting_init(&counter);
total += decl_pair_after_hoist(&counter);
total += decl_after_statement_in_nested_block(1);
total += (int)decl_platform_conditional();
return (total >= 0) ? 0 : 1;
}
+54
View File
@@ -0,0 +1,54 @@
/* Focused compile smoke test for the VC6 C89-mixed-declarations fixes in
* four Include/internal/pycore_*.h headers: pycore_code.h, pycore_dict.h,
* pycore_list.h, and pycore_call.h.
*
* VC6 is a C89-only compiler (plus Microsoft extensions): block-scope
* declarations must precede all statements in that block. CPython 3.11's
* source freely uses C99-style declarations after statements (e.g. after
* an assert()); the fix hoists each such declaration to the top of its
* enclosing block without changing evaluation order or behavior. See
* PORT_STATUS.md ("Mixed-declarations header slice") for full detail.
*
* Unlike scripts/fixtures/vc6_atomic_smoke.c and vc6_stdbool_smoke.c, this
* fixture includes the real "Python.h" (Py_BUILD_CORE is supplied by the
* probe script) rather than isolating the pycore_*.h headers from it:
* these four headers assume types (PyObject, PyCodeObject, PyListObject,
* PyDictValues, vectorcallfunc, ...) that come from the normal public
* header chain, the same way real CPython .c sources reach them.
*/
#include "Python.h"
#include "pycore_code.h"
#include "pycore_dict.h"
#include "pycore_list.h"
#include "pycore_call.h"
int
vc6_locals_kind_smoke(PyObject *kinds, int i, _PyLocals_Kind kind)
{
_PyLocals_SetKind(kinds, i, kind);
return (int)_PyLocals_GetKind(kinds, i);
}
uint16_t
vc6_adaptive_counter_smoke(uint16_t counter)
{
return adaptive_counter_backoff(counter);
}
void
vc6_dict_values_smoke(PyDictValues *values, Py_ssize_t ix)
{
_PyDictValues_AddToInsertionOrder(values, ix);
}
int
vc6_list_append_smoke(PyListObject *self, PyObject *newitem)
{
return _PyList_AppendTakeRef(self, newitem);
}
vectorcallfunc
vc6_vectorcall_smoke(PyObject *callable)
{
return _PyVectorcall_FunctionInline(callable);
}
+31
View File
@@ -0,0 +1,31 @@
/* Focused, isolated compile smoke test for compat/msvc600/stdbool.h under
* VC6.
*
* Deliberately does NOT go through pycore_interp.h or any other pycore_*.h
* header: this isolates the result to the <stdbool.h> shim itself, the
* same way scripts/fixtures/vc6_atomic_smoke.c isolates pycore_atomic.h.
* Modules/main.c (see scripts/vc6-probe-core.test.sh) is the real-header
* reference case that proves compat/msvc600/stdbool.h is actually what
* pycore_interp.h's `#include <stdbool.h>` resolves to via the VC6 probe
* scripts' include path.
*/
#include <stdbool.h>
static bool vc6_stdbool_flag = true;
int
vc6_stdbool_smoke(void)
{
bool a = true;
bool b = false;
bool c = a && !b;
vc6_stdbool_flag = c;
if (!__bool_true_false_are_defined) {
return -1;
}
return (int)(a == true) + (int)(b == false) + (int)(c == true)
+ (int)vc6_stdbool_flag;
}
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
#
# Py_BUILD_CORE compile probe for the Windows 9x port.
#
# scripts/vc6-probe.sh only ever compiles a public-header translation unit
# (Programs/python.c). CPython's internal (`Include/internal/pycore_*.h`)
# headers are a different, stricter surface: every one of them requires
# Py_BUILD_CORE to be defined and Include/internal on the include path, and
# they use compiler features the public headers avoid. This script probes
# that surface directly by compiling Modules/main.c, the first internal
# translation unit CPython's own build reaches, the same way
# scripts/vc6-probe.sh probes the public one: run the external MSVC600
# `cl.exe` under Wine against the CPython 3.11.16 baseline in ./cpython,
# using the project-local compatibility headers in ./compat/msvc600.
# Compile-only (/c): this measures how far the VC6 front end gets, it does
# not attempt to link.
#
# The MSVC600 bundle is an external, local-only prerequisite; nothing under
# MSVC600/ is part of this project's source.
#
# Usage:
# scripts/vc6-probe-core.sh # probe Modules/main.c
# scripts/vc6-probe-core.sh path/to/other.c # probe another core TU
#
# Environment:
# MSVC600_ROOT override the MSVC600 location (default: <repo>/MSVC600)
# WINEPREFIX override the Wine prefix (default: ~/.wine-win9xport)
set -u
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Reuse winpath()/strip_prefix() from vc6-probe.sh instead of duplicating
# them; sourcing it does not run the probe itself (see its own guard).
# shellcheck source=scripts/vc6-probe.sh
source "$REPO_ROOT/scripts/vc6-probe.sh"
run_probe_core() {
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
BUILD_DIR="$REPO_ROOT/build/vc6-probe-core"
SOURCE_REL="${1:-cpython/Modules/main.c}"
SOURCE_ABS="$REPO_ROOT/$SOURCE_REL"
CL="$MSVC600_ROOT/VC98/Bin/CL.EXE"
VCVARS="$MSVC600_ROOT/VC98/Bin/VCVARS32.BAT"
for required in "$CL" "$VCVARS" "$MSVC600_ROOT/VC98/Include" \
"$MSVC600_ROOT/Common/MSDev98/Bin" "$SOURCE_ABS"; do
if [ ! -e "$required" ]; then
echo "vc6-probe-core: missing prerequisite: $required" >&2
return 2
fi
done
if ! command -v wine >/dev/null 2>&1; then
echo "vc6-probe-core: wine is not installed" >&2
return 2
fi
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-win9xport}"
export WINEDEBUG="${WINEDEBUG:--all}"
# CL.EXE loads C1/C2 from VC98/Bin and MSPDB60.DLL from Common/MSDev98/Bin.
export WINEPATH="$(winpath "$MSVC600_ROOT/VC98/Bin");$(winpath "$MSVC600_ROOT/Common/MSDev98/Bin")"
mkdir -p "$BUILD_DIR"
# Include/internal must come before Include so pycore_*.h headers can
# find each other with their own bare #include "pycore_foo.h" style,
# same order CPython's own PC build uses.
INCLUDE_ARGS=(
"$(winpath "$REPO_ROOT/compat/msvc600")"
"$(winpath "$REPO_ROOT/cpython/Include/internal")"
"$(winpath "$REPO_ROOT/cpython/Include")"
"$(winpath "$REPO_ROOT/cpython/PC")"
"$(winpath "$MSVC600_ROOT/VC98/Include")"
)
echo "== MSVC600 Py_BUILD_CORE compile probe =="
echo "source : $SOURCE_REL"
echo "prefix : $WINEPREFIX"
echo
CMD="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && cl /nologo /D Py_BUILD_CORE"
for include_arg in "${INCLUDE_ARGS[@]}"; do
CMD+=" /I $include_arg"
done
CMD+=" /c $(winpath "$SOURCE_ABS") /Fo$(winpath "$BUILD_DIR")\\"
wine cmd /c "$CMD" 2>&1 |
grep -v '^[0-9a-f]\{4\}:err:' |
strip_prefix "$(winpath "$REPO_ROOT")\\"
status="${PIPESTATUS[0]}"
echo
echo "== cl.exe exit status: $status =="
return "$status"
}
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
run_probe_core "$@"
exit "$?"
fi
+337
View File
@@ -0,0 +1,337 @@
#!/usr/bin/env bash
#
# Regression tests for scripts/vc6-probe-core.sh and, through it, the VC6
# _Py_ATOMIC_VC6_X86 branch added to
# cpython/Include/internal/pycore_atomic.h.
#
# Skipped (not failed) when wine + MSVC600 are unavailable, matching
# scripts/vc6-probe.test.sh's convention, since CI/dev boxes without the
# external MSVC600 bundle cannot exercise this.
#
# Usage: scripts/vc6-probe-core.test.sh
set -u
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROBE="$REPO_ROOT/scripts/vc6-probe-core.sh"
fail=0
pass() { echo "ok - $1"; }
bad() { echo "not ok - $1"; fail=1; }
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
# --- positive case: the isolated atomics smoke fixture compiles clean ---
out="$("$PROBE" scripts/fixtures/vc6_atomic_smoke.c 2>&1)"
status=$?
if [ "$status" -eq 0 ]; then
pass "pycore_atomic.h VC6 branch: atomics smoke fixture compiles (exit 0)"
else
bad "pycore_atomic.h VC6 branch: atomics smoke fixture compiles (exit $status)"
echo "$out"
fi
if printf '%s' "$out" | grep -qi 'error'; then
bad "atomics smoke fixture: no compiler errors in output"
else
pass "atomics smoke fixture: no compiler errors in output"
fi
if printf '%s' "$out" | grep -q 'intrin.h'; then
bad "atomics smoke fixture: does not hit the intrin.h blocker"
else
pass "atomics smoke fixture: does not hit the intrin.h blocker"
fi
# --- negative case: a deliberately invalid atomics usage still fails ---
BAD_SRC="$REPO_ROOT/scripts/fixtures/_vc6_atomic_smoke_bad.c"
cat > "$BAD_SRC" <<'EOF'
#include <inttypes.h>
#include "pycore_atomic.h"
int bad(void) { return this_identifier_does_not_exist; }
EOF
trap 'rm -f "$BAD_SRC"' EXIT
out_bad="$("$PROBE" scripts/fixtures/_vc6_atomic_smoke_bad.c 2>&1)"
status_bad=$?
if [ "$status_bad" -ne 0 ]; then
pass "probe-core reports nonzero exit for a broken translation unit"
else
bad "probe-core reports nonzero exit for a broken translation unit (got: $status_bad)"
fi
if printf '%s' "$out_bad" | grep -q 'error C'; then
pass "probe-core output surfaces the compiler diagnostic"
else
bad "probe-core output surfaces the compiler diagnostic"
fi
# --- reference case: Modules/main.c reaches past pycore_atomic.h ---
# main.c is expected to fail today (pycore_interp.h needs <stdbool.h>,
# a separate, unrelated VC6 gap) but must not fail *at*
# pycore_atomic.h/intrin.h; that would mean the atomics branch
# regressed even though the isolated fixture above passed.
out_main="$("$PROBE" cpython/Modules/main.c 2>&1)"
if printf '%s' "$out_main" | grep -q 'pycore_atomic.h.*intrin.h\|intrin.h.*No such file'; then
bad "Modules/main.c probe: does not regress to the pycore_atomic.h/intrin.h blocker"
else
pass "Modules/main.c probe: does not regress to the pycore_atomic.h/intrin.h blocker"
fi
# --- positive case: the isolated stdbool smoke fixture compiles clean ---
out_bool="$("$PROBE" scripts/fixtures/vc6_stdbool_smoke.c 2>&1)"
status_bool=$?
if [ "$status_bool" -eq 0 ]; then
pass "compat/msvc600/stdbool.h: stdbool smoke fixture compiles (exit 0)"
else
bad "compat/msvc600/stdbool.h: stdbool smoke fixture compiles (exit $status_bool)"
echo "$out_bool"
fi
if printf '%s' "$out_bool" | grep -qi 'error'; then
bad "stdbool smoke fixture: no compiler errors in output"
else
pass "stdbool smoke fixture: no compiler errors in output"
fi
# --- negative case: a deliberately invalid bool usage still fails ---
BAD_BOOL_SRC="$REPO_ROOT/scripts/fixtures/_vc6_stdbool_smoke_bad.c"
cat > "$BAD_BOOL_SRC" <<'EOF'
#include <stdbool.h>
int bad(void) { return this_identifier_does_not_exist; }
EOF
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC"' EXIT
out_bool_bad="$("$PROBE" scripts/fixtures/_vc6_stdbool_smoke_bad.c 2>&1)"
status_bool_bad=$?
if [ "$status_bool_bad" -ne 0 ]; then
pass "probe-core reports nonzero exit for a broken stdbool translation unit"
else
bad "probe-core reports nonzero exit for a broken stdbool translation unit (got: $status_bool_bad)"
fi
if printf '%s' "$out_bool_bad" | grep -q 'error C'; then
pass "probe-core output surfaces the compiler diagnostic (stdbool case)"
else
bad "probe-core output surfaces the compiler diagnostic (stdbool case)"
fi
# --- reference case: Modules/main.c reaches past pycore_interp.h's
# <stdbool.h> include. It is expected to still fail today -- the next,
# unrelated blocker is VC6's C89-only parser rejecting C99-style mixed
# declarations-and-code in several pycore_*.h inline functions (e.g.
# Include/internal/pycore_code.h's _PyLocals_GetKind(), which declares
# `char *ptr` after a preceding `assert(...)` statement) -- but it must
# not fail *at* <stdbool.h> anymore; that would mean this slice
# regressed even though the isolated fixture above passed.
if printf '%s' "$out_main" | grep -q "Cannot open include file: 'stdbool.h'"; then
bad "Modules/main.c probe: does not regress to the pycore_interp.h/stdbool.h blocker"
else
pass "Modules/main.c probe: does not regress to the pycore_interp.h/stdbool.h blocker"
fi
# --- positive case: the isolated mixed-declarations smoke fixture
# compiles clean (pycore_code.h, pycore_dict.h, pycore_list.h,
# pycore_call.h all hoisted their C99 mid-block declarations) ---
out_decls="$("$PROBE" scripts/fixtures/vc6_mixed_decls_smoke.c 2>&1)"
status_decls=$?
if [ "$status_decls" -eq 0 ]; then
pass "mixed-declarations header slice: smoke fixture compiles (exit 0)"
else
bad "mixed-declarations header slice: smoke fixture compiles (exit $status_decls)"
echo "$out_decls"
fi
if printf '%s' "$out_decls" | grep -qi 'error'; then
bad "mixed-declarations smoke fixture: no compiler errors in output"
else
pass "mixed-declarations smoke fixture: no compiler errors in output"
fi
# --- negative case: a deliberately invalid mixed-declarations usage
# still fails ---
BAD_DECLS_SRC="$REPO_ROOT/scripts/fixtures/_vc6_mixed_decls_smoke_bad.c"
cat > "$BAD_DECLS_SRC" <<'EOF'
#include "Python.h"
#include "pycore_code.h"
int bad(void) { return this_identifier_does_not_exist; }
EOF
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC" "$BAD_DECLS_SRC"' EXIT
out_decls_bad="$("$PROBE" scripts/fixtures/_vc6_mixed_decls_smoke_bad.c 2>&1)"
status_decls_bad=$?
if [ "$status_decls_bad" -ne 0 ]; then
pass "probe-core reports nonzero exit for a broken mixed-declarations translation unit"
else
bad "probe-core reports nonzero exit for a broken mixed-declarations translation unit (got: $status_decls_bad)"
fi
if printf '%s' "$out_decls_bad" | grep -q 'error C'; then
pass "probe-core output surfaces the compiler diagnostic (mixed-declarations case)"
else
bad "probe-core output surfaces the compiler diagnostic (mixed-declarations case)"
fi
# --- reference case: Modules/main.c no longer fails inside any of the
# four fixed pycore_*.h headers. It is expected to still fail today --
# Modules/main.c itself uses the same C99 mixed-declarations pattern
# pervasively in its own function bodies, a much larger, whole-file
# source rewrite that is out of scope for this header-only slice; see
# PORT_STATUS.md's "Mixed-declarations header slice" section for the
# documented boundary -- but it must not fail *inside*
# pycore_code.h/pycore_dict.h/pycore_list.h/pycore_call.h anymore;
# that would mean this slice regressed even though the isolated
# fixture above passed.
if printf '%s' "$out_main" | grep -qE 'pycore_(code|dict|list|call)\.h\([0-9]+\)'; then
bad "Modules/main.c probe: does not regress to the pycore_code/dict/list/call.h mixed-declarations blocker"
else
pass "Modules/main.c probe: does not regress to the pycore_code/dict/list/call.h mixed-declarations blocker"
fi
# --- positive case: the isolated declaration-order smoke fixture
# compiles clean. It exercises, self-contained, the same five hoist
# shapes applied to Modules/main.c itself (bare hoist above an early
# return, side-effecting-initializer hoist, a hoisted pair where fixing
# the first declaration would otherwise turn the second into a new
# violation, a nested-block hoist, and a platform-conditional same-name
# hoist) so this guards the general transformation, independent of
# main.c's own future edits. ---
out_decl_order="$("$PROBE" scripts/fixtures/vc6_decl_order_smoke.c 2>&1)"
status_decl_order=$?
if [ "$status_decl_order" -eq 0 ]; then
pass "Modules/main.c declaration-order slice: smoke fixture compiles (exit 0)"
else
bad "Modules/main.c declaration-order slice: smoke fixture compiles (exit $status_decl_order)"
echo "$out_decl_order"
fi
if printf '%s' "$out_decl_order" | grep -qi 'error'; then
bad "declaration-order smoke fixture: no compiler errors in output"
else
pass "declaration-order smoke fixture: no compiler errors in output"
fi
# --- negative case: a deliberately invalid declaration-order usage
# still fails ---
BAD_DECL_ORDER_SRC="$REPO_ROOT/scripts/fixtures/_vc6_decl_order_smoke_bad.c"
cat > "$BAD_DECL_ORDER_SRC" <<'EOF'
int bad(void) { return this_identifier_does_not_exist; }
EOF
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC" "$BAD_DECLS_SRC" "$BAD_DECL_ORDER_SRC"' EXIT
out_decl_order_bad="$("$PROBE" scripts/fixtures/_vc6_decl_order_smoke_bad.c 2>&1)"
status_decl_order_bad=$?
if [ "$status_decl_order_bad" -ne 0 ]; then
pass "probe-core reports nonzero exit for a broken declaration-order translation unit"
else
bad "probe-core reports nonzero exit for a broken declaration-order translation unit (got: $status_decl_order_bad)"
fi
if printf '%s' "$out_decl_order_bad" | grep -q 'error C'; then
pass "probe-core output surfaces the compiler diagnostic (declaration-order case)"
else
bad "probe-core output surfaces the compiler diagnostic (declaration-order case)"
fi
# --- reference case: Modules/main.c no longer fails on any C89
# mixed-declaration site anywhere in its own body (the C2146/C2065/
# C2275 "illegal use of this type as an expression" pattern tied to a
# declaration keyword). At the time this check was written, main.c
# still failed with a distinct, out-of-scope blocker instead (C99
# compound literals/designated initializers, see below); that blocker
# is now also fixed (see the compound-literal checks further down),
# but this check's job is narrower and unchanged: main.c must not show
# the declaration-order-specific error codes; that would mean this
# slice regressed even though the isolated fixture above passed.
if printf '%s' "$out_main" | grep -qE "main\.c\([0-9]+\) : error C2146|main\.c\([0-9]+\) : error C2065.*undeclared identifier|main\.c\([0-9]+\) : error C2275"; then
bad "Modules/main.c probe: does not regress to a C89 mixed-declaration error inside main.c's own body"
else
pass "Modules/main.c probe: does not regress to a C89 mixed-declaration error inside main.c's own body"
fi
# --- positive case: the isolated compound-literal/designated-
# initializer smoke fixture compiles clean. It exercises, self-
# contained (aside from the real Python.h/pycore_initconfig.h types
# it needs), the three replacement patterns applied to main.c: calling
# PyStatus_Ok() instead of the _PyStatus_OK() compound-literal macro,
# explicit field assignment instead of the _PyCompilerFlags_INIT
# compound-literal macro, and explicit field assignment instead of a
# _PyArgv designated-initializer aggregate. ---
out_compound="$("$PROBE" scripts/fixtures/vc6_compound_literal_smoke.c 2>&1)"
status_compound=$?
if [ "$status_compound" -eq 0 ]; then
pass "compound-literal slice: smoke fixture compiles (exit 0)"
else
bad "compound-literal slice: smoke fixture compiles (exit $status_compound)"
echo "$out_compound"
fi
if printf '%s' "$out_compound" | grep -qi 'error'; then
bad "compound-literal smoke fixture: no compiler errors in output"
else
pass "compound-literal smoke fixture: no compiler errors in output"
fi
# --- negative case: a deliberately invalid compound-literal-fixture
# usage still fails ---
BAD_COMPOUND_SRC="$REPO_ROOT/scripts/fixtures/_vc6_compound_literal_smoke_bad.c"
cat > "$BAD_COMPOUND_SRC" <<'EOF'
#include "Python.h"
#include "pycore_initconfig.h"
int bad(void) { return this_identifier_does_not_exist; }
EOF
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC" "$BAD_DECLS_SRC" "$BAD_DECL_ORDER_SRC" "$BAD_COMPOUND_SRC"' EXIT
out_compound_bad="$("$PROBE" scripts/fixtures/_vc6_compound_literal_smoke_bad.c 2>&1)"
status_compound_bad=$?
if [ "$status_compound_bad" -ne 0 ]; then
pass "probe-core reports nonzero exit for a broken compound-literal translation unit"
else
bad "probe-core reports nonzero exit for a broken compound-literal translation unit (got: $status_compound_bad)"
fi
if printf '%s' "$out_compound_bad" | grep -q 'error C'; then
pass "probe-core output surfaces the compiler diagnostic (compound-literal case)"
else
bad "probe-core output surfaces the compiler diagnostic (compound-literal case)"
fi
# --- reference case: Modules/main.c itself no longer shows any C2059
# "syntax error : '{'"/"syntax error : '.'" (the compound-literal/
# designated-initializer error signature), and -- since this was the
# last recorded blocker for main.c as a compile-only (/c) translation
# unit -- now compiles with exit status 0. This is a compile-only
# result: no link or runtime success is claimed (vc6-probe-core.sh is
# /c only; see its header comment). ---
if printf '%s' "$out_main" | grep -q 'main\.c([0-9]*) : error C2059'; then
bad "Modules/main.c probe: does not regress to the compound-literal/designated-initializer C2059 blocker"
else
pass "Modules/main.c probe: does not regress to the compound-literal/designated-initializer C2059 blocker"
fi
out_main_status="$("$PROBE" cpython/Modules/main.c >/dev/null 2>&1; echo $?)"
if [ "$out_main_status" -eq 0 ]; then
pass "Modules/main.c probe: compiles clean end-to-end (compile-only, exit 0)"
else
bad "Modules/main.c probe: compiles clean end-to-end (compile-only, got exit $out_main_status)"
fi
else
echo "skip - wine/MSVC600 not available; skipping live probe-core integration checks"
fi
if [ "$fail" -ne 0 ]; then
echo "FAILED"
exit 1
fi
echo "ALL TESTS PASSED"
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
#
# Minimal reproducible MSVC 6.0 compile probe for the Windows 9x port.
#
# Runs the external MSVC600 `cl.exe` under Wine against the CPython 3.11.16
# baseline in ./cpython, using the project-local compatibility headers in
# ./compat/msvc600. Compile-only (/c): this probe measures how far the VC6
# front end gets through the public headers, it does not attempt to link.
#
# The MSVC600 bundle is an external, local-only prerequisite; nothing under
# MSVC600/ is part of this project's source.
#
# Usage:
# scripts/vc6-probe.sh # probe Programs/python.c
# scripts/vc6-probe.sh path/to/other.c # probe another translation unit
#
# Environment:
# MSVC600_ROOT override the MSVC600 location (default: <repo>/MSVC600)
# WINEPREFIX override the Wine prefix (default: ~/.wine-win9xport)
set -u
# Absolute POSIX path -> Wine drive Z: path.
winpath() { printf 'Z:%s' "${1//\//\\}"; }
# Strip a literal (non-regex) prefix from every line of stdin.
#
# sed and bash glob patterns both treat backslashes in the *pattern* as
# escape characters, so passing a Windows path (full of literal `\`
# separators) as a sed/glob pattern silently fails to match: `\h`, `\u`,
# etc. are consumed as (mostly no-op) escapes instead of literal
# characters. Quoting the parameter inside a bash `${var//pattern/}`
# expansion forces literal (non-glob) matching, which sidesteps the
# problem entirely.
strip_prefix() {
local prefix="$1" line
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "${line//"$prefix"/}"
done
}
run_probe() {
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
BUILD_DIR="$REPO_ROOT/build/vc6-probe"
SOURCE_REL="${1:-cpython/Programs/python.c}"
SOURCE_ABS="$REPO_ROOT/$SOURCE_REL"
CL="$MSVC600_ROOT/VC98/Bin/CL.EXE"
VCVARS="$MSVC600_ROOT/VC98/Bin/VCVARS32.BAT"
for required in "$CL" "$VCVARS" "$MSVC600_ROOT/VC98/Include" \
"$MSVC600_ROOT/Common/MSDev98/Bin" "$SOURCE_ABS"; do
if [ ! -e "$required" ]; then
echo "vc6-probe: missing prerequisite: $required" >&2
return 2
fi
done
if ! command -v wine >/dev/null 2>&1; then
echo "vc6-probe: wine is not installed" >&2
return 2
fi
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-win9xport}"
export WINEDEBUG="${WINEDEBUG:--all}"
# CL.EXE loads C1/C2 from VC98/Bin and MSPDB60.DLL from Common/MSDev98/Bin.
export WINEPATH="$(winpath "$MSVC600_ROOT/VC98/Bin");$(winpath "$MSVC600_ROOT/Common/MSDev98/Bin")"
mkdir -p "$BUILD_DIR"
INCLUDE_ARGS=(
"$(winpath "$REPO_ROOT/compat/msvc600")"
"$(winpath "$REPO_ROOT/cpython/Include")"
"$(winpath "$REPO_ROOT/cpython/PC")"
"$(winpath "$MSVC600_ROOT/VC98/Include")"
)
echo "== MSVC600 compile probe =="
echo "source : $SOURCE_REL"
echo "prefix : $WINEPREFIX"
echo
CMD="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && cl /nologo"
for include_arg in "${INCLUDE_ARGS[@]}"; do
CMD+=" /I $include_arg"
done
CMD+=" /c $(winpath "$SOURCE_ABS") /Fo$(winpath "$BUILD_DIR")\\"
wine cmd /c "$CMD" 2>&1 |
grep -v '^[0-9a-f]\{4\}:err:' |
strip_prefix "$(winpath "$REPO_ROOT")\\"
status="${PIPESTATUS[0]}"
echo
echo "== cl.exe exit status: $status =="
return "$status"
}
# Allow `source scripts/vc6-probe.sh` (e.g. from tests) to pull in
# winpath/strip_prefix without running the probe itself.
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
run_probe "$@"
exit "$?"
fi
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
#
# Regression tests for scripts/vc6-probe.sh.
#
# Covers the two failure modes that made the probe's exit status and
# output untrustworthy:
# 1. strip_prefix() must remove a literal Windows-style path prefix
# (backslash-separated) from probe output. A naive sed/glob
# pattern silently no-ops on this because `\h`, `\u`, etc. are
# consumed as escapes instead of literal characters.
# 2. The reported exit status must be cl.exe's real status even
# though its output passes through `grep` and `strip_prefix` in a
# pipeline.
#
# Part 2 additionally runs the real probe against a deliberately
# broken translation unit when wine + MSVC600 are available, so the
# check exercises the actual pipeline, not just the helper functions.
#
# Usage: scripts/vc6-probe.test.sh
set -u
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROBE="$REPO_ROOT/scripts/vc6-probe.sh"
fail=0
pass() { echo "ok - $1"; }
bad() { echo "not ok - $1"; fail=1; }
# --- unit test: strip_prefix removes a literal backslash-heavy prefix ---
# shellcheck source=scripts/vc6-probe.sh
source "$PROBE"
prefix='Z:\home\ubuntu\projects\python-win9x\'
input="Z:\\home\\ubuntu\\projects\\python-win9x\\cpython\\Programs\\python.c(1) : error C2061"
expected="cpython\\Programs\\python.c(1) : error C2061"
actual="$(printf '%s\n' "$input" | strip_prefix "$prefix")"
if [ "$actual" = "$expected" ]; then
pass "strip_prefix removes literal backslash-separated prefix"
else
bad "strip_prefix removes literal backslash-separated prefix (got: [$actual], want: [$expected])"
fi
# A line that does not start with the prefix must be passed through
# unchanged (no partial/garbled matches).
input2="unrelated line with no prefix"
actual2="$(printf '%s\n' "$input2" | strip_prefix "$prefix")"
if [ "$actual2" = "$input2" ]; then
pass "strip_prefix leaves non-matching lines untouched"
else
bad "strip_prefix leaves non-matching lines untouched (got: [$actual2])"
fi
# --- unit test: pipeline exit status reflects the first command, not
# grep/sed-equivalent downstream stages ---
fake_failure() { return 7; }
fake_failure 2>&1 | grep -v '^nomatch' | strip_prefix "$prefix" >/dev/null
status="${PIPESTATUS[0]}"
if [ "$status" -eq 7 ]; then
pass "PIPESTATUS[0] reports the producing command's exit status through the filter pipeline"
else
bad "PIPESTATUS[0] reports the producing command's exit status through the filter pipeline (got: $status)"
fi
# --- integration test: run the real probe against a broken translation
# unit, when the toolchain is actually available. Skipped (not
# failed) otherwise, since CI/dev boxes without wine+MSVC600
# cannot exercise this. ---
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
BAD_SRC="$REPO_ROOT/cpython/Programs/_vc6_probe_test_bad.c"
printf 'this is not valid C !!! ###\n' > "$BAD_SRC"
trap 'rm -f "$BAD_SRC"' EXIT
out="$("$PROBE" cpython/Programs/_vc6_probe_test_bad.c 2>&1)"
real_status=$?
if [ "$real_status" -ne 0 ]; then
pass "probe reports nonzero exit for a broken translation unit"
else
bad "probe reports nonzero exit for a broken translation unit (got: $real_status)"
fi
if printf '%s' "$out" | grep -q 'error C'; then
pass "probe output surfaces the compiler diagnostic"
else
bad "probe output surfaces the compiler diagnostic"
fi
if printf '%s' "$out" | grep -q 'Z:\\home'; then
bad "probe output leaks an unstripped absolute Wine path"
else
pass "probe output does not leak an unstripped absolute Wine path"
fi
else
echo "skip - wine/MSVC600 not available; skipping live probe integration check"
fi
if [ "$fail" -ne 0 ]; then
echo "FAILED"
exit 1
fi
echo "ALL TESTS PASSED"