Index: compiler-rt/trunk/lib/esan/esan.cpp =================================================================== --- compiler-rt/trunk/lib/esan/esan.cpp +++ compiler-rt/trunk/lib/esan/esan.cpp @@ -149,8 +149,16 @@ VPrintf(1, "Shadow #%d: [%zx-%zx) (%zuGB)\n", i, ShadowStart, ShadowEnd, (ShadowEnd - ShadowStart) >> 30); - uptr Map = (uptr)MmapFixedNoReserve(ShadowStart, ShadowEnd - ShadowStart, - "shadow"); + uptr Map; + if (WhichTool == ESAN_WorkingSet) { + // We want to identify all shadow pages that are touched so we start + // out inaccessible. + Map = (uptr)MmapFixedNoAccess(ShadowStart, ShadowEnd- ShadowStart, + "shadow"); + } else { + Map = (uptr)MmapFixedNoReserve(ShadowStart, ShadowEnd - ShadowStart, + "shadow"); + } if (Map != ShadowStart) { Printf("FATAL: EfficiencySanitizer failed to map its shadow memory.\n"); Die(); Index: compiler-rt/trunk/lib/esan/working_set.cpp =================================================================== --- compiler-rt/trunk/lib/esan/working_set.cpp +++ compiler-rt/trunk/lib/esan/working_set.cpp @@ -16,6 +16,7 @@ #include "esan.h" #include "esan_flags.h" #include "esan_shadow.h" +#include "sanitizer_common/sanitizer_procmaps.h" // We shadow every cache line of app memory with one shadow byte. // - The highest bit of each shadow byte indicates whether the corresponding @@ -30,6 +31,9 @@ namespace __esan { +// Our shadow memory assumes that the line size is 64. +static const u32 CacheLineSize = 64; + // See the shadow byte layout description above. static const u32 TotalWorkingSetBitIdx = 7; static const u32 CurWorkingSetBitIdx = 0; @@ -75,16 +79,80 @@ } } +// This routine will word-align ShadowStart and ShadowEnd prior to scanning. +static u32 countAndClearShadowValues(u32 BitIdx, uptr ShadowStart, + uptr ShadowEnd) { + u32 WorkingSetSize = 0; + u32 ByteValue = 0x1 << BitIdx; + u32 WordValue = ByteValue | ByteValue << 8 | ByteValue << 16 | + ByteValue << 24; + // Get word aligned start. + ShadowStart = RoundDownTo(ShadowStart, sizeof(u32)); + for (u32 *Ptr = (u32 *)ShadowStart; Ptr < (u32 *)ShadowEnd; ++Ptr) { + if ((*Ptr & WordValue) != 0) { + byte *BytePtr = (byte *)Ptr; + for (u32 j = 0; j < sizeof(u32); ++j) { + if (BytePtr[j] & ByteValue) { + ++WorkingSetSize; + // TODO: Accumulate to the lower-frequency bit to the left. + } + } + // Clear this bit from every shadow byte. + *Ptr &= ~WordValue; + } + } + return WorkingSetSize; +} + +// Scan shadow memory to calculate the number of cache lines being accessed, +// i.e., the number of non-zero bits indexed by BitIdx in each shadow byte. +// We also clear the lowest bits (most recent working set snapshot). +static u32 computeWorkingSizeAndReset(u32 BitIdx) { + u32 WorkingSetSize = 0; + MemoryMappingLayout MemIter(true/*cache*/); + uptr Start, End, Prot; + while (MemIter.Next(&Start, &End, nullptr/*offs*/, nullptr/*file*/, + 0/*file size*/, &Prot)) { + VPrintf(4, "%s: considering %p-%p app=%d shadow=%d prot=%u\n", + __FUNCTION__, Start, End, Prot, isAppMem(Start), + isShadowMem(Start)); + if (isShadowMem(Start) && (Prot & MemoryMappingLayout::kProtectionWrite)) { + VPrintf(3, "%s: walking %p-%p\n", __FUNCTION__, Start, End); + WorkingSetSize += countAndClearShadowValues(BitIdx, Start, End); + } + } + return WorkingSetSize; +} + void initializeWorkingSet() { - // The shadow mapping assumes 64 so this cannot be changed. - CHECK(getFlags()->cache_line_size == 64); + CHECK(getFlags()->cache_line_size == CacheLineSize); registerMemoryFaultHandler(); } +static u32 getSizeForPrinting(u32 NumOfCachelines, const char *&Unit) { + // We need a constant to avoid software divide support: + static const u32 KilobyteCachelines = (0x1 << 10) / CacheLineSize; + static const u32 MegabyteCachelines = KilobyteCachelines << 10; + + if (NumOfCachelines > 10 * MegabyteCachelines) { + Unit = "MB"; + return NumOfCachelines / MegabyteCachelines; + } else if (NumOfCachelines > 10 * KilobyteCachelines) { + Unit = "KB"; + return NumOfCachelines / KilobyteCachelines; + } else { + Unit = "Bytes"; + return NumOfCachelines * CacheLineSize; + } +} + int finalizeWorkingSet() { - // FIXME NYI: we need to add memory scanning to report the total lines - // touched, and later add sampling to get intermediate values. - Report("%s is not finished: nothing yet to report\n", SanitizerToolName); + // Get the working set size for the entire execution. + u32 NumOfCachelines = computeWorkingSizeAndReset(TotalWorkingSetBitIdx); + const char *Unit; + u32 Size = getSizeForPrinting(NumOfCachelines, Unit); + Report(" %s: the total working set size: %u %s (%u cache lines)\n", + SanitizerToolName, Size, Unit, NumOfCachelines); return 0; } Index: compiler-rt/trunk/lib/esan/working_set_posix.cpp =================================================================== --- compiler-rt/trunk/lib/esan/working_set_posix.cpp +++ compiler-rt/trunk/lib/esan/working_set_posix.cpp @@ -68,10 +68,16 @@ // app to handle it just as the app would do without our tool in place. static void handleMemoryFault(int SigNum, void *Info, void *Ctx) { if (SigNum == SIGSEGV) { - - // TODO: Add shadow memory fault detection and handling. - - if (AppSigAct.sigaction) { + // We rely on si_addr being filled in (thus we do not support old kernels). + siginfo_t *SigInfo = (siginfo_t *)Info; + uptr Addr = (uptr)SigInfo->si_addr; + if (isShadowMem(Addr)) { + VPrintf(3, "Shadow fault @%p\n", Addr); + uptr PageSize = GetPageSizeCached(); + int Res = internal_mprotect((void *)RoundDownTo(Addr, PageSize), + PageSize, PROT_READ|PROT_WRITE); + CHECK(Res == 0); + } else if (AppSigAct.sigaction) { // FIXME: For simplicity we ignore app options including its signal stack // (we just use ours) and all the delivery flags. AppSigAct.sigaction(SigNum, Info, Ctx); Index: compiler-rt/trunk/test/esan/TestCases/workingset-memset.cpp =================================================================== --- compiler-rt/trunk/test/esan/TestCases/workingset-memset.cpp +++ compiler-rt/trunk/test/esan/TestCases/workingset-memset.cpp @@ -16,6 +16,5 @@ memset((char *)p + 63*i, i, 63*i); munmap(p, size); return 0; - // FIXME: once the memory scan and size report is in place add it here. - // CHECK: {{.*}}EfficiencySanitizer is not finished: nothing yet to report + // CHECK: {{.*}} EfficiencySanitizer: the total working set size: 77 KB (12{{[0-9]+}} cache lines) } Index: compiler-rt/trunk/test/esan/TestCases/workingset-signal-posix.cpp =================================================================== --- compiler-rt/trunk/test/esan/TestCases/workingset-signal-posix.cpp +++ compiler-rt/trunk/test/esan/TestCases/workingset-signal-posix.cpp @@ -57,3 +57,4 @@ // CHECK-NEXT: Past longjmp for signal // CHECK-NEXT: Handling SIGSEGV for sigaction // CHECK-NEXT: Past longjmp for sigaction +// CHECK: {{.*}} EfficiencySanitizer: the total working set size: {{[0-9][0-9][0-9]}} Bytes ({{[0-9][0-9]}} cache lines) Index: compiler-rt/trunk/test/esan/TestCases/workingset-simple.cpp =================================================================== --- compiler-rt/trunk/test/esan/TestCases/workingset-simple.cpp +++ compiler-rt/trunk/test/esan/TestCases/workingset-simple.cpp @@ -0,0 +1,30 @@ +// RUN: %clang_esan_wset -O0 %s -o %t 2>&1 +// RUN: %run %t 2>&1 | FileCheck %s + +#include +#include +#include +#include + +const int size = 0x1 << 25; // 523288 cache lines +const int line_size = 64; + +int main(int argc, char **argv) { + char *bufA = (char *)malloc(sizeof(char) * line_size); + char bufB[64]; + char *bufC = (char *)mmap(0, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + bufA[0] = 0; + // This additional access to the same line should not increase the line + // count: but it's difficult to make a non-flaky test that measures the + // lines down to the ones digit so right now we're not really testing that. + // If we add a heap-only mode we may be able to be more precise. + bufA[1] = 0; + bufB[33] = 1; + for (int i = 0; i < size; i += line_size) + bufC[i] = 0; + free(bufA); + munmap(bufC, 0x4000); + // CHECK: {{.*}} EfficiencySanitizer: the total working set size: 32 MB (524{{[0-9][0-9][0-9]}} cache lines) + return 0; +}