Wine-NSPA – Local Section Bypass

This page documents the client-side section path built on top of local-file handles.

Table of Contents

  1. Overview
  2. Coverage
  3. Eligibility and fallback
  4. Handle range and section table
  5. Section lifecycle
  6. DuplicateHandle and ownership boundaries
  7. Sharing arbitration and mapping bits
  8. Validation and observed effect
  9. Implementation map
  10. Related docs

1. Overview

Wine-NSPA no longer has to mint a wineserver section object for every eligible CreateFileMapping on a local-file handle. The client process can keep a bounded section table of its own, duplicate the unix fd at section-creation time, and service the common same-process view lifecycle without a server round-trip.

The feature sits on top of the local-file bypass rather than replacing it. A local file handle still owns pathname resolution, open semantics, and the cross-process sharing aggregate. The local section path reuses that file handle state to build an unnamed file-backed section, then publishes mapping bits back into the same aggregate so later file operations still see correct STATUS_USER_MAPPED_FILE and sharing behavior.

The current boundary is intentionally narrow:

That is enough to retire a large volume of mapping RPC traffic from the common case while leaving the hard cross-process edge cases on the authoritative server path.


2. Coverage

Surface Current behavior
NtCreateSection Eligible file-backed mappings on local-file handles can mint a client-side section handle instead of calling wineserver.
NtMapViewOfSection / NtMapViewOfSectionEx Same-process maps on local section handles install the view directly in the client process.
NtUnmapViewOfSectionEx Local section views unmap without a server hop.
NtQuerySection SectionBasicInformation is answered locally; image-only queries still fall back or return the honest non-image status where appropriate.
NtClose Local section close tears down local views and releases the duplicated unix fd; if a same-process duplicate already promoted the section, the cached server handle is also released.
NtDuplicateObject Same-process duplicate of a local section promotes once to a server section, caches that server handle, and then lets the duplicate proceed.
File/section coherence Mapping bits are published into the local-file aggregate so later share checks and FileEndOfFileInformation handling still see active mappings.

This path is the default for eligible unnamed file-backed sections.


3. Eligibility and fallback

The local section path is deliberately smaller than the full NT section surface. A section stays local only when all of the following are true:

Anything outside that envelope falls back to the normal server path. That keeps the feature easy to reason about and avoids pretending that cross-process namespace or image-loader semantics can be reconstructed client-side.

Local section eligibility and fallback `NtCreateSection(file_handle, ...)` section request reaches ntdll unix layer local-section gate local-file handle + file-backed + unnamed not `SEC_IMAGE`, not `SEC_LARGE_PAGES` otherwise fall through to the normal server section path ineligible wineserver section path keeps image, named, and large-page cases honest mint local section handle duplicate unix fd, publish mapping bits, return client-range handle same-process map / query / unmap / close stay local until a server boundary is crossed

4. Handle range and section table

Local section handles live in their own client-private range:

That split matters because later APIs can distinguish a local section from a local file with a cheap range check before they decide which local table to consult.

Each local section entry keeps:

The table is process-local and protected by its own PI mutex. Cross-process coordination is not done through the section table itself; only the mapping effects are published into the shared local-file aggregate.


5. Section lifecycle

The local section lifecycle is a same-process fast path from creation to final close. The main change versus the older path is that the unix fd is duplicated at section creation time, so the mapping can survive a later CloseHandle() on the original file handle.

Local section lifecycle local-file handle pathname + sharing state already owned locally create local section duplicate unix fd allocate section handle publish `FILE_MAPPING_*` bits map and use view `NtMapViewOfSection` tracks local view metadata no server section object involved local queries `NtQuerySection` basic info `NtUnmapViewOfSectionEx` local close bookkeeping file handle can close earlier `CreateFile -> CreateFileMapping -> CloseHandle(file)` section keeps its own duplicated unix fd later map still succeeds final section close unmap remaining local views clear mapping bits and release fd also closes cached server handle if same-process DUP promoted earlier

Two points are worth calling out:


6. DuplicateHandle and ownership boundaries

The local section path is same-process first. That is deliberate.

When NtDuplicateObject() sees a local section handle and both the source and destination process are the current process, it promotes the section once to a server section, caches that promoted handle on the local entry, and then lets the duplicate proceed through the normal server machinery.

When the duplicate crosses a process boundary, the client-side section handle stops being a valid abstraction. The server does not have access to the remote process’s private section table, so the current behavior is to fall through cleanly rather than invent cross-process state. That produces the right failure instead of a confusing partially-working handle.

DuplicateHandle boundary local section handle client-private source handle `NtDuplicateObject(source, dest, ...)` decision point depends on whether the destination is the current process same process promote once, then duplicate server section handle cached locally later local close releases both local and promoted state other process fall back cleanly remote process cannot see the private section table current boundary is honest failure, not partial emulation

This is one of the key differences between a local optimization and a fake object namespace. The local section handle is a process-local implementation detail until the call explicitly crosses into server-owned handle semantics.


7. Sharing arbitration and mapping bits

The local section path stays correct by feeding its mapping state back into the same shared aggregate that local-file already uses for open and sharing arbitration.

When a local section is created:

That shared publication matters for two reasons:

This is the architectural seam between the file path and the section path: the file table owns the cross-process aggregate, and the section table reuses it instead of inventing a second source of truth.


8. Validation and observed effect

The local section path was validated on the real workload that exposed the old cost: repeated file mapping and view traffic during app startup and UI initialization.

Key observed results from the landed implementation:

The companion local-file follow-ons matter here too. Because mapping bits are published into the shared aggregate, later file-side changes like local FileEndOfFileInformation handling can preserve the same mapped-file boundary without reintroducing a mandatory section RPC.


9. Implementation map

Path Role
dlls/ntdll/unix/nspa/local_file.c local section table, handle range, create path, mapping-bit publication, same-process duplicate support
dlls/ntdll/unix/sync.c NtCreateSection / NtCreateSectionEx entrypoint hooks
dlls/ntdll/unix/virtual.c NtMapViewOfSection, NtMapViewOfSectionEx, local unmap, view tagging, and NtQuerySection support
dlls/ntdll/unix/server.c NtDuplicateObject promotion boundary and NtClose teardown
dlls/ntdll/unix/unix_private.h shared declarations for local-file and local-section helpers

The design intentionally stays inside existing Wine layers:

That keeps the patch understandable and reduces the number of call sites that need to know about the feature.