mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
[cpu] Implement SysV variadic float ABI (xmm0-7 capture, float returns, printf %f) (#59)
* [cpu] Implement SysV variadic float ABI (xmm0-7 capture, float returns, printf %f) The import trampoline spilled only xmm0 and never reloaded a return xmm0. The guest uses the System V AMD64 ABI: variadic float args pass in xmm0..xmm7 and float/double returns come back in xmm0. As a result variadic float args past the first were unavailable to HLE handlers, float returns never reached the guest, and direct printf read %f/%e/%g from GP registers instead of XMM, printing garbage and desynchronizing every following argument. - Trampoline: spill xmm0..xmm7 into a 0x80-byte save area below the GP argpack (r12 stays at the argpack base) and reload the return xmm0 in the epilogue. - Gateway: read xmm0..7 from the save area into CpuContext and write the handler's xmm0 back. XMM is caller-saved in SysV, so restoring xmm0 on return is safe for non-float imports too. - RegisterPrintfArgumentSource: read float args from xmm0..7 with independent GP/FP counters and a shared stack-overflow cursor. Every emitted byte was decoded; a unit test confirms float args read xmm0..7 (not GP) and interleaved "%d %f %d %f" stays synchronized. Build 0/0. * [cpu] Document the scalar-only leaf-import constraint at its registration site - IsLeafImport: spell out the no-XMM-args / no-XMM-return invariant the fast path relies on and what breaks if it is violated; record the 2026-07-11 audit. - Name every previously uncommented NID in the leaf list (mutex lock/unlock, usleep, the Ampr/Apr command-buffer block, the unknown AGC packet NID). - IsNoBlockLeafImport: document that it is a sub-filter of IsLeafImport and that its five extra entries currently take the full gateway path; fix the mislabeled K-jXhbt2gn4 comment (pthread_mutex_trylock, not scePthreadMutexTrylock, which is upoVrzMHFeE). - Point the DispatchImport call-site note at the audited list. Comment-only change: the comment-stripped diff is empty and the solution builds with 0 warnings / 0 errors.
This commit is contained in:
@@ -99,6 +99,12 @@ public sealed partial class DirectExecutionBackend
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Raw sentinel recoveries: {num2} (last import index={importIndex})");
|
||||
_lastReportedRawSentinelRecoveries = num2;
|
||||
}
|
||||
// Leaf imports take a scalar-only fast path that reads its own operands and
|
||||
// intentionally bypasses the SysV variadic XMM marshalling below. This is safe
|
||||
// only while the leaf set contains no float-variadic or float-returning function.
|
||||
// The constraint and the audited list live on IsLeafImport — do not add a
|
||||
// function that consumes xmm0-7 args or returns in xmm0 to the leaf set;
|
||||
// such functions must stay on the full gateway path below.
|
||||
if (IsLeafImport(importStubEntry.Nid) &&
|
||||
TryDispatchLeafImport(cpuContext, importStubEntry, argPackPtr, num, out var leafResult))
|
||||
{
|
||||
@@ -118,10 +124,17 @@ public sealed partial class DirectExecutionBackend
|
||||
cpuContext[CpuRegister.R13] = *(ulong*)(argPackPtr + 72);
|
||||
cpuContext[CpuRegister.R14] = *(ulong*)(argPackPtr + 80);
|
||||
cpuContext[CpuRegister.R15] = *(ulong*)(argPackPtr + 88);
|
||||
cpuContext.SetXmmRegister(
|
||||
0,
|
||||
*(ulong*)(argPackPtr - 16),
|
||||
*(ulong*)(argPackPtr - 8));
|
||||
// The trampoline spills the SysV variadic XMM save area (xmm0..xmm7) into the
|
||||
// 0x80 bytes immediately below the GP argpack, so variadic float args (printf
|
||||
// %f, and powf/logf inputs) reach the handler. xmm{i} is at argPackPtr-0x80+i*16.
|
||||
for (var xmmIndex = 0; xmmIndex < 8; xmmIndex++)
|
||||
{
|
||||
var xmmSlot = argPackPtr - 0x80 + (xmmIndex * 16);
|
||||
cpuContext.SetXmmRegister(
|
||||
xmmIndex,
|
||||
*(ulong*)xmmSlot,
|
||||
*(ulong*)(xmmSlot + 8));
|
||||
}
|
||||
cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL;
|
||||
ulong value = cpuContext[CpuRegister.Rdi];
|
||||
ulong value2 = cpuContext[CpuRegister.Rsi];
|
||||
@@ -491,6 +504,13 @@ public sealed partial class DirectExecutionBackend
|
||||
Console.Error.Flush();
|
||||
}
|
||||
}
|
||||
// Publish the handler's XMM0 back into the argpack's xmm0 save slot; the
|
||||
// trampoline epilogue reloads it into the guest's XMM0, delivering float/double
|
||||
// return values (powf/logf/wcstod). Harmless for int/pointer returns (XMM is
|
||||
// volatile across a SysV call, so the guest never relies on a preserved XMM0).
|
||||
cpuContext.GetXmmRegister(0, out var returnXmm0Low, out var returnXmm0High);
|
||||
*(ulong*)(argPackPtr - 0x80) = returnXmm0Low;
|
||||
*(ulong*)(argPackPtr - 0x80 + 8) = returnXmm0High;
|
||||
return cpuContext[CpuRegister.Rax];
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -622,6 +642,13 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
// Subset of the leaf set that additionally skips the import-call-frame
|
||||
// bookkeeping. The same scalar-only (no XMM args, no XMM return) constraint
|
||||
// as IsLeafImport applies — see the note there before adding entries.
|
||||
// NOTE: this filter is only consulted after IsLeafImport accepts the NID;
|
||||
// entries listed here but not in IsLeafImport (scePadRead, scePadOpen,
|
||||
// sceUserServiceGetEvent, sceUserServiceGetPlatformPrivacySetting,
|
||||
// pthread_mutex_trylock) currently take the full gateway path.
|
||||
private static bool IsNoBlockLeafImport(string nid) =>
|
||||
nid is
|
||||
"8aI7R7WaOlc" or // sceAmprCommandBufferConstructor
|
||||
@@ -648,7 +675,7 @@ public sealed partial class DirectExecutionBackend
|
||||
"xk0AcarP3V4" or // scePadOpen
|
||||
"yH17Q6NWtVg" or // sceUserServiceGetEvent
|
||||
"D-CzAxQL0XI" or // sceUserServiceGetPlatformPrivacySetting
|
||||
"K-jXhbt2gn4"; // scePthreadMutexTrylock
|
||||
"K-jXhbt2gn4"; // pthread_mutex_trylock (scePthreadMutexTrylock is upoVrzMHFeE)
|
||||
|
||||
private bool ShouldLogImportResult(string nid, OrbisGen2Result result)
|
||||
{
|
||||
@@ -715,22 +742,41 @@ public sealed partial class DirectExecutionBackend
|
||||
"1G3lF1Gg1k8" or // sceKernelOpen
|
||||
"gEpBkcwxUjw"; // sceKernelAprResolveFilepathsToIdsAndFileSizes
|
||||
|
||||
// LEAF-IMPORT REGISTRATION — scalar-only constraint.
|
||||
//
|
||||
// TryDispatchLeafImport is a fast path that never copies the trampoline's XMM
|
||||
// save area into CpuContext and never publishes an XMM0 return back to the
|
||||
// guest (see DispatchImport). Every NID listed here must therefore satisfy BOTH:
|
||||
// 1. no float/double parameters (nothing passed in xmm0..xmm7), and
|
||||
// 2. no float/double return value (nothing returned in xmm0).
|
||||
// Integer/pointer arguments and returns only. va_list-based functions
|
||||
// (vsnprintf) are safe: SysV va_list floats are read from the caller-built
|
||||
// reg_save_area in guest memory, not from the callee's incoming XMM registers.
|
||||
//
|
||||
// If a function that consumes XMM arguments or returns in xmm0 (e.g. powf,
|
||||
// logf, wcstod, sceVideoOutColorSettingsSetGamma_) is ever added here, its
|
||||
// float arguments will silently arrive stale and its return value will be
|
||||
// dropped. Such functions must stay on the full gateway path — do not list them.
|
||||
//
|
||||
// Audited 2026-07-11 (PR #59): every NID below maps to a registered
|
||||
// SysAbiExport whose handler neither reads nor writes CpuContext XMM state
|
||||
// and whose known guest signature is integer/pointer only.
|
||||
private bool IsLeafImport(string nid)
|
||||
{
|
||||
if (nid == "1jfXLRVzisc")
|
||||
if (nid == "1jfXLRVzisc") // sceKernelUsleep
|
||||
{
|
||||
return !_logUsleep;
|
||||
}
|
||||
|
||||
return nid is
|
||||
"9UK1vLZQft4" or
|
||||
"tn3VlD0hG60" or
|
||||
"7H0iTOciTLo" or
|
||||
"2Z+PpY6CaJg" or
|
||||
"8aI7R7WaOlc" or
|
||||
"9UK1vLZQft4" or // scePthreadMutexLock
|
||||
"tn3VlD0hG60" or // scePthreadMutexUnlock
|
||||
"7H0iTOciTLo" or // pthread_mutex_lock
|
||||
"2Z+PpY6CaJg" or // pthread_mutex_unlock
|
||||
"8aI7R7WaOlc" or // sceAmprCommandBufferConstructor
|
||||
"zgXifHT9ErY" or // sceVideoOutIsFlipPending
|
||||
"V++UgBtQhn0" or // sceAgcGetDataPacketPayloadAddress
|
||||
"qj7QZpgr9Uw" or // Gen5 graphics type-2 packet
|
||||
"qj7QZpgr9Uw" or // sceAgcUnknownQj7QZpgr9Uw (unknown NID; observed scalar: command-buffer pointer in, packet address out)
|
||||
"LtTouSCZjHM" or // sceAgcCbNop
|
||||
"k3GhuSNmBLU" or // sceAgcCbDispatch
|
||||
"UZbQjYAwwXM" or // sceAgcCbSetShRegistersDirect
|
||||
@@ -753,24 +799,24 @@ public sealed partial class DirectExecutionBackend
|
||||
"IxYiarKlXxM" or // sceAgcDmaDataPatchSetDstAddressOrOffset
|
||||
"3KDcnM3lrcU" or // sceAgcWaitRegMemPatchAddress
|
||||
"0fWWK5uG9rQ" or // sceAgcQueueEndOfPipeActionPatchAddress
|
||||
"a8uLzYY--tM" or
|
||||
"Qs1xtplKo0U" or
|
||||
"GuchCTefuZw" or
|
||||
"N-FSPA4S3nI" or
|
||||
"baQO9ez2gL4" or
|
||||
"ULvXMDz56po" or
|
||||
"mQ16-QdKv7k" or
|
||||
"vWU-odnS+fU" or
|
||||
"sSAUCCU1dv4" or
|
||||
"C+IEj+BsAFM" or
|
||||
"tZDDEo2tE5k" or
|
||||
"GnxKOHEawhk" or
|
||||
"H896Pt-yB4I" or
|
||||
"sJXyWHjP-F8" or
|
||||
"ASoW5WE-UPo" or
|
||||
"rqwFKI4PAiM" or
|
||||
"eE4Szl8sil8" or
|
||||
"qvMUCyyaCSI" or
|
||||
"a8uLzYY--tM" or // sceAmprAprCommandBufferConstructor
|
||||
"Qs1xtplKo0U" or // sceAmprAprCommandBufferDestructor
|
||||
"GuchCTefuZw" or // sceAmprCommandBufferDestructor
|
||||
"N-FSPA4S3nI" or // sceAmprCommandBufferSetBuffer
|
||||
"baQO9ez2gL4" or // sceAmprCommandBufferReset
|
||||
"ULvXMDz56po" or // sceAmprCommandBufferClearBuffer
|
||||
"mQ16-QdKv7k" or // sceAmprAprCommandBufferReadFile
|
||||
"vWU-odnS+fU" or // sceAmprMeasureCommandSizeReadFile
|
||||
"sSAUCCU1dv4" or // sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00
|
||||
"C+IEj+BsAFM" or // sceAmprMeasureCommandSizeWriteAddressOnCompletion
|
||||
"tZDDEo2tE5k" or // sceAmprCommandBufferGetSize
|
||||
"GnxKOHEawhk" or // sceAmprCommandBufferGetCurrentOffset
|
||||
"H896Pt-yB4I" or // sceAmprCommandBufferWriteKernelEventQueue_04_00
|
||||
"sJXyWHjP-F8" or // sceAmprCommandBufferWriteAddressOnCompletion
|
||||
"ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult
|
||||
"rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer
|
||||
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
|
||||
"qvMUCyyaCSI" or // sceKernelAprSubmitCommandBufferAndGetId
|
||||
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
||||
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
||||
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
||||
|
||||
Reference in New Issue
Block a user