diff --git a/CCPAssert.cpp b/CCPAssert.cpp index 20f9d71..6d0e371 100644 --- a/CCPAssert.cpp +++ b/CCPAssert.cpp @@ -193,6 +193,36 @@ bool CcpIsDebuggerPresent() return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } +#elif defined(__linux__) + +#include +#include +#include + +// A tracer (debugger) is attached if the TracerPid field of /proc/self/status +// is non-zero. +bool CcpIsDebuggerPresent() +{ + FILE* file = fopen( "/proc/self/status", "r" ); + if( !file ) + { + return false; + } + + bool traced = false; + char line[256]; + while( fgets( line, sizeof( line ), file ) ) + { + if( strncmp( line, "TracerPid:", 10 ) == 0 ) + { + traced = atoi( line + 10 ) != 0; + break; + } + } + fclose( file ); + return traced; +} + #elif _WIN32 bool CcpIsDebuggerPresent() diff --git a/CCPCallstack.cpp b/CCPCallstack.cpp index c5c1f1f..99d7a36 100644 --- a/CCPCallstack.cpp +++ b/CCPCallstack.cpp @@ -394,7 +394,7 @@ void CCPCallstack::Enumerate( void ( *callback )( size_t codePointer, const char { std::string record = lines[i]; const char* plus = strchr( lines[i], '+' ); - if( *plus ) + if( plus ) { std::string name; GetLastWord( lines[i], plus - 1, name ); diff --git a/CCPMemory.cpp b/CCPMemory.cpp index ab7ec90..23dd6b8 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -3,9 +3,12 @@ #include "include/CCPMemory.h" #include "include/CCPMemoryTracker.h" #include "include/CCPAssert.h" +#include "include/CcpSecureCrt.h" #include "include/CcpTelemetry.h" #include "CcpMemoryTrackerMutex.h" +#include + #ifdef __APPLE__ #include #include @@ -14,6 +17,8 @@ #include #else #include +#include +#include #endif // We need to initialize the memory system before any other static initializers are executed. @@ -791,6 +796,36 @@ bool CcpGetProcessMemoryInfo( CcpProcessMemoryInfo& result ) result.pageFaultCount = size_t( vmEvents.faults ); return true; } +#elif defined(__linux__) + // VmRSS/VmSize are reported in kB in /proc/self/status. + FILE* file = fopen( "/proc/self/status", "r" ); + if( file ) + { + size_t vmRss = 0; + size_t vmSize = 0; + char line[256]; + while( fgets( line, sizeof( line ), file ) ) + { + if( sscanf( line, "VmRSS: %zu", &vmRss ) == 1 ) + { + continue; + } + if( sscanf( line, "VmSize: %zu", &vmSize ) == 1 ) + { + continue; + } + } + fclose( file ); + result.workingSetSize = vmRss * 1024; + result.pageFileUsage = vmSize * 1024; + + rusage usage; + if( getrusage( RUSAGE_SELF, &usage ) == 0 ) + { + result.pageFaultCount = size_t( usage.ru_minflt + usage.ru_majflt ); + return true; + } + } #endif return false; } diff --git a/CMakeLists.txt b/CMakeLists.txt index c0ec58a..25f4aaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,12 @@ set(SRC_FILES CcpCore.cpp CCPAssert.cpp CCPCallstack.cpp - CCPDefines.cpp + CcpDefines.cpp CcpFileUtils.cpp CCPHash.cpp CCPMemory.cpp CCPMemoryTracker.cpp - CCPMemoryTrackerMutex.h + CcpMemoryTrackerMutex.h CcpMutex.cpp CcpProcess.cpp CcpSecureCrt.cpp diff --git a/CMakePresets.json b/CMakePresets.json index 49da81c..21faa38 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -39,6 +39,16 @@ }, "hidden": true }, + { + "name": "linux", + "inherits": "common", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "hidden": true + }, { "name": "x64-windows-v145", "inherits": "windows", @@ -73,6 +83,14 @@ }, "hidden": true }, + { + "name": "x64-linux", + "inherits": "linux", + "cacheVariables": { + "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/vendor/github.com/carbonengine/vcpkg-registry/toolchains/x64-linux-carbon.cmake" + }, + "hidden": true + }, { "name": "x64-windows-v145-internal", "inherits": "x64-windows-v145", @@ -216,6 +234,42 @@ "VCPKG_TARGET_TRIPLET": "x64-osx-trinitydev", "VCPKG_HOST_TRIPLET": "x64-osx-trinitydev" } + }, + { + "name": "x64-linux-internal", + "inherits": "x64-linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Internal", + "VCPKG_TARGET_TRIPLET": "x64-linux-internal", + "VCPKG_HOST_TRIPLET": "x64-linux-internal" + } + }, + { + "name": "x64-linux-release", + "inherits": "x64-linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "VCPKG_TARGET_TRIPLET": "x64-linux-release", + "VCPKG_HOST_TRIPLET": "x64-linux-release" + } + }, + { + "name": "x64-linux-debug", + "inherits": "x64-linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "VCPKG_TARGET_TRIPLET": "x64-linux-debug", + "VCPKG_HOST_TRIPLET": "x64-linux-debug" + } + }, + { + "name": "x64-linux-trinitydev", + "inherits": "x64-linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "TrinityDev", + "VCPKG_TARGET_TRIPLET": "x64-linux-trinitydev", + "VCPKG_HOST_TRIPLET": "x64-linux-trinitydev" + } } ] } diff --git a/CcpFileUtils.cpp b/CcpFileUtils.cpp index ed59d96..c64324b 100644 --- a/CcpFileUtils.cpp +++ b/CcpFileUtils.cpp @@ -98,10 +98,13 @@ off_t CcpTell( int fd ) #else +#include + int ConvertShareMode( CcpShareMode shareMode ) { int shflag = 0; -#ifndef __ANDROID__ +// O_EXLOCK/O_SHLOCK are BSD extensions that do not exist on Linux or Android. +#if defined(O_EXLOCK) && defined(O_SHLOCK) switch( shareMode ) { case CCP_SM_NOSHARING: @@ -120,6 +123,39 @@ int ConvertShareMode( CcpShareMode shareMode ) return shflag; } +namespace +{ +#if defined(O_EXLOCK) && defined(O_SHLOCK) + + // The share mode is applied atomically by open() via O_EXLOCK/O_SHLOCK. + inline int ApplyShareMode( int fd, CcpShareMode ) + { + return fd; + } + +#else + + // BSD's O_EXLOCK/O_SHLOCK acquire a flock-style advisory lock atomically at + // open() time. Platforms without those flags apply the equivalent flock() + // right after opening instead. + int ApplyShareMode( int fd, CcpShareMode shareMode ) + { + if( fd < 0 || shareMode == CCP_SM_RWSHARING ) + { + return fd; + } + + if( flock( fd, shareMode == CCP_SM_NOSHARING ? LOCK_EX : LOCK_SH ) != 0 ) + { + close( fd ); + return -1; + } + return fd; + } + +#endif +} + int ConvertOpenMode( CcpOpenMode mode ) { int oflag = 0; @@ -145,7 +181,7 @@ int CcpOpenFile( const wchar_t* filename, CcpOpenMode mode, CcpShareMode shareMo int shflag = ConvertShareMode( shareMode ); int fd = open( CW2A( filename ), oflag | shflag, S_IRUSR | S_IWUSR ); - return fd; + return ApplyShareMode( fd, shareMode ); } int CcpCreateFile( const wchar_t* filename ) @@ -160,7 +196,7 @@ int CcpCreateFile( const wchar_t* filename, CcpShareMode shareMode ) int shflag = ConvertShareMode( shareMode ); int fd = open( CW2A( filename ), O_CREAT | O_TRUNC | O_RDWR | shflag, S_IRUSR | S_IWUSR ); - return fd; + return ApplyShareMode( fd, shareMode ); } void CcpCloseFile( int fd ) @@ -217,6 +253,7 @@ bool CcpRenameFile( const std::wstring& src, const std::wstring& dst ) #include #include #include +#include #ifdef __APPLE__ #include #endif @@ -734,10 +771,19 @@ std::wstring CcpExecutablePath() tmp.resize( size ); _NSGetExecutablePath( &tmp[0], &size ); } - + char actualpath [PATH_MAX]; char* path = realpath(&tmp[0], actualpath); return std::wstring( CA2W( actualpath ) ); +#elif defined(__linux__) + char buffer[PATH_MAX]; + ssize_t len = readlink( "/proc/self/exe", buffer, sizeof( buffer ) - 1 ); + if( len < 0 ) + { + return std::wstring(); + } + buffer[len] = 0; + return std::wstring( CA2W( buffer ) ); #else static_assert( false, "CcpExecutablePath is not implemented" ); #endif diff --git a/CcpMutex.cpp b/CcpMutex.cpp index d6b8c52..bf6df85 100644 --- a/CcpMutex.cpp +++ b/CcpMutex.cpp @@ -5,7 +5,9 @@ #include "include/CcpAtomic.h" #include "include/CcpMutex.h" #include "include/CcpThread.h" +#if CCP_TELEMETRY_ENABLED #include "tracy/TracyC.h" +#endif namespace diff --git a/CcpSemaphore.cpp b/CcpSemaphore.cpp index 112836e..d7696c8 100644 --- a/CcpSemaphore.cpp +++ b/CcpSemaphore.cpp @@ -1,8 +1,14 @@ // Copyright © 2013 CCP ehf. #include "include/CcpSemaphore.h" +#include "include/CcpSecureCrt.h" +#if CCP_TELEMETRY_ENABLED #include "tracy/TracyC.h" +#endif + +#include +#include // OS specific includes: #ifdef _WIN32 @@ -58,6 +64,7 @@ CcpSemaphore::CcpSemaphore( const char* semaphoreName, uint32_t initialCount, ui #endif } +#if CCP_TELEMETRY_ENABLED namespace { void AnnounceSemaphoreToTelemetry( TracyCLockCtx& ctx, const char* name ) @@ -73,6 +80,7 @@ namespace } } } +#endif // Preferred constructor, with default value overloads (see header file for details) CcpSemaphore::CcpSemaphore( const char* semaphoreName ) @@ -168,9 +176,18 @@ bool CcpSemaphore::TimedWait( uint32_t timeoutInMs ) mts.tv_nsec = ( timeoutInMs % 1000 ) * 1000000; const bool result = semaphore_timedwait( m_impl->semaphore, mts ) == KERN_SUCCESS; #else + // sem_timedwait expects an absolute CLOCK_REALTIME timestamp, not a + // relative timeout; passing a relative value makes the wait return + // (almost) immediately. timespec ts; - ts.tv_sec = timeoutInMs / 1000; - ts.tv_nsec = (timeoutInMs % 1000) * 1000000; + clock_gettime( CLOCK_REALTIME, &ts ); + ts.tv_sec += timeoutInMs / 1000; + ts.tv_nsec += (timeoutInMs % 1000) * 1000000; + if( ts.tv_nsec >= 1000000000 ) + { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } const bool result = sem_timedwait( &m_impl->semaphore, &ts ) == 0; #endif diff --git a/CcpStatistics.cpp b/CcpStatistics.cpp index faa9dcd..7be94e0 100644 --- a/CcpStatistics.cpp +++ b/CcpStatistics.cpp @@ -2,7 +2,10 @@ #include "include/CcpStatistics.h" +#include +#if CCP_TELEMETRY_ENABLED #include +#endif #include "CcpTelemetry.h" diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 00c0ae4..713eed0 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -203,12 +203,10 @@ void CcpTelemetryTick() if ( !s_fiberEraseMap.empty() ) { auto now = std::chrono::steady_clock::now(); - auto elem = s_fiberEraseMap.front(); - while ( !s_fiberEraseMap.empty() && elem.second >= now ) + while ( !s_fiberEraseMap.empty() && s_fiberEraseMap.front().second >= now ) { - s_fiberNameStore.erase( elem.first ); + s_fiberNameStore.erase( s_fiberEraseMap.front().first ); s_fiberEraseMap.pop(); - elem = s_fiberEraseMap.front(); } } diff --git a/CcpThread.cpp b/CcpThread.cpp index 3e5ba4f..7aa739f 100644 --- a/CcpThread.cpp +++ b/CcpThread.cpp @@ -110,11 +110,15 @@ bool CcpGetThreadTimes( int64_t& kernelTime, int64_t& userTime ) return GetThreadTimes( GetCurrentThread(), &dummy, &dummy, (LPFILETIME)&kernelTime, (LPFILETIME)&userTime ) != 0; } -#elif __APPLE__ +#elif defined(__APPLE__) || defined(__linux__) #include #include "include/CCPAssert.h" +#ifdef __APPLE__ #include +#else +#include +#endif namespace { @@ -131,7 +135,11 @@ namespace CcpThreadId_t CcpGetCurrentThreadId() { +#ifdef __APPLE__ return pthread_mach_thread_np(pthread_self()); +#else + return pthread_self(); +#endif } CcpThreadHandle_t CcpCreateThread( CcpThreadProc_t threadProc, void* context, CcpThreadPriority_t priority ) @@ -139,7 +147,7 @@ CcpThreadHandle_t CcpCreateThread( CcpThreadProc_t threadProc, void* context, Cc pthread_attr_t attr; if( pthread_attr_init( &attr ) != 0 ) { - return nullptr; + return CcpThreadHandle_t(); } CreateThreadData* data = CCP_NEW( "CcpCreateThread/data" ) CreateThreadData; @@ -161,7 +169,7 @@ CcpThreadHandle_t CcpCreateThread( CcpThreadProc_t threadProc, void* context, Cc } else { - return nullptr; + return CcpThreadHandle_t(); } } @@ -243,7 +251,11 @@ int CcpJoinThreadWithTimeout( CcpThreadHandle_t threadHandle, uint32_t timeoutIn CcpThreadId_t CcpGetThreadId( CcpThreadHandle_t handle ) { +#ifdef __APPLE__ return pthread_mach_thread_np(handle); +#else + return handle; +#endif } bool CcpSetThreadPriority( CcpThreadHandle_t thread, CcpThreadPriority_t priority ) @@ -301,6 +313,7 @@ void CcpSetThreadPriority( CcpThread& thread, CcpThreadPriority_t priority ) CcpSetThreadPriority( thread.native_handle(), priority ); } +#ifdef __APPLE__ bool CcpGetThreadTimes( int64_t& kernelTime, int64_t& userTime ) { mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; @@ -319,5 +332,18 @@ bool CcpGetThreadTimes( int64_t& kernelTime, int64_t& userTime ) kernelTime = int64_t( info.system_time.seconds ) * 10000000 + int64_t( info.system_time.microseconds ) * 10; return true; } +#else +bool CcpGetThreadTimes( int64_t& kernelTime, int64_t& userTime ) +{ + rusage usage; + if( getrusage( RUSAGE_THREAD, &usage ) != 0 ) + { + return false; + } + userTime = int64_t( usage.ru_utime.tv_sec ) * 10000000 + int64_t( usage.ru_utime.tv_usec ) * 10; + kernelTime = int64_t( usage.ru_stime.tv_sec ) * 10000000 + int64_t( usage.ru_stime.tv_usec ) * 10; + return true; +} +#endif #endif diff --git a/CcpTime.cpp b/CcpTime.cpp index b9dd159..6b84a79 100644 --- a/CcpTime.cpp +++ b/CcpTime.cpp @@ -3,6 +3,7 @@ #include "include/CcpTime.h" #include "include/CCPAssert.h" #include +#include #include #ifdef _WIN32 @@ -113,6 +114,7 @@ uint64_t CcpGetTickCount() #else #include +#include uint64_t CcpGetTimestamp() { diff --git a/StringConversions.cpp b/StringConversions.cpp index d8f3b7f..85834f9 100644 --- a/StringConversions.cpp +++ b/StringConversions.cpp @@ -103,4 +103,92 @@ void BlueConvertAsciiToWide::Init( const char* src ) m_converted[sizeNeeded] = 0; } +#ifndef __APPLE__ + +// UTF-8 <-> wchar_t (UTF-32 on Linux) conversions. On Apple these two +// overloads are implemented in StringConversions.mm using CoreFoundation. +std::wstring UTF8ToWide( const char* utf8String ) +{ + std::wstring result; + const unsigned char* s = reinterpret_cast( utf8String ); + while( *s ) + { + uint32_t codePoint; + int continuationBytes; + if( *s < 0x80 ) + { + codePoint = *s; + continuationBytes = 0; + } + else if( ( *s & 0xE0 ) == 0xC0 ) + { + codePoint = *s & 0x1F; + continuationBytes = 1; + } + else if( ( *s & 0xF0 ) == 0xE0 ) + { + codePoint = *s & 0x0F; + continuationBytes = 2; + } + else if( ( *s & 0xF8 ) == 0xF0 ) + { + codePoint = *s & 0x07; + continuationBytes = 3; + } + else + { + ++s; + result.push_back( wchar_t( 0xFFFD ) ); + continue; + } + ++s; + for( ; continuationBytes; --continuationBytes ) + { + if( ( *s & 0xC0 ) != 0x80 ) + { + codePoint = 0xFFFD; + break; + } + codePoint = ( codePoint << 6 ) | ( *s & 0x3F ); + ++s; + } + result.push_back( wchar_t( codePoint ) ); + } + return result; +} + +std::string WideToUTF8( const wchar_t* wideString ) +{ + std::string result; + for( const wchar_t* p = wideString; *p; ++p ) + { + uint32_t c = uint32_t( *p ); + if( c < 0x80 ) + { + result.push_back( char( c ) ); + } + else if( c < 0x800 ) + { + result.push_back( char( 0xC0 | ( c >> 6 ) ) ); + result.push_back( char( 0x80 | ( c & 0x3F ) ) ); + } + else if( c < 0x10000 ) + { + result.push_back( char( 0xE0 | ( c >> 12 ) ) ); + result.push_back( char( 0x80 | ( ( c >> 6 ) & 0x3F ) ) ); + result.push_back( char( 0x80 | ( c & 0x3F ) ) ); + } + else + { + result.push_back( char( 0xF0 | ( c >> 18 ) ) ); + result.push_back( char( 0x80 | ( ( c >> 12 ) & 0x3F ) ) ); + result.push_back( char( 0x80 | ( ( c >> 6 ) & 0x3F ) ) ); + result.push_back( char( 0x80 | ( c & 0x3F ) ) ); + } + } + return result; +} + +#endif // !__APPLE__ + #endif diff --git a/include/CCPAssert.h b/include/CCPAssert.h index b78194b..7a71f48 100644 --- a/include/CCPAssert.h +++ b/include/CCPAssert.h @@ -53,7 +53,7 @@ CARBON_CORE_API bool CcpIsDebuggerPresent(); #define CCP_DEBUG_BREAK() __debugbreak() #else #include - #if defined(__APPLE__) + #if defined(__APPLE__) || defined(__linux__) #define CCP_DEBUG_BREAK() { if( CcpIsDebuggerPresent() ) raise(SIGTRAP); } #elif defined(SIGTRAP) #define CCP_DEBUG_BREAK() raise(SIGTRAP) diff --git a/include/CCPLog.h b/include/CCPLog.h index 0727cee..a6ff82a 100644 --- a/include/CCPLog.h +++ b/include/CCPLog.h @@ -5,6 +5,7 @@ #define CCP_LOG_H #include +#include #include #include "carbon_core_export.h" diff --git a/include/CcpThread.h b/include/CcpThread.h index 07018cc..70ccd9a 100644 --- a/include/CcpThread.h +++ b/include/CcpThread.h @@ -26,6 +26,16 @@ typedef mach_port_t CcpThreadId_t; typedef pthread_t CcpThreadHandle_t; + inline void CcpThreadYield() + { + sched_yield(); + } +#elif __linux__ + #include + #include + typedef pthread_t CcpThreadId_t; + typedef pthread_t CcpThreadHandle_t; + inline void CcpThreadYield() { sched_yield(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4fe792f..d5f16bd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,15 +13,22 @@ add_executable(CcpCoreTest CCPMemoryTracker.cpp CcpSecureCrt.cpp CcpStatistics.cpp - CcpTelemetry.cpp CcpThread.cpp CcpTime.cpp StringConversions.cpp TempFile.cpp CCPLog.cpp - TracyTestClient.cpp ) -target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main lz4::lz4 Tracy::TracyClient) +target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main lz4::lz4) +if(WITH_TELEMETRY) + # The telemetry tests talk to a real Tracy client, so they only make sense + # (and only build) when the profiler integration is enabled. + target_sources(CcpCoreTest PRIVATE + CcpTelemetry.cpp + TracyTestClient.cpp + ) + target_link_libraries(CcpCoreTest PRIVATE Tracy::TracyClient) +endif() if(WIN32) target_link_libraries(CcpCoreTest PRIVATE ws2_32) endif()