Merge pull request #2378 from ergo720/sb_fix

Fixed slowness in Steel Battalion caused by WaitApc
This commit is contained in:
Luke Usher 2022-07-08 09:58:01 +01:00 committed by GitHub
commit cfa7be71cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 51 deletions

View File

@ -1003,10 +1003,16 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait)
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
LARGE_INTEGER NewTime;
PLARGE_INTEGER Timeout;
if (dwMilliseconds == INFINITE) {
NewTime.QuadPart = ~0ull;
Timeout = nullptr;
}
else if (dwMilliseconds == 0) {
Timeout = &NewTime;
NewTime.QuadPart = 0;
}
else {
Timeout = &NewTime;
NewTime.QuadPart = xbox::KeQueryInterruptTime();
NewTime.QuadPart += (static_cast<xbox::ulonglong_xt>(dwMilliseconds) * CLOCK_TIME_INCREMENT);
}
@ -1017,7 +1023,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait)
return std::nullopt;
}
return std::make_optional<DWORD>(dwRet);
}, &NewTime, bAlertable, UserMode);
}, Timeout, bAlertable, UserMode);
RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret);
}

View File

@ -112,43 +112,75 @@ void RestoreInterruptMode(bool value);
void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql);
template<typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER AbsoluteExpireTime, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
std::optional<xbox::ntstatus_xt> SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
{
if (const auto ret = Lambda()) {
return ret;
}
xbox::KiApcListMtx.lock();
bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]);
bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]);
xbox::KiApcListMtx.unlock();
if (EmptyKernel == false) {
xbox::KiExecuteKernelApc();
}
if ((EmptyUser == false) &&
(Alertable == TRUE) &&
(WaitMode == xbox::UserMode)) {
xbox::KiExecuteUserApc();
return X_STATUS_USER_APC;
}
return std::nullopt;
}
template<typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
{
// NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should
// also interrupt the wait
xbox::ulonglong_xt now = xbox::KeQueryInterruptTime();
xbox::PETHREAD eThread = reinterpret_cast<xbox::PETHREAD>(EmuKeGetPcr()->Prcb->CurrentThread);
if (AbsoluteExpireTime->QuadPart == 0) {
// This will only happen when the title specifies a zero timeout
AbsoluteExpireTime->QuadPart = now;
}
if (Timeout == nullptr) {
// No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled
while (true) {
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
}
while (now <= static_cast<xbox::ulonglong_xt>(AbsoluteExpireTime->QuadPart)) {
if (const auto ret = Lambda()) {
std::this_thread::yield();
}
}
else if (Timeout->QuadPart == 0) {
// A zero timeout means that we only have to check the conditions once and then return immediately if they are not satisfied
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
}
xbox::KiApcListMtx.lock();
bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]);
bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]);
xbox::KiApcListMtx.unlock();
if (EmptyKernel == false) {
xbox::KiExecuteKernelApc();
else {
return X_STATUS_TIMEOUT;
}
if ((EmptyUser == false) &&
(Alertable == TRUE) &&
(WaitMode == xbox::UserMode)) {
xbox::KiExecuteUserApc();
return X_STATUS_USER_APC;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
now = xbox::KeQueryInterruptTime();
}
else {
// A non-zero timeout means we have to check the conditions until we reach the requested time
xbox::LARGE_INTEGER ExpireTime, DueTime, NewTime;
xbox::ulonglong_xt Now;
ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart; // either positive, negative, but not NULL
xbox::PLARGE_INTEGER AbsoluteExpireTime = xbox::KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime, &Now);
while (Now <= static_cast<xbox::ulonglong_xt>(AbsoluteExpireTime->QuadPart)) {
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
}
return X_STATUS_TIMEOUT;
std::this_thread::yield();
Now = xbox::KeQueryInterruptTime();
}
return X_STATUS_TIMEOUT;
}
}
#endif

View File

@ -714,17 +714,6 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
// We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well
// Test case: Metal Slug 3
LARGE_INTEGER NewTime;
PLARGE_INTEGER pNewTime;
if (Interval) {
LARGE_INTEGER ExpireTime, DueTime;
ExpireTime.QuadPart = DueTime.QuadPart = Interval->QuadPart;
pNewTime = KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime);
}
else {
NewTime.QuadPart = ~0ull;
pNewTime = &NewTime;
}
xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
@ -735,7 +724,12 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, pNewTime, Alertable, WaitMode);
}, Interval, Alertable, WaitMode);
if (ret == X_STATUS_TIMEOUT) {
// NOTE: this function considers a timeout a success
ret = X_STATUS_SUCCESS;
}
RETURN(ret);
}

View File

@ -929,6 +929,25 @@ xbox::void_xt xbox::KiExecuteUserApc()
KiExecuteApc<UserMode>();
}
xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval
(
IN xbox::PLARGE_INTEGER OriginalTime,
IN xbox::PLARGE_INTEGER DueTime,
IN OUT xbox::PLARGE_INTEGER NewTime,
OUT ulonglong_xt *Now
)
{
*Now = xbox::KeQueryInterruptTime();
if (OriginalTime->QuadPart >= 0) {
return OriginalTime;
}
else {
NewTime->QuadPart = *Now;
NewTime->QuadPart -= DueTime->QuadPart;
return NewTime;
}
}
xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval
(
IN xbox::PLARGE_INTEGER OriginalTime,

View File

@ -140,6 +140,14 @@ namespace xbox
void_xt KiExecuteKernelApc();
void_xt KiExecuteUserApc();
PLARGE_INTEGER FASTCALL KiComputeWaitInterval
(
IN PLARGE_INTEGER OriginalTime,
IN PLARGE_INTEGER DueTime,
IN OUT PLARGE_INTEGER NewTime,
OUT ulonglong_xt *Now
);
PLARGE_INTEGER FASTCALL KiComputeWaitInterval
(
IN PLARGE_INTEGER OriginalTime,

View File

@ -2208,17 +2208,6 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
}
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
LARGE_INTEGER NewTime;
PLARGE_INTEGER pNewTime;
if (Timeout) {
LARGE_INTEGER ExpireTime, DueTime;
ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart;
pNewTime = KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime);
}
else {
NewTime.QuadPart = ~0ull;
pNewTime = &NewTime;
}
xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
@ -2233,7 +2222,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, pNewTime, Alertable, WaitMode);
}, Timeout, Alertable, WaitMode);
RETURN(ret);
}