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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Executable
+104
@@ -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
|
||||
Executable
+337
@@ -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"
|
||||
Executable
+107
@@ -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
|
||||
Executable
+107
@@ -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"
|
||||
Reference in New Issue
Block a user