majit: fuse boxing clusters split across blocks, and build ctypes on Windows - #1135
Conversation
WalkthroughThe interpreter now supports additional Windows host-environment functionality. Changes cover ChangesWindows portability
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d839b1aaad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "LoadLibrary: name must be a string or bytes", | ||
| )); | ||
| }; | ||
| let handle = host_ctypes::open_library(&name).map_err(|e| { |
There was a problem hiding this comment.
Honor the Windows DLL search flags
On Windows, ctypes.CDLL._load_library passes its computed winmode as the second argument to _ctypes.LoadLibrary (lib-python/3/ctypes/__init__.py:435-451), but this implementation reads only args[0] and calls the flagless loader. Consequently winmode=0, LOAD_LIBRARY_SEARCH_SYSTEM32, and LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR all behave identically, defeating callers that use these flags to constrain DLL and dependency lookup and potentially loading a different DLL than requested. Pass and enforce args[1] through the Windows loader.
Useful? React with 👍 / 👎.
| let mut buffer = [0u16; MAX_PATH]; | ||
| let length = host_winapi::get_module_file_name(module, &mut buffer); |
There was a problem hiding this comment.
Retry GetModuleFileName with a larger buffer
When pyre is installed or launched from a Windows path longer than 259 UTF-16 units, this fixed buffer truncates the module path and the code then returns that truncated value as success. Callers such as sysconfig._init_non_posix (lib-python/3/sysconfig/__init__.py:420) use the result to derive the installation prefix, so imports and configuration paths can point at a nonexistent directory. Detect a full buffer and retry with a larger allocation instead of forcibly terminating the truncated result.
Useful? React with 👍 / 👎.
| if incoming.any(|other| other.as_ref() != Some(&source)) || source == current { | ||
| break; |
There was a problem hiding this comment.
Trace equivalent SSA families through joins
When a value crosses a diamond, each arm normally has a distinct block-input variable even if both ultimately originate from the same producer. At the join this direct equality test sees the two arm variables as different and stops, so fuse_boxing_alloc misses an unmerged boxing value despite DataFlowFamilyBuilder.complete() recursively unifying both arm variables with their common origin. Resolve each incoming variable to its family/source before deciding that the join is a genuine merge.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 6757955). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patchNone. 2. Other mismatches introduced by this patch
3. Pre-existing mismatches (already present before this patch)
4. Structural adaptations
|
`_ctypes`'s submodules and its functional registration were gated on `all(unix, feature = "host_env")`, so Windows got a placeholder namespace in which every name is `object` and `import ctypes` stopped at `from _ctypes import __version__`. `cdata`, `metaclass` and `stginfo` carry no `libc` and no `cfg(unix)`, `funcptr` has one `libc::wchar_t` that `libc` also spells on Windows, and `rustpython_host_env::ctypes` already carries the Windows half of the loader, the ctypes-local last error, and the COM helpers. Gate the module on `any(unix, windows)` instead; wasm32 keeps the placeholder. Split the loader surface the way `_ctypes.c`'s `#ifdef MS_WIN32` does: `dlopen`/`dlsym`/`dlclose` stay posix, and Windows gets `LoadLibrary`, `FreeLibrary`, `FormatError`, `CopyComPointer`, `_check_HRESULT`, `COMError`, `get_last_error`/`set_last_error` and `FUNCFLAG_STDCALL`/`FUNCFLAG_HRESULT`. `RTLD_LOCAL`/`RTLD_GLOBAL` are registered on both platforms — 0 where there is no `dlfcn.h` — because `ctypes/__init__.py:14` imports them before it branches on `os.name`. `make_exc_type_with_init` becomes `pub(crate)` for `COMError`. The import then reached two more Windows-only gaps: `sys.dllhandle`, which `ctypes/__init__.py:562` builds `pythonapi` out of, and `_winapi.GetModuleFileName`, which `sysconfig._init_non_posix` calls on it. `dllhandle` is `GetModuleHandleW(NULL)`, there being no separate interpreter DLL here. `extra_tests/parity_tests/type_new_metatype_guard.py` imports `ctypes`, so it now passes on Windows; `test.test_ctypes` runs 306 cases there (13 failures, 24 errors, 46 skipped) where it previously stopped at the first import. unix and wasm32 registration is unchanged.
`LoadLibrary` read only args[0] and called the flagless loader, so the `winmode` every `CDLL` computes was discarded. It now parses "U|i" and calls `LoadLibraryExW(name, NULL, load_flags)`, and answers ERROR_MOD_NOT_FOUND with the FileNotFoundError naming the module rather than a bare winerror OSError. Reaching that flags argument means leaving `rustpython_host_env::ctypes`: its Windows door is `libloading::Library::new`, which has no flags parameter, and its raw-handle door is unix-only. The Windows handle is now the HMODULE itself, `FreeLibrary` is the Win32 call on it, and symbol resolution goes through a new `lookup_symbol` so funcptr.rs and cdata.rs stay platform-agnostic. sys.dllhandle is 0, which `get_dllhandle` (vm.py:301) answers without cpyext; `sysconfig` feeds it to `_winapi.GetModuleFileName`, where handle 0 names the executable. `audit_and_parsercreate_name_argument` expects OSError from a non-escape surrogate on win32: `LoadLibraryExW` takes the wide spelling, so a lone surrogate reaches the loader instead of being refused by an encode.
Four Windows entry points that answered from a placeholder or from a hand-rolled binder: - execv/execve were no-op stubs that returned None for any arguments, including a bytearray path. They now convert path, argv and env and call `_wexecv`/`_wexecve`, which return only on failure. - access took two positionals and nothing else, so `os.access(path=, mode=)` raised. It now binds through bind_path_args and refuses dir_fd, effective_ids=True and follow_symlinks=False the way a build without faccessat does. - symlink read args[0..3] straight off the slice, so `symlink(src, dst, target_is_directory, dir_fd)` created the link and returned None, and `dst=` was an unexpected keyword. - get_terminal_size refused a keyword against `posix.get_terminal_size`; this module answers to `nt` on Windows. argument_unavailable loses its unix gate; its text names no platform.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6757955239
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // ── get_last_error / set_last_error — the ctypes-local copy, which is | ||
| // separate from the thread's own Win32 last error. The setter answers | ||
| // with the value it replaced, the same contract `set_errno` above | ||
| // carries and the one the documented signature promises. ── |
There was a problem hiding this comment.
Capture last error for flagged foreign calls
On Windows, CDLL(..., use_last_error=True) sets _FUNCFLAG_USE_LASTERROR, but the newly enabled funcptr::cfuncptr_call only translates FUNCFLAG_USE_ERRNO into CallOptions and leaves every other option at its default. Consequently a native call never swaps or captures the Win32 last-error value, so the newly exported get_last_error() returns a stale value precisely for callers that requested this behavior. Honor FUNCFLAG_USE_LASTERROR around the foreign call.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
| crate::module_ns_store( | ||
| ns, | ||
| "_check_HRESULT", |
There was a problem hiding this comment.
Run the HRESULT restype checker
On Windows, ctypes.HRESULT installs this function as _check_retval_ (lib-python/3/ctypes/__init__.py:514-525), and OleDLL relies on that hook to turn failing HRESULTs into OSError. However, funcptr::cfuncptr_call decodes every Ret::Code result and returns it directly without invoking the restype's _check_retval_, so failing ctypes.oledll calls silently return negative integers instead of raising. Wire this checker into result conversion rather than merely exporting it.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
| for key_obj in keys { | ||
| let value_obj = crate::baseobjspace::getitem(args[2], key_obj)?; | ||
| let key = extract_path(key_obj)?; |
There was a problem hiding this comment.
Root environment keys across mapping callbacks
When env is a custom mapping whose __getitem__ allocates or otherwise triggers collection, this loop keeps the mapping, all keys, and the returned value only in raw PyObjectRef locals while calling back into Python; a moving collection can therefore leave key_obj, value_obj, and the remaining keys entries stale before extract_path uses them. The analogous _winapi::environment_block path explicitly pins the mapping and keys and re-reads them after each callback (module/_winapi/mod.rs:203-222); the new Windows execve path needs the same rooting discipline.
Useful? React with 👍 / 👎.
| crate::baseobjspace::setattr_str( | ||
| w_self, | ||
| "args", | ||
| pyre_object::tupleobject::w_tuple_new(vec![*text, *details]), |
There was a problem hiding this comment.
Preserve hresult in COMError.args
For every constructed COMError, this overwrites the base exception's argument tuple with only (text, details), so str(exc), exception serialization, and code inspecting exc.args lose the HRESULT. PyPy's corresponding lib_pypy/_ctypes/basics.py:48-52 stores (hresult, text, details), matching the constructor's three arguments; keep all three values in args.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs`:
- Around line 9116-9119: Update both exec call sites in
pyre/pyre-interpreter/src/module/posix/interp_posix.rs:9116-9119 and 9167-9170,
covering wexecv and wexecve, to invoke the wide C runtime functions through
crate::builtins::crt_call! and construct failures with
errno_err(crate::builtins::crt_errno(), ""). Replace the current
std::io::Error::last_os_error()/io_err handling at both sites; no other changes
are required.
- Around line 9344-9365: Move the dir_fd_kwarg validation in the symlink entry
point to after both fsencode_path_named_w conversions for src and dst. Preserve
the existing arguments and error handling so path conversions, including
user-defined __fspath__, occur before rejecting dir_fd, matching the ordering
used by access and stat_entry.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b4a3b189-4cbe-473a-95a2-92810c2a30ed
📒 Files selected for processing (8)
pyre/pyre-interpreter/src/builtins.rspyre/pyre-interpreter/src/module/_ctypes/cdata.rspyre/pyre-interpreter/src/module/_ctypes/funcptr.rspyre/pyre-interpreter/src/module/_ctypes/interp_ctypes.rspyre/pyre-interpreter/src/module/_ctypes/mod.rspyre/pyre-interpreter/src/module/_winapi/mod.rspyre/pyre-interpreter/src/module/posix/interp_posix.rspyre/pyre-interpreter/src/module/sys/vm.rs
| unsafe { libc::wexecv(command_w.as_ptr(), argv_ptrs.as_ptr()) }; | ||
| // `wrap_oserror` names no file, so the path stays out of | ||
| // the error. | ||
| Err(io_err(std::io::Error::last_os_error(), "")) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
execv and execve read the wrong error source after a failed C runtime exec. Both call sites invoke a wide C runtime exec function and then build the OSError from std::io::Error::last_os_error(). On Windows that reads GetLastError(), but _wexecv and _wexecve report failure through the C runtime errno. io_err then maps a Win32 code as though it were a POSIX errno, so a failed exec raises an OSError with an unrelated code. The shared fix is to route both calls through crate::builtins::crt_call! and read crate::builtins::crt_errno(), which is the convention the rest of this file already uses for C runtime calls.
pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9116-L9119: wrap thelibc::wexecvcall incrt_call!and build the error witherrno_err(crate::builtins::crt_errno(), "").pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9167-L9170: wrap thelibc::wexecvecall incrt_call!and build the error witherrno_err(crate::builtins::crt_errno(), "").
📍 Affects 1 file
pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9116-L9119(this comment)pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9167-L9170
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 9116 -
9119, Update both exec call sites in
pyre/pyre-interpreter/src/module/posix/interp_posix.rs:9116-9119 and 9167-9170,
covering wexecv and wexecve, to invoke the wide C runtime functions through
crate::builtins::crt_call! and construct failures with
errno_err(crate::builtins::crt_errno(), ""). Replace the current
std::io::Error::last_os_error()/io_err handling at both sites; no other changes
are required.
| let (bound, kwargs) = bind_path_args( | ||
| args, | ||
| "symlink", | ||
| &["src", "dst", "target_is_directory"], | ||
| 2, | ||
| &["dir_fd"], | ||
| )?; | ||
| if crate::builtins::kwarg_get(kwargs, "dir_fd") | ||
| .is_some_and(|w| !unsafe { pyre_object::is_none(w) }) | ||
| { | ||
| return Err(dir_fd_unavailable()); | ||
| } | ||
| if args.len() < 2 { | ||
| return Err(crate::PyError::type_error("symlink() requires 2 arguments")); | ||
| } | ||
| let src = crate::gateway::fsencode_path_named_w(args[0], "symlink", "src")?; | ||
| let dst = crate::gateway::fsencode_path_named_w(args[1], "symlink", "dst")?; | ||
| let target_is_directory = match args | ||
| .get(2) | ||
| .copied() | ||
| .or_else(|| crate::builtins::kwarg_get(kwargs, "target_is_directory")) | ||
| { | ||
| // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`, | ||
| // and `CreateSymbolicLinkW` resolves a relative name against | ||
| // the working directory alone. | ||
| dir_fd_kwarg(kwargs, false)?; | ||
| let src = crate::gateway::fsencode_path_named_w( | ||
| bound[0].expect("src is required"), | ||
| "symlink", | ||
| "src", | ||
| )?; | ||
| let dst = crate::gateway::fsencode_path_named_w( | ||
| bound[1].expect("dst is required"), | ||
| "symlink", | ||
| "dst", | ||
| )?; | ||
| let target_is_directory = match bound[2] { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Convert src and dst before you reject dir_fd.
dir_fd is keyword-only and is declared last. The conversions run in declaration order, and each conversion can raise and can run user code through __fspath__. Here dir_fd_kwarg runs first, so os.symlink(bad_path_object, dst, dir_fd=3) reports the dir_fd platform error instead of the src conversion error.
The access entry point added in this same change states this rule at Line 9023 and follows it: it converts path and mode, then calls dir_fd_kwarg. stat_entry follows the same order at Line 3832 and Line 3836. Move the dir_fd_kwarg call after both path conversions so symlink matches.
🐛 Proposed fix for the conversion order
- // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
- // and `CreateSymbolicLinkW` resolves a relative name against
- // the working directory alone.
- dir_fd_kwarg(kwargs, false)?;
let src = crate::gateway::fsencode_path_named_w(
bound[0].expect("src is required"),
"symlink",
"src",
)?;
let dst = crate::gateway::fsencode_path_named_w(
bound[1].expect("dst is required"),
"symlink",
"dst",
)?;
+ // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
+ // and `CreateSymbolicLinkW` resolves a relative name against
+ // the working directory alone.
+ dir_fd_kwarg(kwargs, false)?;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let (bound, kwargs) = bind_path_args( | |
| args, | |
| "symlink", | |
| &["src", "dst", "target_is_directory"], | |
| 2, | |
| &["dir_fd"], | |
| )?; | |
| if crate::builtins::kwarg_get(kwargs, "dir_fd") | |
| .is_some_and(|w| !unsafe { pyre_object::is_none(w) }) | |
| { | |
| return Err(dir_fd_unavailable()); | |
| } | |
| if args.len() < 2 { | |
| return Err(crate::PyError::type_error("symlink() requires 2 arguments")); | |
| } | |
| let src = crate::gateway::fsencode_path_named_w(args[0], "symlink", "src")?; | |
| let dst = crate::gateway::fsencode_path_named_w(args[1], "symlink", "dst")?; | |
| let target_is_directory = match args | |
| .get(2) | |
| .copied() | |
| .or_else(|| crate::builtins::kwarg_get(kwargs, "target_is_directory")) | |
| { | |
| // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`, | |
| // and `CreateSymbolicLinkW` resolves a relative name against | |
| // the working directory alone. | |
| dir_fd_kwarg(kwargs, false)?; | |
| let src = crate::gateway::fsencode_path_named_w( | |
| bound[0].expect("src is required"), | |
| "symlink", | |
| "src", | |
| )?; | |
| let dst = crate::gateway::fsencode_path_named_w( | |
| bound[1].expect("dst is required"), | |
| "symlink", | |
| "dst", | |
| )?; | |
| let target_is_directory = match bound[2] { | |
| let (bound, kwargs) = bind_path_args( | |
| args, | |
| "symlink", | |
| &["src", "dst", "target_is_directory"], | |
| 2, | |
| &["dir_fd"], | |
| )?; | |
| let src = crate::gateway::fsencode_path_named_w( | |
| bound[0].expect("src is required"), | |
| "symlink", | |
| "src", | |
| )?; | |
| let dst = crate::gateway::fsencode_path_named_w( | |
| bound[1].expect("dst is required"), | |
| "symlink", | |
| "dst", | |
| )?; | |
| // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`, | |
| // and `CreateSymbolicLinkW` resolves a relative name against | |
| // the working directory alone. | |
| dir_fd_kwarg(kwargs, false)?; | |
| let target_is_directory = match bound[2] { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 9344 -
9365, Move the dir_fd_kwarg validation in the symlink entry point to after both
fsencode_path_named_w conversions for src and dst. Preserve the existing
arguments and error handling so path conversions, including user-defined
__fspath__, occur before rejecting dir_fd, matching the ordering used by access
and stat_entry.
Two Windows CI failures: the one main is red on today, and the one waiting behind it.
1.
synth/list_pop_append— the perf gate every runner was missingfuse_boxing_allocrewrites amalloc_typed(W_XObject { ob_header: PyObject { ob_type: &TYPE, .. }, .. })cluster into aNewWithVtableplus payload stores, which is whatjtransform.py:1012-1046 rewrite_op_mallocproduces for a structheaptracker.py:18-30 get_vtable_for_gcstructcan give a vtable. Upstream does no dataflow to get there — theSTRUCTis the malloc's own constant type argument. Here the boxing is spelled as a struct literal handed tomalloc_typed, so the type pointer is a value in the graph, and recovering it means following that value.The pass followed it through operations only. Every boxing constructor is split into blocks by the calls between the
&TYPEread and themalloc_typed—get_instantiate, andgc_interp::enabledfor the numeric boxes — so the aggregate and the header's type pointer reach their uses as block inputargs with no producing operation. The three lookups that key on them all missed, and the pass declined every cluster: 0 of 139 Size descrs in the build carried a vtable.The fix. Resolve a block inputarg back to its incoming variable when every predecessor passes the same one at that column; stop at a genuine merge, and stop at a link carrying a constant. That is
ssa.py:4-10 DataFlowFamilyBuilder's rule — a family holds the one variable a value is stored into plus every variable it is "just passed unmerged into the next block" through — with the constant case matching the separateopportunities_with_constlist upstream keeps those in (ssa.py:28-33). Stopping early leaves the cluster unfused, which is the outcomejtransform.py:1039-1041already gives a struct with no vtable. Same shape asthread_undefined_op_operandsfurther down the file: a hand-rolled walk on majit's lowered graph, because the faithfulDataFlowFamilyBuilderport intranslator/backendopt/ssa.rsis written against the flowspace graph, not this IR.Effect. Size descrs carrying a vtable: 0 → 8.
w_int_newnow emitsnew_with_vtable+setfield_gc_i+ref_returnwhere it emitted aSyntheticTransparentCtorper allocation — each carrying asymbolic_fnaddrhash the sub-walk cannot record, which rolled thelist.pop()fold back on every iteration.All against a 22x gate. Locally it goes 122.5x → 6.0x dynasm (3.83s → 0.22s), 5.5x cranelift, 36.0x wasm. Probe timings with the empty loop subtracted:
pop208ns/op → 41.6ns/op; theappend+poppair 898ms → 136ms over 5M iterations.The five existing
fuse_boxing_alloctests all build single-block graphs, which is how the pass could go dead in production without one going red. Addedfuse_boxing_alloc_follows_a_cluster_split_across_blocks, which threads the header and the aggregate across two block boundaries; neutralising the walk fails it and leaves the other five green.2.
import ctypes— the failure hidden behind itRun pyre/extra_tests/parity_testsis the step afterRun pyre/check.pyin the same job, so while check.py was red it never ran.type_new_metatype_guard.pyimportsctypes, and on Windows that raisedImportError: cannot import name '__version__' from '_ctypes'. Fixing check.py would have turned this step red instead._ctypes's submodules and its functional registration were gated onall(unix, feature = "host_env"), so Windows got a placeholder namespace in which every name isobject. That gate is far wider than the platform-specific code it guards:cdata,metaclassandstginfocarry nolibcand nocfg(unix)at all,funcptrhas a singlelibc::wchar_tthatlibcalso spells on Windows, andrustpython_host_env::ctypesalready carries the Windows half of the loader (libloading), the ctypes-local last error, and the COM helpers. Gated onany(unix, windows)instead; wasm32 keeps the placeholder, since it has no dynamic loader.The loader surface is split the way
_ctypes.c's#ifdef MS_WIN32splits it —dlopen/dlsym/dlclosestay posix, Windows getsLoadLibrary,FreeLibrary,FormatError,CopyComPointer,_check_HRESULT,COMError,get_last_error/set_last_errorandFUNCFLAG_STDCALL/FUNCFLAG_HRESULT.RTLD_LOCAL/RTLD_GLOBALgo on both platforms (0 where there is nodlfcn.h) becausectypes/__init__.py:14imports them before it branches onos.name.The import then reached two more Windows-only gaps:
sys.dllhandle, whichctypes/__init__.py:562buildspythonapiout of with no guard, and_winapi.GetModuleFileName, whichsysconfig._init_non_posixcalls on it.dllhandleisGetModuleHandleW(NULL)— there is no separate interpreter DLL here, so the module hosting it is the executable, which makes a lookup throughpythonapireport the symbol it could not find rather than fail on the handle.It is a working loader, not just an importable namespace:
test.test_ctypesnow runs 306 cases on Windows (13 failures, 24 errors, 46 skipped) where it previously stopped at the first import. Those remaining failures are ctypes features missing on every platform — the buffer protocol over cdata instances,__pointer_type__/type flags — not Windows gaps, andtest.test_ctypesis a non-PASS baseline entry that no gate runs. unix and wasm32 registration is byte-for-byte unchanged.Verification
Windows host, release, LLBC re-extracted after the last source edit (the interpreter change trips the fingerprint legitimately):
pyre/check.py— dynasm 413/413, cranelift 413/413, wasm 409/409; the vendored CPython suite gate skips here, its baseline being darwin-arm64pyre/extra_tests/parity_tests/run.py— every fixture OK on both backends. The one red isbuiltin_module_loader_spec.py [cpython]: this box's CPython 3.14 has notestpackage, which the runners' setup-python doescargo test -p majit-translate— 3165 lib + 63 integration, 0 failedcargo check -p pyre-wasm --target wasm32-unknown-unknownclean, confirming wasm32 stayed on the placeholderThe macOS-only
CPython suite (gate)reds (test_re,test_pickletools) are untouched and their cause is still unknown.Summary by CodeRabbit
New Features
_ctypeson Windows, including dynamic library loading, symbol lookup, and COM-related functionality._winapi.GetModuleFileName.execvandexecve.sys.dllhandleon Windows.Bug Fixes