diff --git a/common/include/Utilities/Exceptions.h b/common/include/Utilities/Exceptions.h index be7951c720..dd0a1610f9 100644 --- a/common/include/Utilities/Exceptions.h +++ b/common/include/Utilities/Exceptions.h @@ -259,43 +259,40 @@ public: \ virtual classname& SetStreamName( const wxString& name ) { StreamName = name; return *this; } \ virtual classname& SetStreamName( const char* name ) { StreamName = fromUTF8(name); return *this; } - #define DEFINE_STREAM_EXCEPTION( classname, parent, message ) \ - DEFINE_RUNTIME_EXCEPTION( classname, parent, message ) \ + #define DEFINE_STREAM_EXCEPTION( classname, parent ) \ + DEFINE_RUNTIME_EXCEPTION( classname, parent, wxEmptyString ) \ classname( const wxString& filename ) { \ StreamName = filename; \ - SetBothMsgs(message); \ } \ DEFINE_STREAM_EXCEPTION_ACCESSORS( classname ) - // Generic stream error. Contains the name of the stream and a message. - // This exception is usually thrown via derived classes, except in the (rare) case of a - // generic / unknown error. + // A generic base error class for bad streams -- corrupted data, sudden closures, loss of + // connection, or anything else that would indicate a failure to open a stream or read the + // data after the stream was successfully opened. // - class Stream : public RuntimeError + class BadStream : public RuntimeError { - DEFINE_STREAM_EXCEPTION( Stream, RuntimeError, wxLt("General file operation error.") ) + DEFINE_STREAM_EXCEPTION( BadStream, RuntimeError ) public: wxString StreamName; // name of the stream (if applicable) virtual wxString FormatDiagnosticMessage() const; virtual wxString FormatDisplayMessage() const; - }; - // A generic base error class for bad streams -- corrupted data, sudden closures, loss of - // connection, or anything else that would indicate a failure to read the data after the - // stream was successfully opened. - // - class BadStream : public Stream - { - DEFINE_STREAM_EXCEPTION( BadStream, Stream, wxLt("File data is corrupted or incomplete, or the stream connection closed unexpectedly.") ) + protected: + void _formatDiagMsg( FastFormatUnicode& dest ) const; + void _formatUserMsg( FastFormatUnicode& dest ) const; }; // A generic exception for odd-ball stream creation errors. // - class CannotCreateStream : public Stream + class CannotCreateStream : public BadStream { - DEFINE_STREAM_EXCEPTION( CannotCreateStream, Stream, wxLt("File could not be created or opened.") ) + DEFINE_STREAM_EXCEPTION( CannotCreateStream, BadStream ) + + virtual wxString FormatDiagnosticMessage() const; + virtual wxString FormatDisplayMessage() const; }; // Exception thrown when an attempt to open a non-existent file is made. @@ -304,22 +301,31 @@ public: \ class FileNotFound : public CannotCreateStream { public: - DEFINE_STREAM_EXCEPTION( FileNotFound, CannotCreateStream, wxLt("File not found.") ) + DEFINE_STREAM_EXCEPTION( FileNotFound, CannotCreateStream ) + + virtual wxString FormatDiagnosticMessage() const; + virtual wxString FormatDisplayMessage() const; }; class AccessDenied : public CannotCreateStream { public: - DEFINE_STREAM_EXCEPTION( AccessDenied, CannotCreateStream, wxLt("Permission denied to file.") ) + DEFINE_STREAM_EXCEPTION( AccessDenied, CannotCreateStream ) + + virtual wxString FormatDiagnosticMessage() const; + virtual wxString FormatDisplayMessage() const; }; // EndOfStream can be used either as an error, or used just as a shortcut for manual // feof checks. // - class EndOfStream : public Stream + class EndOfStream : public BadStream { public: - DEFINE_STREAM_EXCEPTION( EndOfStream, Stream, wxLt("Unexpected end of file or stream.") ); + DEFINE_STREAM_EXCEPTION( EndOfStream, BadStream ) + + virtual wxString FormatDiagnosticMessage() const; + virtual wxString FormatDisplayMessage() const; }; #ifdef __WXMSW__ diff --git a/common/include/Utilities/General.h b/common/include/Utilities/General.h index 4946f33c6c..d405f9472d 100644 --- a/common/include/Utilities/General.h +++ b/common/include/Utilities/General.h @@ -219,11 +219,11 @@ static __fi PageProtectionMode PageAccess_Any() namespace HostSys { void* MmapReserve(uptr base, size_t size); - void MmapCommit(uptr base, size_t size, const PageProtectionMode& mode); + bool MmapCommit(uptr base, size_t size, const PageProtectionMode& mode); void MmapReset(uptr base, size_t size); void* MmapReservePtr(void* base, size_t size); - void MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode); + bool MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode); void MmapResetPtr(void* base, size_t size); // Maps a block of memory for use as a recompiled code buffer. diff --git a/common/include/Utilities/PageFaultSource.h b/common/include/Utilities/PageFaultSource.h index 2d4bf70bb8..cf4bb313a6 100644 --- a/common/include/Utilities/PageFaultSource.h +++ b/common/include/Utilities/PageFaultSource.h @@ -163,10 +163,6 @@ protected: // This function is called for every committed block. virtual void OnCommittedBlock( void* block )=0; - virtual void OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled ) - { - throw; - } }; // -------------------------------------------------------------------------------------- @@ -212,9 +208,9 @@ public: virtual void* Reserve( uint size, uptr base = 0, uptr upper_bounds = 0 ); virtual void Reset(); + virtual bool TryResize( uint newsize ); void OnCommittedBlock( void* block ); - void OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled ); SpatialArrayReserve& SetBlockCount( uint blocks ); SpatialArrayReserve& SetBlockSizeInPages( uint bytes ); diff --git a/common/include/Utilities/ScopedAlloc.h b/common/include/Utilities/ScopedAlloc.h index 8aa2084e96..285e6c39b4 100644 --- a/common/include/Utilities/ScopedAlloc.h +++ b/common/include/Utilities/ScopedAlloc.h @@ -60,6 +60,26 @@ extern void pcsx2_aligned_free(void* pmem); # define _aligned_realloc pcsx2_aligned_realloc #endif +// -------------------------------------------------------------------------------------- +// pxDoOutOfMemory +// -------------------------------------------------------------------------------------- + +typedef void FnType_OutOfMemory( uptr blocksize ); +typedef FnType_OutOfMemory* Fnptr_OutOfMemory; + +// This method is meant to be assigned by applications that link against pxWex. It is called +// (invoked) prior to most pxWex built-in memory/array classes throwing exceptions, and can be +// used by an application to remove unneeded memory allocations and/or reduce internal cache +// reserves. +// +// Example: PCSX2 uses several bloated recompiler code caches. Larger caches improve performance, +// however a rouge cache growth could cause memory constraints in the operating system. If an out- +// of-memory error occurs, PCSX2's implementation of this function attempts to reset all internal +// recompiler caches. This can typically free up 100-150 megs of memory, and will allow the app +// to continue running without crashing or hanging the operating system, etc. +// +extern Fnptr_OutOfMemory pxDoOutOfMemory; + // -------------------------------------------------------------------------------------- // BaseScopedAlloc diff --git a/common/src/Utilities/Exceptions.cpp b/common/src/Utilities/Exceptions.cpp index e4d0d45f4d..64037aa4d1 100644 --- a/common/src/Utilities/Exceptions.cpp +++ b/common/src/Utilities/Exceptions.cpp @@ -23,6 +23,9 @@ #include #endif +// for lack of a better place... +Fnptr_OutOfMemory pxDoOutOfMemory = NULL; + static wxString GetTranslation( const wxChar* msg ) { return msg ? wxGetTranslation( msg ) : wxEmptyString; @@ -195,22 +198,30 @@ Exception::RuntimeError::RuntimeError( const std::exception& ex, const wxString& Exception::OutOfMemory::OutOfMemory( const wxString& allocdesc ) { AllocDescription = allocdesc; - m_message_user = _("Memory allocation failure! Your system has insufficient memory or resources to meet PCSX2's lofty needs."); } wxString Exception::OutOfMemory::FormatDiagnosticMessage() const { - FastFormatUnicode details; - if (!m_message_diag.IsEmpty()) - details.Write(":\n%s",m_message_diag.c_str()); + FastFormatUnicode retmsg; + retmsg.Write(L"Out of memory"); + if (!AllocDescription.IsEmpty()) + retmsg.Write(L" while allocating '%s'", AllocDescription.c_str()); - return pxsFmt(L"Out of memory while allocating '%s'%s", AllocDescription.c_str(), details.c_str()); + if (!m_message_diag.IsEmpty()) + retmsg.Write(L":\n%s", m_message_diag.c_str()); + + return retmsg; } wxString Exception::OutOfMemory::FormatDisplayMessage() const { - if (m_message_user.IsEmpty()) return FormatDisplayMessage(); - return m_message_user; + FastFormatUnicode retmsg; + retmsg.Write( L"%s", _("Oh noes! Out of memory!") ); + + if (!m_message_diag.IsEmpty()) + retmsg.Write(L"\n\n%s", m_message_diag.c_str()); + + return retmsg; } @@ -225,17 +236,31 @@ Exception::VirtualMemoryMapConflict::VirtualMemoryMapConflict( const wxString& a wxString Exception::VirtualMemoryMapConflict::FormatDiagnosticMessage() const { - FastFormatUnicode details; - if (!m_message_diag.IsEmpty()) - details.Write(":\n%s",m_message_diag.c_str()); + FastFormatUnicode retmsg; + retmsg.Write(L"Virtual memory map failed"); + if (!AllocDescription.IsEmpty()) + retmsg.Write(L" while reserving '%s'", AllocDescription.c_str()); - return pxsFmt(L"Out of virtual memory while reserving '%s'%s", AllocDescription.c_str(), details.c_str()); + if (!m_message_diag.IsEmpty()) + retmsg.Write(L":\n%s", m_message_diag.c_str()); + + return retmsg; } wxString Exception::VirtualMemoryMapConflict::FormatDisplayMessage() const { - if (m_message_user.IsEmpty()) return FormatDisplayMessage(); - return m_message_user; + FastFormatUnicode retmsg; + retmsg.Write( L"%s", + pxE( ".Error:VirtualMemoryMap", + L"There is not enough virtual memory available, or necessary virtual memory " + L"mappings have already been reserved by other processes, services, or DLLs." + ) + ); + + if (!m_message_diag.IsEmpty()) + retmsg.Write(L"\n\n%s", m_message_diag.c_str()); + + return retmsg; } @@ -250,20 +275,120 @@ wxString Exception::CancelEvent::FormatDisplayMessage() const return L"Action canceled: " + m_message_diag; } -wxString Exception::Stream::FormatDiagnosticMessage() const +// -------------------------------------------------------------------------------------- +// Exception::BadStream (implementations) +// -------------------------------------------------------------------------------------- +wxString Exception::BadStream::FormatDiagnosticMessage() const { - return pxsFmt( - L"%s\n\tFile/Object: %s", - m_message_diag.c_str(), StreamName.c_str() - ); + FastFormatUnicode retval; + _formatDiagMsg(retval); + return retval; } -wxString Exception::Stream::FormatDisplayMessage() const +wxString Exception::BadStream::FormatDisplayMessage() const { - wxString retval( m_message_user ); - if (!StreamName.IsEmpty()) - retval += L"\n\n" + pxsFmt( _("Path: %s"), StreamName.c_str() ); + FastFormatUnicode retval; + _formatUserMsg(retval); + return retval; +} +void Exception::BadStream::_formatDiagMsg( FastFormatUnicode& dest ) const +{ + dest.Write( L"Path: " ); + if (!StreamName.IsEmpty()) + dest.Write( L"%s", StreamName.c_str() ); + else + dest.Write( L"[Unnamed or unknown]" ); + + if (!m_message_diag.IsEmpty()) + dest.Write(L"\n%s", m_message_diag.c_str()); +} + +void Exception::BadStream::_formatUserMsg( FastFormatUnicode& dest ) const +{ + dest.Write( _("Path: ") ); + if (!StreamName.IsEmpty()) + dest.Write( L"%s", StreamName.c_str() ); + else + dest.Write( _("[Unnamed or unknown]") ); + + if (!m_message_user.IsEmpty()) + dest.Write(L"\n%s", m_message_user.c_str()); +} + +// -------------------------------------------------------------------------------------- +// Exception::CannotCreateStream (implementations) +// -------------------------------------------------------------------------------------- +wxString Exception::CannotCreateStream::FormatDiagnosticMessage() const +{ + FastFormatUnicode retval; + retval.Write("File could not be created."); + _formatDiagMsg(retval); + return retval; +} + +wxString Exception::CannotCreateStream::FormatDisplayMessage() const +{ + FastFormatUnicode retval; + retval.Write(_("A file could not be created.")); + _formatUserMsg(retval); + return retval; +} + +// -------------------------------------------------------------------------------------- +// Exception::FileNotFound (implementations) +// -------------------------------------------------------------------------------------- +wxString Exception::FileNotFound::FormatDiagnosticMessage() const +{ + FastFormatUnicode retval; + retval.Write("File not found."); + _formatDiagMsg(retval); + return retval; +} + +wxString Exception::FileNotFound::FormatDisplayMessage() const +{ + FastFormatUnicode retval; + retval.Write(_("File not found.")); + _formatUserMsg(retval); + return retval; +} + +// -------------------------------------------------------------------------------------- +// Exception::AccessDenied (implementations) +// -------------------------------------------------------------------------------------- +wxString Exception::AccessDenied::FormatDiagnosticMessage() const +{ + FastFormatUnicode retval; + retval.Write("Permission denied to file."); + _formatDiagMsg(retval); + return retval; +} + +wxString Exception::AccessDenied::FormatDisplayMessage() const +{ + FastFormatUnicode retval; + retval.Write(_("Permission denied while trying to open file, likely due to insufficient user account rights.")); + _formatUserMsg(retval); + return retval; +} + +// -------------------------------------------------------------------------------------- +// Exception::EndOfStream (implementations) +// -------------------------------------------------------------------------------------- +wxString Exception::EndOfStream::FormatDiagnosticMessage() const +{ + FastFormatUnicode retval; + retval.Write("Unexpected end of file or stream."); + _formatDiagMsg(retval); + return retval; +} + +wxString Exception::EndOfStream::FormatDisplayMessage() const +{ + FastFormatUnicode retval; + retval.Write(_("Unexpected end of file or stream encountered. File is probably truncated or corrupted.")); + _formatUserMsg(retval); return retval; } @@ -281,7 +406,7 @@ BaseException* Exception::FromErrno( const wxString& streamname, int errcode ) { case EINVAL: pxFailDev( L"Invalid argument" ); - return &(new Exception::Stream( streamname ))->SetDiagMsg(L"Invalid argument? (likely caused by an unforgivable programmer error!)" ); + return &(new Exception::BadStream( streamname ))->SetDiagMsg(L"Invalid argument? (likely caused by an unforgivable programmer error!)" ); case EACCES: // Access denied! return new Exception::AccessDenied( streamname ); @@ -302,6 +427,6 @@ BaseException* Exception::FromErrno( const wxString& streamname, int errcode ) return &(new Exception::BadStream( streamname ))->SetDiagMsg(L"Bad file number"); default: - return &(new Exception::Stream( streamname ))->SetDiagMsg(pxsFmt( L"General file/stream error [errno: %d]", errcode )); + return &(new Exception::BadStream( streamname ))->SetDiagMsg(pxsFmt( L"General file/stream error [errno: %d]", errcode )); } } diff --git a/common/src/Utilities/Linux/LnxHostSys.cpp b/common/src/Utilities/Linux/LnxHostSys.cpp index 4b9689b5d4..5c224431f4 100644 --- a/common/src/Utilities/Linux/LnxHostSys.cpp +++ b/common/src/Utilities/Linux/LnxHostSys.cpp @@ -35,6 +35,44 @@ static __ri void PageSizeAssertionTest( size_t size ) ); } +// returns FALSE if the mprotect call fails with an ENOMEM. +// Raises assertions on other types of POSIX errors (since those typically reflect invalid object +// or memory states). +static bool _memprotect( void* baseaddr, size_t size, const PageProtectionMode& mode ) +{ + PageSizeAssertionTest(size); + + uint lnxmode = 0; + + if (mode.CanWrite()) lnxmode |= PROT_WRITE; + if (mode.CanRead()) lnxmode |= PROT_READ; + if (mode.CanExecute()) lnxmode |= PROT_EXEC | PROT_READ; + + const int result = mprotect( baseaddr, size, lnxmode ); + + if (result == 0) return true; + + switch(errno) + { + case EINVAL: + pxFailDev(pxsFmt(L"mprotect returned EINVAL @ 0x%08X -> 0x%08X (mode=%s)", + baseaddr, (uptr)baseaddr+size, mode.ToString().c_str()) + ); + break; + + case EACCES: + pxFailDev(pxsFmt(L"mprotect returned EACCES @ 0x%08X -> 0x%08X (mode=%s)", + baseaddr, (uptr)baseaddr+size, mode.ToString().c_str()) + ); + break; + + case ENOMEM: + // caller handles assertion or exception, or whatever. + break; + } + return false; +} + void* HostSys::MmapReservePtr(void* base, size_t size) { PageSizeAssertionTest(size); @@ -46,7 +84,7 @@ void* HostSys::MmapReservePtr(void* base, size_t size) return mmap(base, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); } -void HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode) +bool HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode) { // In linux, reserved memory is automatically committed when its permissions are // changed to something other than PROT_NONE. If the user is committing memory @@ -54,7 +92,12 @@ void HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& m // later when the user changes permissions to something useful via calls to MemProtect). if (mode.IsNone()) return; - MemProtect( base, size, mode ); + + if (_memprotect( base, size, mode )) return true; + + if (!pxDoOutOfMemory) return false; + pxDoOutOfMemory(size); + return _memprotect( base, size, mode ); } void HostSys::MmapResetPtr(void* base, size_t size) @@ -82,9 +125,9 @@ void* HostSys::MmapReserve(uptr base, size_t size) return MmapReservePtr((void*)base, size); } -void HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode) +bool HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode) { - MmapCommitPtr( (void*)base, size, mode ); + return MmapCommitPtr( (void*)base, size, mode ); } void HostSys::MmapReset(uptr base, size_t size) @@ -108,35 +151,12 @@ void HostSys::Munmap(uptr base, size_t size) void HostSys::MemProtect( void* baseaddr, size_t size, const PageProtectionMode& mode ) { - PageSizeAssertionTest(size); - - uint lnxmode = 0; - - if (mode.CanWrite()) lnxmode |= PROT_WRITE; - if (mode.CanRead()) lnxmode |= PROT_READ; - if (mode.CanExecute()) lnxmode |= PROT_EXEC | PROT_READ; - - int result = mprotect( baseaddr, size, lnxmode ); - - if (result != 0) + if (!_memprotect(baseaddr, size, mode)) { - switch(errno) - { - case EINVAL: - pxFailDev(pxsFmt(L"mprotect returned EINVAL @ 0x%08X -> 0x%08X (mode=%s)", - baseaddr, (uptr)baseaddr+size, mode.ToString().c_str()) - ); - break; - - case ENOMEM: - throw Exception::OutOfMemory( pxsFmt( L"mprotect failed @ 0x%08X -> 0x%08X (mode=%s)", - baseaddr, (uptr)baseaddr+size, mode.ToString().c_str()) - ); - break; - - case EACCES: - break; - } - throw Exception::OutOfMemory(); + throw Exception::OutOfMemory( "MemProtect" ) + .SetDiagMsg(pxsFmt( L"mprotect failed @ 0x%08X -> 0x%08X (mode=%s)", + baseaddr, (uptr)baseaddr+size, mode.ToString().c_str() + ) + ); } } diff --git a/common/src/Utilities/VirtualMemory.cpp b/common/src/Utilities/VirtualMemory.cpp index 52a1592479..2c7086c57d 100644 --- a/common/src/Utilities/VirtualMemory.cpp +++ b/common/src/Utilities/VirtualMemory.cpp @@ -167,14 +167,19 @@ bool BaseVirtualMemoryReserve::TryResize( uint newsize ) return true; } + void BaseVirtualMemoryReserve::CommitBlocks( uptr page, uint blocks ) { - const uint blocksbytes = blocks * m_blocksize * __pagesize; + const uptr blocksbytes = blocks * m_blocksize * __pagesize; void* blockptr = (u8*)m_baseptr + (page * __pagesize); - // Depending on the operating system, one or both of these could fail if the system - // is low on either physical ram or virtual memory. - HostSys::MmapCommitPtr(blockptr, blocksbytes, m_prot_mode); + // Depending on the operating system, this call could fail if the system is low on either + // physical ram or virtual memory. + if (!HostSys::MmapCommitPtr(blockptr, blocksbytes, m_prot_mode)) + { + throw Exception::OutOfMemory(Name) + .SetDiagMsg(pxsFmt("An additional %u blocks @ 0x%08x were requested, but could not be committed!", blocks, blockptr)); + } u8* init = (u8*)blockptr; u8* endpos = init + blocksbytes; @@ -189,28 +194,37 @@ void BaseVirtualMemoryReserve::OnPageFaultEvent(const PageFaultInfo& info, bool& sptr offset = (info.addr - (uptr)m_baseptr) / __pagesize; if ((offset < 0) || ((uptr)offset >= m_reserved)) return; - try { - DoCommitAndProtect( offset ); - handled = true; - } - catch (Exception::OutOfMemory& ex) - { - handled = false; - OnOutOfMemory( ex, (u8*)m_baseptr + (offset * __pagesize), handled ); - } - #ifndef __WXMSW__ + // Linux Note! the SIGNAL handler is very limited in what it can do, and not only can't + // we let the C++ exception try to unwind the stack, we may not be able to log it either. + // (but we might as well try -- kernel/posix rules says not to do it, but Linux kernel + // implementations seem to support it). + // Note also that logging the exception and/or issuing an assertion dialog are always + // possible if the thread handling the signal is not the main thread. + // In windows we can let exceptions bubble out of the page fault handler. SEH will more // or less handle them in a semi-expected way, and might even avoid a GPF long enough // for the system to log the error or something. - - // In Linux, however, the SIGNAL handler is very limited in what it can do, and not only - // can't we let the C++ exception try to unwind the stack, we can't really log it either. - // We can't issue a proper assertion (requires user popup). We can't do jack or shit, - // *unless* its attached to a debugger; then we can, at a bare minimum, trap it. + + #ifndef __WXMSW__ + try { + #endif + throw Exception::OutOfMemory( L"Right Here" ); + DoCommitAndProtect( offset ); + handled = true; + + #ifndef __WXMSW__ + } catch (Exception::BaseException& ex) { handled = false; - wxTrap(); + if (!wxThread::IsMain()) + { + pxFailRel( ex.FormatDiagnosticMessage() ); + } + else + { + wxTrap(); + } } #endif } @@ -237,6 +251,33 @@ void SpatialArrayReserve::Reset() memzero_sse_a(m_blockbits.GetPtr(), _calcBlockBitArrayLength()); } +// Important! The number of blocks of the array will be altered when using this method. +// +bool SpatialArrayReserve::TryResize( uint newsize ) +{ + uint newpages = (newsize + __pagesize - 1) / __pagesize; + + // find the last allocated block -- we cannot be allowed to resize any smaller than that: + + uint i; + for (i=m_numblocks-1; i; --i) + { + uint bit = i & 7; + if (m_blockbits[i / 8] & bit) break; + } + + uint pages_in_use = i * m_blocksize; + if (newpages < pages_in_use) return false; + + if (!__parent::TryResize( newsize )) return false; + + // On success, we must re-calibrate the internal blockbits array. + + m_blockbits.Resize( (m_numblocks + 7) / 8 ); + + return true; +} + // This method allows the programmer to specify the block size of the array as a function // of its reserved size. This function *must* be called *after* the reserve has been made, // and *before* the array contents have been accessed. @@ -272,7 +313,7 @@ SpatialArrayReserve& SpatialArrayReserve::SetBlockSizeInPages( uint pages ) return *this; } -// This method assigns the block size of the spatial array, in bytes. The actual size of +// SetBlockSize assigns the block size of the spatial array, in bytes. The actual size of // each block will be rounded up to the nearest page size. The resulting size is returned. // // This method must be called prior to accessing or modifying the array contents. Calls to @@ -295,11 +336,6 @@ void SpatialArrayReserve::OnCommittedBlock( void* block ) m_commited += m_blocksize; } -void SpatialArrayReserve::OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled ) -{ - -} - // -------------------------------------------------------------------------------------- // PageProtectionMode (implementations) diff --git a/common/src/Utilities/Windows/WinHostSys.cpp b/common/src/Utilities/Windows/WinHostSys.cpp index 6c137bee7f..fc0f9b5842 100644 --- a/common/src/Utilities/Windows/WinHostSys.cpp +++ b/common/src/Utilities/Windows/WinHostSys.cpp @@ -42,10 +42,26 @@ void* HostSys::MmapReservePtr(void* base, size_t size) return VirtualAlloc(base, size, MEM_RESERVE, PAGE_NOACCESS); } -void HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode) +bool HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode) { void* result = VirtualAlloc(base, size, MEM_COMMIT, ConvertToWinApi(mode)); - pxAssumeDev(result, L"VirtualAlloc COMMIT failed: " + Exception::WinApiError().GetMsgFromWindows()); + if (result) return true; + + const DWORD errcode = GetLastError(); + if (errcode == ERROR_COMMITMENT_MINIMUM) + { + Console.Warning("(MmapCommit) Received windows error %u {Virtual Memory Minimum Too Low}.", ERROR_COMMITMENT_MINIMUM); + Sleep(1000); // Cut windows some time to rework its memory... + } + else if (errcode != ERROR_NOT_ENOUGH_MEMORY && errcode != ERROR_OUTOFMEMORY) + { + pxFailDev(L"VirtualAlloc COMMIT failed: " + Exception::WinApiError().GetMsgFromWindows()); + return false; + } + + if (!pxDoOutOfMemory) return false; + pxDoOutOfMemory(size); + return VirtualAlloc(base, size, MEM_COMMIT, ConvertToWinApi(mode)) != NULL; } void HostSys::MmapResetPtr(void* base, size_t size) @@ -59,9 +75,9 @@ void* HostSys::MmapReserve(uptr base, size_t size) return MmapReservePtr((void*)base, size); } -void HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode) +bool HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode) { - MmapCommitPtr( (void*)base, size, mode ); + return MmapCommitPtr( (void*)base, size, mode ); } void HostSys::MmapReset(uptr base, size_t size) diff --git a/pcsx2/Elfheader.cpp b/pcsx2/Elfheader.cpp index 756962c4f5..5a820bf830 100644 --- a/pcsx2/Elfheader.cpp +++ b/pcsx2/Elfheader.cpp @@ -472,16 +472,16 @@ int GetPS2ElfName( wxString& name ) return 0; } } - catch (Exception::BadStream& ex) - { - Console.Error(ex.FormatDiagnosticMessage()); - return 0; // ISO error - } catch( Exception::FileNotFound& ) { //Console.Warning(ex.FormatDiagnosticMessage()); return 0; // no SYSTEM.CNF, not a PS1/PS2 disc. } + catch (Exception::BadStream& ex) + { + Console.Error(ex.FormatDiagnosticMessage()); + return 0; // ISO error + } #ifdef PCSX2_DEVBUILD FILE *fp; diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index c9822f1b24..da172756a8 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -674,18 +674,34 @@ SysCorePlugins *g_plugins = NULL; // Plugin-related Exception Implementations // --------------------------------------------------------------------------------- +wxString Exception::SaveStateLoadError::FormatDiagnosticMessage() const +{ + FastFormatUnicode retval; + retval.Write("Savestate is corrupt or incomplete!"); + _formatDiagMsg(retval); + return retval; +} + +wxString Exception::SaveStateLoadError::FormatDisplayMessage() const +{ + FastFormatUnicode retval; + retval.Write(_("The savestate cannot be loaded, as it appears to be corrupt or incomplete.")); + _formatUserMsg(retval); + return retval; +} + Exception::PluginOpenError::PluginOpenError( PluginsEnum_t pid ) { PluginId = pid; m_message_diag = L"%s plugin failed to open!"; - m_message_user = L"%s plugin failed to open. Your computer may have insufficient resources, or incompatible hardware/drivers."; + m_message_user = _("%s plugin failed to open. Your computer may have insufficient resources, or incompatible hardware/drivers."); } Exception::PluginInitError::PluginInitError( PluginsEnum_t pid ) { PluginId = pid; m_message_diag = L"%s plugin initialization failed!"; - m_message_user = L"%s plugin failed to initialize. Your system may have insufficient memory or resources needed."; + m_message_user = _("%s plugin failed to initialize. Your system may have insufficient memory or resources needed."); } Exception::PluginLoadError::PluginLoadError( PluginsEnum_t pid ) @@ -695,29 +711,29 @@ Exception::PluginLoadError::PluginLoadError( PluginsEnum_t pid ) wxString Exception::PluginLoadError::FormatDiagnosticMessage() const { - return wxsFormat( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() ) + + return pxsFmt( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() ) + L"\n\n" + StreamName; } wxString Exception::PluginLoadError::FormatDisplayMessage() const { - return wxsFormat( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() ) + + return pxsFmt( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() ) + L"\n\n" + StreamName; } wxString Exception::PluginError::FormatDiagnosticMessage() const { - return wxsFormat( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() ); + return pxsFmt( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() ); } wxString Exception::PluginError::FormatDisplayMessage() const { - return wxsFormat( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() ); + return pxsFmt( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() ); } wxString Exception::FreezePluginFailure::FormatDiagnosticMessage() const { - return wxsFormat( + return pxsFmt( L"%s plugin returned an error while saving the state.\n\n", tbl_PluginInfo[PluginId].shortname ); @@ -731,7 +747,7 @@ wxString Exception::FreezePluginFailure::FormatDisplayMessage() const wxString Exception::ThawPluginFailure::FormatDiagnosticMessage() const { - return wxsFormat( + return pxsFmt( L"%s plugin returned an error while loading the state.\n\n", tbl_PluginInfo[PluginId].shortname ); diff --git a/pcsx2/Plugins.h b/pcsx2/Plugins.h index fd2443874b..80b445a730 100644 --- a/pcsx2/Plugins.h +++ b/pcsx2/Plugins.h @@ -57,12 +57,15 @@ namespace Exception // Exception thrown when a corrupted or truncated savestate is encountered. class SaveStateLoadError : public BadStream { - DEFINE_STREAM_EXCEPTION( SaveStateLoadError, BadStream, wxLt("The savestate appears to be corrupt or incomplete.") ) + DEFINE_STREAM_EXCEPTION( SaveStateLoadError, BadStream ) + + virtual wxString FormatDiagnosticMessage() const; + virtual wxString FormatDisplayMessage() const; }; class PluginError : public RuntimeError { - DEFINE_RUNTIME_EXCEPTION( PluginError, RuntimeError, L"Generic plugin error") + DEFINE_RUNTIME_EXCEPTION( PluginError, RuntimeError, L"Generic plugin error!" ) public: PluginsEnum_t PluginId; diff --git a/pcsx2/System.cpp b/pcsx2/System.cpp index 0083ae6235..c55047efa3 100644 --- a/pcsx2/System.cpp +++ b/pcsx2/System.cpp @@ -108,58 +108,40 @@ void RecompiledCodeReserve::OnCommittedBlock( void* block ) } } -void RecompiledCodeReserve::ResetProcessReserves() const +void SysOutOfMemory_EmergencyResponse(uptr blocksize) { - Cpu->SetCacheReserve( (Cpu->GetCacheReserve() * 3) / 2 ); - Cpu->Reset(); + // An out of memory error occurred. All we can try to do in response is reset the various + // recompiler caches (which can sometimes total over 120megs, so it can be quite helpful). + // If the user is using interpreters, or if the memory allocation failure was on a very small + // allocation, then this code could fail; but that's fine. We're already trying harder than + // 99.995% of all programs ever written. -- air - CpuVU0->SetCacheReserve( (CpuVU0->GetCacheReserve() * 3) / 2 ); - CpuVU0->Reset(); - - CpuVU1->SetCacheReserve( (CpuVU1->GetCacheReserve() * 3) / 2 ); - CpuVU1->Reset(); - - psxCpu->SetCacheReserve( (psxCpu->GetCacheReserve() * 3) / 2 ); - psxCpu->Reset(); -} - - -// Default behavior for out of memory: the -void RecompiledCodeReserve::OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled ) -{ - // Since the recompiler is happy writing away to memory, we have to truncate the reserve - // to include the page currently being accessed, and cannot go any smaller. This will - // allow the rec to finish emitting the current block of instructions, detect that it has - // exceeded the threshold buffer, and reset the buffer on its own. - - // Note: We attempt to commit multiple pages first, since a single block of recompiled - // code can pretty easily surpass 4k. We should have enough for this, since we just - // cleared the other rec caches above -- but who knows what could happen if the user - // has another process sucking up RAM or if the operating system is fickle. If even - // that fails, give up and kill the process. - - try + if (Cpu) { - // Truncate and reset reserves of all other in-use recompiler caches, as this should - // help free up quite a bit of emergency memory. - - ResetProcessReserves(); - - uint cusion = std::min( m_blocksize, 4 ); - HostSys::MmapCommitPtr((u8*)blockptr, cusion * __pagesize, m_prot_mode); - - handled = true; + Cpu->SetCacheReserve( (Cpu->GetCacheReserve() * 3) / 2 ); + Cpu->Reset(); } - catch (Exception::BaseException&) - { - // Fickle has become our reality. By setting handled to FALSE, the OS should kill - // the process for us. No point trying to log anything; this is a super-awesomely - // serious condition that likely means the system is hosed. ;) - handled = false; + if (CpuVU0) + { + CpuVU0->SetCacheReserve( (CpuVU0->GetCacheReserve() * 3) / 2 ); + CpuVU0->Reset(); + } + + if (CpuVU1) + { + CpuVU1->SetCacheReserve( (CpuVU1->GetCacheReserve() * 3) / 2 ); + CpuVU1->Reset(); + } + + if (psxCpu) + { + psxCpu->SetCacheReserve( (psxCpu->GetCacheReserve() * 3) / 2 ); + psxCpu->Reset(); } } + #if _MSC_VER # include "svnrev.h" #endif diff --git a/pcsx2/System.h b/pcsx2/System.h index f0e1fc1db5..52280eb138 100644 --- a/pcsx2/System.h +++ b/pcsx2/System.h @@ -134,6 +134,7 @@ extern SysCpuProviderPack& GetCpuProviders(); extern void SysLogMachineCaps(); // Detects cpu type and fills cpuInfo structs. extern void SysClearExecutionCache(); // clears recompiled execution caches! +extern void SysOutOfMemory_EmergencyResponse(uptr blocksize); extern u8 *SysMmapEx(uptr base, u32 size, uptr bounds, const char *caller="Unnamed"); extern void vSyncDebugStuff( uint frame ); diff --git a/pcsx2/System/RecTypes.h b/pcsx2/System/RecTypes.h index c4367437f7..e999780058 100644 --- a/pcsx2/System/RecTypes.h +++ b/pcsx2/System/RecTypes.h @@ -21,9 +21,7 @@ // RecompiledCodeReserve // -------------------------------------------------------------------------------------- // A recompiled code reserve is a simple sequential-growth block of memory which is auto- -// cleared to INT 3 (0xcc) as needed. When using this class, care should be take to re- -// implement the provided OnOutOfMemory handler so that it clears other recompiled memory -// reserves that are known to be attached to the process. +// cleared to INT 3 (0xcc) as needed. // class RecompiledCodeReserve : public BaseVirtualMemoryReserve { @@ -44,7 +42,6 @@ public: virtual void* Reserve( uint size, uptr base=0, uptr upper_bounds=0 ); virtual void OnCommittedBlock( void* block ); - virtual void OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled ); virtual RecompiledCodeReserve& SetProfilerName( const wxString& shortname ); virtual RecompiledCodeReserve& SetProfilerName( const char* shortname ) diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index d0a4288d8e..ac3ebafff8 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -527,7 +527,9 @@ bool Pcsx2App::OnInit() InitCPUTicks(); - pxDoAssert = AppDoAssert; + pxDoAssert = AppDoAssert; + pxDoOutOfMemory = SysOutOfMemory_EmergencyResponse; + g_Conf = new AppConfig(); wxInitAllImageHandlers(); diff --git a/pcsx2/gui/Panels/PluginSelectorPanel.cpp b/pcsx2/gui/Panels/PluginSelectorPanel.cpp index d4772a462e..49863ea6f4 100644 --- a/pcsx2/gui/Panels/PluginSelectorPanel.cpp +++ b/pcsx2/gui/Panels/PluginSelectorPanel.cpp @@ -59,7 +59,15 @@ namespace Exception class NotEnumerablePlugin : public BadStream { public: - DEFINE_STREAM_EXCEPTION( NotEnumerablePlugin, BadStream, wxLt("File is not a PCSX2 plugin") ); + DEFINE_STREAM_EXCEPTION( NotEnumerablePlugin, BadStream ); + + wxString FormatDiagnosticMessage() const + { + FastFormatUnicode retval; + retval.Write("File is not a PCSX2 plugin"); + _formatDiagMsg(retval); + return retval; + } }; } diff --git a/pcsx2/gui/SysState.cpp b/pcsx2/gui/SysState.cpp index 8a7a894a63..5059dbdc9f 100644 --- a/pcsx2/gui/SysState.cpp +++ b/pcsx2/gui/SysState.cpp @@ -62,21 +62,6 @@ static void SaveStateFile_ReadHeader( IStreamReader& thr ) .SetUserMsg(_("Cannot load this savestate. The state is an unsupported version, likely created by a newer edition of PCSX2.")); }; -class gzError : public Exception::BadStream -{ - DEFINE_STREAM_EXCEPTION( gzError, BadStream, wxLt("Invalid or corrupted gzip archive") ) -}; - -class gzReadError : public gzError -{ - -}; - -class gzWriteError : public gzError -{ - -}; - // -------------------------------------------------------------------------------------- // gzipReader // -------------------------------------------------------------------------------------- diff --git a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj index c72c18c5cb..4a3a6e8a62 100644 --- a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj +++ b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj @@ -451,10 +451,6 @@ RelativePath="..\..\NakedAsm.h" > - - diff --git a/pcsx2/windows/WinCompressNTFS.cpp b/pcsx2/windows/WinCompressNTFS.cpp index 3f9a42f848..c49cc0b9ea 100644 --- a/pcsx2/windows/WinCompressNTFS.cpp +++ b/pcsx2/windows/WinCompressNTFS.cpp @@ -46,7 +46,7 @@ void StreamException_ThrowLastError( const wxString& streamname, HANDLE result ) default: { - throw Exception::Stream( streamname ).SetDiagMsg(wxsFormat( L"General Win32 File/stream error [GetLastError: %d]", error )); + throw Exception::BadStream( streamname ).SetDiagMsg(pxsFmt( L"General Win32 File/stream error [GetLastError: %d]", error )); } } } @@ -58,7 +58,7 @@ bool StreamException_LogLastError( const wxString& streamname, const wxChar* act { StreamException_ThrowLastError( streamname, result ); } - catch( Exception::Stream& ex ) + catch( Exception::BadStream& ex ) { Console.WriteLn( Color_Yellow, L"%s: %s", action, ex.FormatDiagnosticMessage().c_str() ); return true;