Skip to content

Fix sjiswrap GetProcAddress regressions; cut per-compile overhead ~2x for MWCC workloads - #145

Open
fjooord wants to merge 4 commits into
decompals:mainfrom
fjooord:sjiswrap-fixes-and-perf
Open

Fix sjiswrap GetProcAddress regressions; cut per-compile overhead ~2x for MWCC workloads#145
fjooord wants to merge 4 commits into
decompals:mainfrom
fjooord:sjiswrap-fixes-and-perf

Conversation

@fjooord

@fjooord fjooord commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Two regression fixes that unblock sjiswrap.exe on wibo ≥ 1.2.0, plus two performance commits that roughly halve wall-clock for MWCC compiler workloads. All four verified against the melee (doldecomp) build: full build of 2135 objects, every object byte-identical, 100.9s → 51.4s (macOS x86_64 host; similar ratios measured on Linux i686).

Regression fixes (why projects are stuck on old versions)

  1. FormatMessageA export lost — implemented in winbase.cpp but its header declaration was commented out, so the trampoline generator never emitted a resolver entry. GetProcAddress(kernel32, "FormatMessageA") returns NULL and sjiswrap aborts with PeLoaderErr(GetProcAddressFail). Restored + regression test.
  2. GetProcAddress by ordinal on the builtin lmgr stub returns NULL — sjiswrap resolves mwcceppc.exe's imports itself and requests lmgr11.dll ordinal 191. wibo 1.1.0 returned a lazy missing-function stub (abort only if called; MWCC never calls it). Restored that behavior, scoped to the pinned/fake lmgr module only — other builtins keep NULL + ERROR_PROC_NOT_FOUND for feature detection.

Together these are why doldecomp/melee still pins 0.7.0 (see also #104, whose lmgr-from-disk mechanism the existing pinning already mitigates).

Performance

  1. Guest-side stubs for hot no-kernel APIs. Profiling one MWCC compile: GetTickCount ×135,731, GetLastError ×101,434, IsDBCSLeadByte ×100,553 — each a full guest↔host trampoline round-trip for what Windows serves from userland. These now execute entirely in 32-bit guest mode: GetLastError/SetLastError read/write fs:[0x34] (TEB.LastErrorValue, already the source of truth), GetCurrentThreadId reads the TEB, IsDBCSLeadByte is constant-FALSE only when the ACP has no DBCS lead bytes, and GetTickCount reads a KUSER_SHARED_DATA-style tick page maintained at 1ms by a lazily-started updater (Windows itself has 10–16ms granularity). WIBO_DEBUG=1 falls back to host thunks so debug traces still show these calls.
  2. Filesystem resolver caching. One MWCC compile issued ~182k syscalls for ~360 real opens: per-component directory enumeration on every missing-path probe, 6 metadata ops per successful CreateFile, double-stats in GetFileAttributes, and filesystem access in the lexically-defined GetFullPathName. With a per-directory case-insensitive cache (readdir-order tie-breaks preserved, negative caching, stat-identity revalidation, invalidation on wibo-initiated mutations), lazy canonicalization, and a lexical GetFullPathName: ~182k → ~18k syscalls per compile (QEMU_STRACE, Linux i686). For reference, wibo 0.7.0 issues ~44k on the same TU — this branch is ~1.6x faster than 0.7.0 wall-clock in the same container.

Verification

  • Full melee build before/after: all 2135 .o files sha256-identical; .d outputs identical.
  • sjiswrap + mwcceppc 1.2.5n end-to-end golden compile passes on macOS x86_64 and Linux i686 (QEMU).
  • Each commit builds standalone (release-macos preset).
  • Fixture tests: existing suite requires MinGW/Wine, unavailable locally — CI should exercise them.

Happy to split this into separate PRs (fixes vs perf) if you prefer.

🤖 Generated with Claude Code

sjiswrap.exe resolves mwcceppc.exe's imports via GetProcAddress and
requests lmgr11.dll ordinal 191, which the builtin lmgr stub does not
implement. wibo 1.1.0 returned a lazy missing-function stub (aborting
only if actually called); the module refactor changed this to NULL,
which makes sjiswrap abort at load with PeLoaderErr(GetProcAddressFail).

Restore the 1.1.0 behavior, scoped to the builtin lmgr module: it is a
deliberately fake, pinned module, so any name/ordinal a real FlexLM DLL
would export should resolve to a callable-abort stub. Other builtin
modules keep NULL + ERROR_PROC_NOT_FOUND for feature detection.
…yscalls

An MWCC compile through wibo issued ~182k syscalls for ~360 real file
opens: every failed include probe walked each path component with a
directory enumeration (no cache), CreateFile performed exists+status+
open+fstat+weakly_canonical+lseek per open, GetFileAttributes stat'd
resolved paths a second time, and GetFullPathNameA hit the filesystem
for what Win32 defines as a lexical operation.

- per-directory case-insensitive lookup cache (one enumeration per
  directory, first-seen-wins to preserve readdir-order tie-breaks) with
  negative caching, revalidated by directory stat identity and
  invalidated on wibo-initiated create/rename/delete
- CreateFile: single open with disposition-derived flags + one fstat;
  canonicalization is now lazy (only delete-on-close/name queries)
- GetFileAttributes[Ex] reuses the stat gathered during resolution
- GetFullPathNameA/W is purely lexical per the Win32 contract

Syscalls per MWCC compile drop from ~182k to ~18k; wall clock for a
full melee (doldecomp) build drops ~2x. All 2135 objects verified
byte-identical before/after.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant