Skip to content

feat(platform): add full Windows x86_64 PE32+ cross-compilation support - #446

Draft
Guikingone wants to merge 2 commits into
illegalstudio:mainfrom
Guikingone:feat/windows-pe
Draft

feat(platform): add full Windows x86_64 PE32+ cross-compilation support#446
Guikingone wants to merge 2 commits into
illegalstudio:mainfrom
Guikingone:feat/windows-pe

Conversation

@Guikingone

@Guikingone Guikingone commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Closes #379.

Summary

This pull request adds an experimental windows-x86_64 backend that produces
standalone PE32+ executables and brings the compiler, runtime, bridge crates,
Magician/eval, TLS, and web mode onto native Windows validation paths.

The implementation uses the GNU/MinGW Windows ABI. The accepted
x86_64-pc-windows-msvc spelling is a CLI compatibility alias only; it does not
claim MSVC ABI output. The branch is rebased on the current main and remains
one squashed commit. Windows stays experimental until every strict gate below
is green on the same revision.

What is included

Target, PE/COFF, and toolchain support

  • Adds windows-x86_64 / x86_64-pc-windows-gnu target selection.
  • Emits and links x86-64 PE/COFF executables through MinGW-w64.
  • Supports an opt-in Clang integrated-assembler + LLD path while retaining the
    GNU Windows ABI.
  • Emits PE startup/runtime data for argc/argv, process exit, exceptions,
    callbacks, fibers, refcounted values, unwind records, and loader mitigations.
  • Runs the compiler pipeline on a dedicated 64 MiB Windows worker stack.
  • Cross-builds and discovers GNU/COFF bridge archives even when the compiler is
    running under Rust's native MSVC host toolchain.
  • Keeps Control Flow Guard disabled until indirect-call instrumentation, GFIDS,
    and the PE load-configuration directory can be emitted as one validated unit.

Windows ABI and runtime

  • Centralizes the boundary between elephc's internal runtime-helper convention
    and Microsoft x64 ABI imports, including shadow space, integer/floating
    register classes, stack arguments, variadics, callbacks, and indirect calls.
  • Adds Win32/CRT/Winsock implementations for memory, Unicode filesystem paths,
    metadata, directory iteration, streams, processes, clocks, entropy, sockets,
    DNS, synchronization, and console/file I/O.
  • Implements PHP-compatible Windows realpath/stat/lstat/fstat
    metadata, readonly/link/executable modes, reparse-point probing, dangling
    symlinks, tempnam, tmpfile, loopback socketpair, stream_isatty, and
    stream timeout behavior.
  • Opens canonicalization handles with compatible sharing, follows symbolic
    links through GetFinalPathNameByHandleW, materializes all seven
    CreateFileW arguments in their exact MSx64 slots (including directory
    backup semantics), and avoids issuing reparse-point control requests for
    ordinary files and directories.
  • Matches PHP's tempnam() contract for prefix basenames, Windows path
    separators, the 63-byte prefix limit, and the false failure sentinel.
  • Keeps chown()/chgrp() visible with Windows failure semantics while
    omitting lchown()/lchgrp() exactly as php-src does without HAVE_LCHOWN.
    Direct calls, namespace fallback, user redeclarations, first-class callables,
    dynamic dispatch, Reflection, eval, function_exists(), and is_callable()
    all use the same target-aware catalog.
  • Supports PHP's full fopen base-mode and suffix surface, Windows
    temporary-file deletion, complete 64-bit Winsock handles, and the correct
    close order for filters, TLS sessions, stream state, CRT descriptors, and
    sockets.
  • Preserves stream payloads and arguments across Windows slot lookups in
    ordinary I/O and zlib, bzip2, iconv, and user-filter paths; fseek() and
    rewind() use the same handle mapping.
  • Preserves user-filter instances across Windows slot allocation so
    onCreate, filter callbacks, parameters, and onClose use the same object.
  • Uses the MinGW LLP64 z_stream layout and 32-bit uLong fields for one-shot
    zlib operations, and recognizes PHP's exact uppercase INF, -INF, and
    NAN serialized float tokens before falling back to MSVCRT strtod.
  • Normalizes Windows verbatim canonical paths before exposing __FILE__,
    __DIR__, closure names, and EIR source metadata.
  • Uses native Winsock protocol/service lookup and advertises only transports
    the runtime can negotiate; Windows omits Unix-domain transports and all
    targets omit unavailable SSLv2/SSLv3 aliases.
  • Widens signed 32-bit CRT long results before 64-bit consumers and derives
    PHP_EOL, DIRECTORY_SEPARATOR, and PATH_SEPARATOR from the target.

Processes, shell behavior, eval, and web

  • Implements Windows proc_open() descriptor inheritance and sparse
    descriptor behavior, proc_close(), proc_terminate(), and
    proc_get_status(). Windows reports cached: false, while Unix preserves
    PHP's post-exit cached transition.
  • Polls nonblocking process pipes with php-src's bounded wait and caps
    synchronous reads to the bytes reported by PeekNamedPipe, preventing both
    startup races and accidental blocking.
  • Accepts PHP command arrays with associative or numeric keys in insertion
    order, ignores the keys like php-src, and preserves raw numeric environment
    entries while skipping empty converted values.
  • Matches php-src's Windows shell escaping behavior and preserves arguments
    correctly through CRT argv construction.
  • Extends Magician/eval parity for filesystem, process, shell, network,
    randomness, time, string, callable, and target-specific builtin behavior.
  • Exercises --web as a PE program with exported ABI validation, HTTP
    success/error responses, Ctrl-C shutdown, watchdog termination, and clean
    --max-requests exit.

TLS and stream contexts

  • Cross-builds and validates the Rust TLS bridge on native and cross Windows
    paths.
  • Loads Windows native certificate roots by default so administrator-installed
    and enterprise trust anchors remain visible; other targets retain the
    deterministic bundled root set.
  • Uses bridge ABI v3 with a stable 88-byte ElephcTlsClientOptions structure
    (options layout ABI v1) shared by HTTPS connects and STARTTLS attachment.
  • Implements independent PHP stream-context policies for verify_peer,
    verify_peer_name, and allow_self_signed, plus peer_name, additive
    cafile/capath, and combinable local_cert/local_pk.
  • Treats local_pk as optional when the key is present in local_cert, and
    ignores local_pk without a client certificate, matching php-src.
  • Restricts allow_self_signed to a valid depth-zero genuinely self-signed
    leaf. It never accepts an arbitrary untrusted leaf, never bypasses hostname
    validation, and always verifies TLS 1.2/1.3 handshake signatures.
  • Applies PHP boolean truthiness to stream-context options (0 and "0" are
    false; any other non-zero integer is true).
  • Distinguishes an omitted/null STARTTLS crypto method from an explicit zero,
    enforces supported TLS 1.2/1.3 client masks, and reuses the source TLS
    configuration/session cache when session_stream is provided.
  • Completes HTTPS request and tempfile writes across partial I/O, maps rustls's
    terminal UnexpectedEof read to the same response EOF behavior php-src
    enables through SSL_OP_IGNORE_UNEXPECTED_EOF, and rejects oversized,
    truncated, or unrewindable response streams with balanced handle cleanup.
  • Validates Unix file descriptors and Winsock sockets before adopting them into
    Rust, preserving retryable WouldBlock/TimedOut results without marking
    streams as EOF.

Final php-src audit

The implementation was audited against the current official php-src Windows,
stream, TLS, process, shell, and filesystem paths:

The last comparison found and closed the remaining gaps around native Windows
trust roots, TLS policy independence and true self-signed handling, client key
fallback, STARTTLS crypto-method/session propagation, complete HTTPS I/O,
target-aware lchown/lchgrp visibility, namespace and callable resolution,
tempnam() failure/prefix semantics, proc_open() command/environment
marshalling, proc_get_status().cached, shell escaping, stream-filter register
preservation and object lifetime, bounded process-pipe reads, LLP64 zlib
layout, serialized non-finite floats, loopback socket-pair fixtures,
canonical-path sharing/reparse handling, terminal TLS EOF, Windows stream
seeking, deterministic process-filter line endings, and target-path
normalization. The resulting CI investigation also
closed an AArch64 runtime-callable stack-padding regression without changing
the Windows shadow-space contract, and split a redundant composite eval
failure-path regression into focused cases so each native scenario retains its
own timeout budget.

The audit also removed the obsolete Wine allow-list/known-failures snapshots
and their exemption commands. The retained aggregate verifier accepts only
exact native shard coverage with no failing testcase, validates the expected
JUnit report count inside the verifier, and emits an explicit cardinality
diagnostic instead of counting CLI argument tokens in the workflow shell.

What the first full CI run exposed

Earlier runs on this branch had all been cancelled, so the complete matrix had
never seen this code. The first full run returned 14 red checks across 6
distinct defect classes
. Every one is fixed in this revision, and each was
visible on exactly one platform while the others hid it:

defect failed only on what hid it elsewhere
mod zval; dropped by the merge resolution Linux + macOS (link) nothing — an undeclared module is absent, not dead, so no warning is possible
sprintf missing the ll length modifier linux-x86_64 no fixture exercised a value above 2³²
sprintf overflow re-render losing its variadic linux-aarch64 Apple's ABI passes every variadic on the stack, so the stack copy sufficed
random_int losing its min bound Windows a bare syscall clobbers only rcx/r11; the Windows rewrite turns it into a call, which destroys r9
POSIX-only test fixtures Windows
fclose() on a TLS stream faulting linux-x86_64 this path had never run in CI at all (#[ignore], outbound network)

The TLS fault was the last to close and was not where it appeared to be. Connect,
write, and reads all succeed on x86-64; only closing failed. On that target the
descriptor lives in rax while __rt_user_filter_release_fd reads it from rdi,
and lower_fclose was the one call site in the function that did not stage the
argument. It worked by accident, because the TLS teardown's own map lookup leaves
the descriptor in rdi — but only while it finds no session to close. Once it does,
it loads the session handle into rdi and calls the bridge, and the Rust close
leaves rdi holding whatever it likes; the filter table was then indexed with that
value. AArch64 was unaffected because it passes the argument in x0, which the
teardown restores.

Two further defects were found while investigating the above and are fixed here:

  • __rt_fread and stream_get_contents wrote past the end of the fixed 64 KiB
    concat arena. The returned bytes were correct — read back from the overflowed
    region — while whatever followed the buffer was destroyed, so the observable
    casualty changed from program to program. Reads that do not fit now take an owned
    block, sized from what the stream can actually supply rather than from the
    requested length, since fread($f, $n) reads up to $n bytes.
  • microtime(true) returned a value roughly 2000× too large on macOS. Darwin's
    gettimeofday trap returns 0 without filling the caller's timeval, and
    suseconds_t is 32 bits there; both are now handled.

CI and acceptance gates

Cross-compilation gate

Windows PE Cross-Compile Tests and the independent Windows PE LLVM/LLD Tests run on Ubuntu with MinGW-w64 and Wine. Together they:

  • build warning-free;
  • cross-build all supported Windows bridge archives;
  • verify the GNU and opt-in Clang integrated-assembler/LLD toolchain paths;
  • run the dedicated PE integration tests; and
  • compile, inspect, and execute representative PHP programs as PE32+
    binaries.

Native bridge gate

Windows Native PE & Bridge Tests runs on Windows Server 2025. It:

  • builds and tests bridge crates with the native MSVC Rust host;
  • cross-builds their GNU/COFF archives;
  • verifies every exported C ABI symbol;
  • runs the complete windows_pe integration module; and
  • validates native bridge, eval, TLS, and web behavior.

Strict native codegen gate

The runnable ci-profile codegen inventory is split into 16 deterministic
partitions and executed directly on Windows Server 2025. There is no allowlist
or known-failure exemption: each runnable fixture must appear exactly once and
every shard must succeed. The aggregate Windows gates are required by the
top-level Build & Test job.

The same top-level gate explicitly waits for both ordinary and Magician/eval
codegen matrices on macOS AArch64, Linux x86_64, and Linux AArch64, so a
separately sharded eval failure cannot be hidden behind a green aggregate.

Validation

The complete CI matrix is green on this revision: 123 jobs, no failures, across
macOS AArch64, Linux x86-64, Linux AArch64, and native Windows Server 2025, including
all 16 strict native codegen shards, both eval matrices, and the aggregate gates.

Focused local validation performed during development includes:

  • 135 Windows-specific library tests;
  • focused magic-constant, process, TLS, stream, eval, ownership, and PE/Wine
    regressions;
  • MinGW assembly/link and PE inspection of representative programs;
  • bridge export and multi-archive link validation;
  • focused supported-target regressions discovered by CI;
  • oracle comparison against php 8.5.6 for every behavioural fix above, plus
    execution of the affected fixtures as PE32+ binaries under Wine; and
  • post-rebase Cargo metadata validation, assembly-comment validation,
    git diff --check, ancestry, squash, and unmerged-file checks.

Closing the disclosed gaps

The four items previously disclosed here have been worked through; what changed and what
did not is recorded below rather than quietly dropped.

Windows ACLs behind is_readable() / is_writable() — closed. __rt_sys_access was
GetFileAttributesW-only, and its own docblock admitted that "R_OK succeeds whenever the
path exists". It now performs php-src's check from TSRM/tsrm_win32.c: a cached
impersonation token, GetFileSecurityW, MapGenericMask, then AccessCheck, with php's
exact FILE_GENERIC_READ / FILE_GENERIC_WRITE masks. advapi32 joins both the
production and test-harness link sets.

The check fails open by construction. Every failure — no token, a failed security
query, a descriptor larger than the inline buffer, a failed AccessCheck — returns a
sentinel that the gate treats exactly like "granted", so the DACL verdict can only ever
turn a yes into a no and never invents a denial. That matters more than it sounds: a
fail-closed design would make is_readable() return false for every path, permanently and
invisibly, under a restricted token (a service, AppContainer, or low integrity). The
worst case here equals the previous behaviour.

Reaching that guarantee required dropping the heap from the path entirely. An earlier
draft allocated the security descriptor and treated a null return as its fallback, but
__rt_heap_alloc never returns null — it diverges through __rt_heap_exhausted, which on
Windows issues Linux syscall numbers that neither print nor exit before falling off the end
of the helper. The fallback branch was unreachable. The descriptor now lives in a
fixed-size stack buffer, and an oversized descriptor takes the same fail-open path.

_elephc_web_handler non-volatile registers — closed. The handler is an
extern "C" fn() the bridge's Rust server loop calls directly, so it owes that caller the
platform's callee-saved contract; the register allocator does not discharge it, because its
pools deliberately exclude the registers involved (AArch64 allocates x21-x28, x86_64 only
rbx). abi::nested_call_reg hands out x19 and r12 regardless, hand-written runtime routines
use r14/r15 as scratch, and float spills stage through xmm15. The handler now preserves
those itself, plus rdi/rsi and xmm6-xmm15 on Windows, where MSx64 makes them callee-saved
and elephc's SysV-internal convention treats them as scratch. AArch64 was violating the same
contract through x19 and is fixed with it.

Oversized-read heap blocks — closed for fread(). This needed two independent
omissions, which is why fixing either alone measured as no change at all. The EIR already
emitted a release for these values and the backend discarded it, because
value_is_scratch_string treats any non-Fresh runtime call as arena scratch; and the
block carried heap kind 0, which every __rt_decref_any in the runtime's own read paths
skips as raw. Arena slices stay safe under the release: _concat_buf and _heap_buf are
separate .comm objects, so an arena pointer can never satisfy __rt_heap_free_safe's
managed-heap window test, making the discrimination exact rather than heuristic.

Still open: a stream_get_contents() result boxed into a Mixed cell strands its grown
accumulation buffer. __rt_mixed_from_value copies the bytes out, so the original block is
orphaned before any release refers to it. That belongs to the boxing path.

Marginal eval fixtures — closed. Two fixtures that compile and link a PHP program at
run time sat against the global 60s slow-timeout. They now carry the same documented
per-test override the four heavier fixtures above them already use.

Known gaps

  • A stream_get_contents() result boxed into a Mixed cell still strands its accumulation
    buffer, as described above.
  • __rt_heap_exhausted does not terminate correctly on Windows: it issues Linux syscall
    numbers, so heap exhaustion falls off the end of the helper rather than reporting and
    exiting. Pre-existing and independent of this branch's work; found while reviewing the
    ACL fallback and worth fixing on its own.

Promotion criteria

Windows can move into the first-class supported-target matrix only when one
revision satisfies all of the following:

  • all 16 strict native codegen shards and the exact-coverage aggregate are
    green;
  • native PE, bridge, Magician/eval, TLS, and web suites are green;
  • GNU and LLVM/LLD cross-toolchain probes are green;
  • PHP's Windows-specific behavior has been audited against php-src; and
  • no Windows path silently reports success for unsupported behavior or retains
    reduced semantics that the project claims to support.

On this revision the first three are met, and the php-src audit is recorded above.
The last is not: is_readable()/is_writable() still ignore Windows ACLs, which is
exactly the kind of reduced semantics that criterion exists to catch. Promotion is
the maintainer's call, and this branch does not claim it — documentation and
diagnostics continue to label the target experimental.

@Guikingone
Guikingone marked this pull request as draft July 1, 2026 10:12
@Guikingone
Guikingone force-pushed the feat/windows-pe branch 2 times, most recently from 1a6888e to 4b7fd88 Compare July 1, 2026 15:43
@Guikingone Guikingone changed the title feat(windows-pe): Full Windows x86_64 PE32+ cross-compilation support feat(platform): add full Windows x86_64 PE32+ cross-compilation support Jul 1, 2026
@Guikingone
Guikingone force-pushed the feat/windows-pe branch 2 times, most recently from aa5e6be to 2ac8b78 Compare July 2, 2026 17:30
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.

Callable-invoker ABI on Windows x86_64:
- The descriptor-based runtime callable invoker read its descriptor from a
  hardcoded rdi, but on Windows the caller passes it in the MSx64 first arg
  (rcx); read it from int_arg_reg_name(target, 0) instead.
- The invoker clone helpers passed arguments to the __rt_array_clone_shallow /
  __rt_array_to_mixed / __rt_hash_clone_shallow / __rt_hash_to_mixed /
  __rt_array_new runtime helpers via int_arg_reg_name, which returns the target
  user-call ABI (rcx/rdx on Windows); those helpers use the System V AMD64 ABI on
  every x86_64 target and read rdi/rsi. Add runtime_int_arg_reg_name (SysV on all
  x86_64, platform ABI on AArch64) and route the four helper call sites through
  it. No-op on Linux and AArch64.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.

Callable-invoker ABI on Windows x86_64:
- The descriptor-based runtime callable invoker read its descriptor from a
  hardcoded rdi, but on Windows the caller passes it in the MSx64 first arg
  (rcx); read it from int_arg_reg_name(target, 0) instead.
- The invoker clone helpers passed arguments to the __rt_array_clone_shallow /
  __rt_array_to_mixed / __rt_hash_clone_shallow / __rt_hash_to_mixed /
  __rt_array_new runtime helpers via int_arg_reg_name, which returns the target
  user-call ABI (rcx/rdx on Windows); those helpers use the System V AMD64 ABI on
  every x86_64 target and read rdi/rsi. Add runtime_int_arg_reg_name (SysV on all
  x86_64, platform ABI on AArch64) and route the four helper call sites through
  it. No-op on Linux and AArch64.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.

Callable-invoker ABI on Windows x86_64:
- The descriptor-based runtime callable invoker read its descriptor from a
  hardcoded rdi, but on Windows the caller passes it in the MSx64 first arg
  (rcx); read it from int_arg_reg_name(target, 0) instead.
- The invoker clone helpers passed arguments to the __rt_array_clone_shallow /
  __rt_array_to_mixed / __rt_hash_clone_shallow / __rt_hash_to_mixed /
  __rt_array_new runtime helpers via int_arg_reg_name, which returns the target
  user-call ABI (rcx/rdx on Windows); those helpers use the System V AMD64 ABI on
  every x86_64 target and read rdi/rsi. Add runtime_int_arg_reg_name (SysV on all
  x86_64, platform ABI on AArch64) and route the four helper call sites through
  it. No-op on Linux and AArch64.
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 6, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.

Callable-invoker ABI on Windows x86_64:
- The descriptor-based runtime callable invoker read its descriptor from a
  hardcoded rdi, but on Windows the caller passes it in the MSx64 first arg
  (rcx); read it from int_arg_reg_name(target, 0) instead.
- The invoker clone helpers passed arguments to the __rt_array_clone_shallow /
  __rt_array_to_mixed / __rt_hash_clone_shallow / __rt_hash_to_mixed /
  __rt_array_new runtime helpers via int_arg_reg_name, which returns the target
  user-call ABI (rcx/rdx on Windows); those helpers use the System V AMD64 ABI on
  every x86_64 target and read rdi/rsi. Add runtime_int_arg_reg_name (SysV on all
  x86_64, platform ABI on AArch64) and route the four helper call sites through
  it. No-op on Linux and AArch64.
@Guikingone
Guikingone force-pushed the feat/windows-pe branch 2 times, most recently from a1b7fa8 to b0d4620 Compare July 7, 2026 07:11
Guikingone added a commit to Guikingone/elephc that referenced this pull request Jul 7, 2026
…es builtin

Reconstruct PR illegalstudio#446's feature set on top of current main.

Windows x86_64 PE target:
- Platform::Windows with the MSx64 ABI (rcx/rdx/r8/r9, 32-byte shadow, 16-byte
  alignment) and ~40 Win32 shim wrappers converting SysV->MSx64 (WriteFile/ReadFile,
  sockets, file/dir ops, VirtualAlloc/Heap, BCryptGenRandom, ...).
- Syscall->shim transform wired into the pipeline + runtime cache (Windows-only) so
  PE binaries contain no raw Linux syscalls; unmapped syscalls route to
  __rt_unsupported_syscall instead of a silent trap.
- Entry wrapper (MSx64->SysV), MinGW-w64 assembler/linker, kernel32/msvcrt/winmm/
  ws2_32/bcrypt/shlwapi link set. --target windows-x86_64 (alias
  x86_64-pc-windows-gnu); .exe / .dll outputs. CI windows-pe-cross-compile job
  (MinGW + Wine) wired into the gate.

Fix the implicit end-of-main exit code on Windows: emit_exit and
emit_exit_with_result_reg now load the code into edi and call the __rt_sys_exit
(ExitProcess) shim directly, identical to an explicit exit($n), instead of returning
through the MinGW CRT. The CRT's exit() reaches the same rdi-consuming shim, so the
old return path left rdi holding leftover data and exited with a garbage nonzero code.

random_bytes(int $length): string:
- Cryptographically secure random byte string on every supported target
  (arc4random_buf / getrandom / BCryptGenRandom), fatal on entropy failure or a
  constant length below 1.
- Expressed as a single-source registry home file (src/builtins/math/random_bytes.rs)
  instead of the legacy hand-maintained tables; excluded from the legacy
  runtime-callable wrapper as an EIR-only builtin. Builtin docs + registry JSON
  regenerated.

Callable-invoker ABI on Windows x86_64:
- The descriptor-based runtime callable invoker read its descriptor from a
  hardcoded rdi, but on Windows the caller passes it in the MSx64 first arg
  (rcx); read it from int_arg_reg_name(target, 0) instead.
- The invoker clone helpers passed arguments to the __rt_array_clone_shallow /
  __rt_array_to_mixed / __rt_hash_clone_shallow / __rt_hash_to_mixed /
  __rt_array_new runtime helpers via int_arg_reg_name, which returns the target
  user-call ABI (rcx/rdx on Windows); those helpers use the System V AMD64 ABI on
  every x86_64 target and read rdi/rsi. Add runtime_int_arg_reg_name (SysV on all
  x86_64, platform ABI on AArch64) and route the four helper call sites through
  it. No-op on Linux and AArch64.

# Conflicts:
#	CHANGELOG.md
#	CLAUDE.md
#	docs/internals/builtins/math/mt_rand.md
#	docs/internals/builtins/math/rand.md
#	docs/internals/builtins/math/random_int.md
#	src/codegen/abi/mod.rs
#	src/codegen/builtins/arrays/call_user_func_array.rs
#	src/codegen/builtins/system/exit.rs
#	src/codegen/callable_dispatch.rs
#	src/codegen/prescan.rs
#	src/codegen/runtime_callable_invoker.rs
#	src/codegen_support/abi/bootstrap.rs
#	src/codegen_support/platform/windows_transform.rs
#	src/codegen_support/runtime/arrays/random_bytes.rs
#	src/codegen_support/runtime/emitters.rs
#	src/pipeline.rs
@Guikingone
Guikingone force-pushed the feat/windows-pe branch 2 times, most recently from d2bb042 to d31d8bf Compare July 8, 2026 07:58
@github-actions github-actions Bot added area:magician Touches eval, include execution, or elephc-magician. area:web Touches --web mode, its prelude, or elephc-web. and removed area:codegen Touches target-aware assembly or backend lowering. area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. labels Jul 23, 2026
@Guikingone
Guikingone force-pushed the feat/windows-pe branch 23 times, most recently from 536a403 to 3c3e6ed Compare July 27, 2026 07:57
…php compliance gaps

Windows x86_64 (PE32+) joins the target list: a MinGW-GNU-ABI backend with MSx64
calling-convention codegen, Win32 shims standing in for the Linux syscalls the
runtime issues, an import table, and a parity gate that runs the codegen suite
against the new target.

Auditing that target against php-src turned up defects that were never
Windows-specific, so they are fixed here as well. Each is verified against php 8.5.6
and locked by a fixture that fails without the fix.

Silent data loss and memory disclosure:

  sprintf("%s", $s) copied its argument into a 128-byte stack buffer to
  NUL-terminate it for snprintf, truncating every string past 127 bytes and cutting
  short at any NUL byte php allows inside a string. %s is now rendered natively.

  Any sprintf conversion wider than 127 characters copied adjacent stack memory into
  the result, because snprintf reports the length it *would* have written. Comparing
  lengths hides this; only a content hash shows it.

  filemtime() and filesize() read the stat buffer without checking whether stat()
  had written it, so a missing path returned whatever the stack held. Both now
  report false, as php does.

  array_splice($a, 1, -1) corrupted the array outright: the negative length passed
  the upper clamp and became the capacity handed to the allocator.

php semantics that C's formatter does not share, all of which snprintf was deciding:
precision is ignored on %d/%u/%c and empties %x/%X/%o, the space flag has no
meaning, %b and the %'X pad character do not exist in C, and %N$ names an argument
without advancing the sequential counter.

Negative lengths, which four functions read as "select nothing" because -1 doubled
as an in-band "argument omitted" sentinel: substr, substr_replace, array_slice and
array_splice now read them as php does, and the sentinel is gone.

The temporary directory was the "/tmp" literal on every POSIX target, ignoring
TMPDIR, which macOS always sets. sys_get_temp_dir(), tmpfile() and tempnam() now
resolve it through one runtime helper, and tempnam() falls back to the system
directory instead of returning a path to a file it never created.

Also: tls:// and ssl:// opened in plaintext, random_int() truncated its range to 32
bits, fopen's append mode did not append, and SIGPIPE killed the process instead of
failing the write.

The first full CI run of this branch turned four more defects up, each visible on
exactly one platform and hidden on the others.

The zval pack/unpack bridge had stopped being compiled at all. Resolving the merge
that brought this runtime in dropped `mod zval;` and the ten `zval::emit_*` calls,
replacing them with the new win32 entries. Nothing warned: an undeclared module is
not dead code, it is absent, so the zero-warning build stayed clean while every
helper it defines left the runtime object and `__rt_zval_pack` went undefined at
link.

sprintf formatted php integers at 32 bits on x86_64. The mini format named a bare
conversion, so snprintf read half the value and sprintf("%u", -1) rendered
4294967295. The AArch64 arm has written the "ll" modifier since it was first
emitted; no fixture used a value above 2**32, so nothing caught it.

sprintf rendered oversized conversions from clobbered registers on linux-aarch64.
A result wider than the 128-byte scratch is rendered a second time, and the
argument registers are caller-saved. Apple's ABI passes every variadic argument on
the stack, so the existing stack copy carried the re-render on macOS and hid this
completely; AAPCS64 passes the first in x3, or d0 for a double, so Linux formatted
whatever the first call had left behind -- the right number of bytes, the wrong
ones.

random_int dropped its lower bound on windows: random_int(-100, -50) returned
values in [0, 50]. The x86_64 lowering held min in r9 across the sampler call, and
r9 is caller-saved in both SysV and MSx64, so that only worked by accident of which
registers the sampler touched. On Linux the entropy draw is a bare syscall, which
clobbers rcx and r11; on windows it is rewritten into a call reaching
BCryptGenRandom, which clobbers r9. The AArch64 arm already spilled it.

Five fixtures asserted POSIX rules php does not apply on windows -- TMPDIR against
GetTempPath, /var/tmp probe paths, and the eval constants, which passed there only
because eval answered with the host's values regardless of the target. Each now
names what php defines for its own platform. compiled_binary_size measured `test`
rather than `test.exe` and turned the missing file into a zero, reporting a link
that never happened as a bridge that was not linked.

The crypto-transport fixtures asserted two things at once -- that the handshake
completed, and that the encrypted stream then reads and writes correctly -- so a
failure could not say which half broke. A fixture now asserts only that
stream_socket_client() hands back a stream, which is the half php guarantees in
the transport factory, separating the two failure modes.

Two defects the fixtures could not have caught, found while instrumenting them.

The handshake stream_socket_client() runs on a crypto transport had no bound at
all. Against a peer that completes the TCP connect and then says nothing, rustls
waits on a read the descriptor has no timeout for, and the process blocks forever
-- php returns false after default_socket_timeout. A hung client costs far more
than a failing one: it takes a whole CI shard down with it. php gets this for free
because the stream carries the timeout and crypto is negotiated through the
stream; elephc attaches rustls to a raw descriptor, so it is set explicitly.

microtime(true) reported an instant unrelated to the clock on macos,
intermittently, and the fixture guarding it -- `$t > 1000000000` -- passed under
both faults. The AArch64 helper asked the Darwin trap to fill a timeval, which it
does not do: that is libsystem's job, and the trap returns 0, so the struct was
read back holding whatever the stack carried. Routing through libc, as the x86_64
arm already did, exposed the second fault: Darwin's suseconds_t is 32 bits, so a
64-bit load of tv_usec folded in padding libc does not clear, putting the result
4294.967296 seconds ahead whenever it held 1. The replacement fixture requires
agreement with time(), which samples the same clock.

A second fixture covers the write half of the encrypted path on its own, asserting
fwrite()'s byte count without reading back. With the connect half already
separated, the three data fixtures stop being one indivisible signal: whichever of
connect, write and read is at fault, exactly one fixture fails and names it.

The concat arena also had no capacity at all. It is a flat 64 KiB buffer with an
offset, shared by every string-producing helper, and both read paths wrote into it
without ever asking whether the bytes fit. fread($f, 200000) and
stream_get_contents() on anything past roughly 72 KiB ran straight off the end.
Nothing reported it: the returned string was correct, because it came back from
the overflowed region, while whatever followed the buffer was destroyed -- the
stream itself, in the observed cases, so fclose() rejected it as "unknown given"
where php closes it cleanly. Which neighbour dies depends on the program's layout,
which is what made it look intermittent.

A read that does not fit now takes its own heap block, and stream_get_contents
grows its accumulation onto the heap, doubling so a stream of n bytes costs O(n)
copying. Callers cannot tell: a heap-backed string is the same pointer/length
pair, and __rt_decref_any validates against the managed heap window, so it
releases those blocks and goes on ignoring arena slices. The arena cursor only
moves when the bytes actually landed in the arena.
…urps

Two independent paths took an owned heap block from __rt_stream_get_contents and
then dropped the only reference to it, leaking one block per call for the life of
the process. Both were invisible below 64 KiB: the helper returns a borrowed slice
of the concat arena while the stream fits in it, and only hands back an owned
__rt_heap_alloc block once it does not.

stream_get_contents() is typed `string|false`, so its result is boxed into a Mixed
cell inside the lowering. The boxing copies rather than adopts -- __rt_mixed_from_value
tag 1 goes through __rt_str_persist, which always allocates -- so the slurped block
was left with no owner and, because no EIR value names it, no release either. A
500 KB stream stranded one 524288-byte block per read.

The six file_get_contents() URL branches (http/https/ftp, both arches) have the same
shape one level down: each slurps the response, persists it for ownership, and then
drops the slurped pointer. A 200 KB body cost ~256 KiB per read.

Both fixes release the pre-copy pointer through __rt_heap_free_safe once the copy is
complete. That separates the two shapes exactly rather than heuristically: _concat_buf
and _heap_buf are distinct .comm objects, so an arena pointer can never satisfy its
managed-heap window test and passes through untouched. The arena-sized fixtures are
the negative controls for that -- freeing a borrowed slice would be a wild free, not
a leak.

The release is safe here only because no EIR value names the pre-copy pointer. It must
not be copied to a builtin typed plain `string`: fread() is exactly that, and there the
EIR release already reclaims the block, so a second free would be a double free.

Fixtures assert `leak summary: clean` under --heap-debug and check the bytes, not just
the length -- releasing before the copy completed would still yield the right length.
Verified failing before and passing after: 4 blocks / 2097216 B and 1 block / 262076 B.

docs/specs/mixed-string-ownership-debts.md records the wider investigation these two
fixes came out of: the still-open Mixed->Str cast leak (~one source-sized block per call
across a family of builtins), why a uniform fix there is a use-after-free rather than a
leak fix, four other pre-existing defects found on the way -- including silent data
corruption on dynamic phar:// reads and an unbounded arena copy in bounded
stream_get_contents -- and the measurement pitfalls that make this area easy to get
wrong. Claims in it are tagged measured or read so the unexecuted ones stay visible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:magician Touches eval, include execution, or elephc-magician. area:platform Touches targets, object formats, linking, or platform support. area:web Touches --web mode, its prelude, or elephc-web. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:xl Very large pull request that needs deliberate review planning. target:windows-x86_64 Contains behavior specific to the Windows x86_64 target. type:feature Introduces new user-visible behavior or capabilities.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support PE file (windows)

1 participant