port: add MSVC600 CPython compatibility layer
This commit is contained in:
+869
-23
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user