Skip to content

Commit e202ff4

Browse files
w2yehiaWael Yehiahubert-reinterpretcast
committed
[profile] Implement a non-mmap path when reading profile files from a non-local filesystem (#131177)
On AIX, when accessing mmap'ed memory associated to a file on NFS, a SIGBUS might be raised at random. The problem is still in open state with the OS team. This PR teaches the profile runtime, under certain conditions, to avoid the mmap when reading the profile file during online merging. This PR has no effect on any platform other than AIX because I'm not aware of this problem on other platforms. Other platforms can easily opt-in to this functionality in the future. The logic in function `is_local_filesystem` was copied from [llvm/lib/Support/Unix/Path.inc](https://github.com/llvm/llvm-project/blob/f388ca3d9d9a58e3d189458b590ba68dfd9e5a2d/llvm/lib/Support/Unix/Path.inc#L515) (https://reviews.llvm.org/D58801), because it seems that the compiler-rt/profile cannot reuse code from llvm except through `InstrProfData.inc`. Thanks to @hubert-reinterpretcast for substantial feedback downstream. --------- Co-authored-by: Wael Yehia <[email protected]> Co-authored-by: Hubert Tong <[email protected]>
1 parent 5f6d9b4 commit e202ff4

File tree

5 files changed

+209
-31
lines changed

5 files changed

+209
-31
lines changed

compiler-rt/lib/profile/InstrProfilingFile.c

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -419,17 +419,17 @@ static int getProfileFileSizeForMerging(FILE *ProfileFile,
419419
* \p ProfileBuffer. Returns -1 on failure. On success, the caller is
420420
* responsible for unmapping the mmap'd buffer in \p ProfileBuffer. */
421421
static int mmapProfileForMerging(FILE *ProfileFile, uint64_t ProfileFileSize,
422-
char **ProfileBuffer) {
423-
*ProfileBuffer = mmap(NULL, ProfileFileSize, PROT_READ, MAP_SHARED | MAP_FILE,
424-
fileno(ProfileFile), 0);
425-
if (*ProfileBuffer == MAP_FAILED) {
426-
PROF_ERR("Unable to merge profile data, mmap failed: %s\n",
427-
strerror(errno));
422+
ManagedMemory *ProfileBuffer) {
423+
lprofGetFileContentBuffer(ProfileFile, ProfileFileSize, ProfileBuffer);
424+
425+
if (ProfileBuffer->Status == MS_INVALID) {
426+
PROF_ERR("Unable to merge profile data: %s\n", "reading file failed");
428427
return -1;
429428
}
430429

431-
if (__llvm_profile_check_compatibility(*ProfileBuffer, ProfileFileSize)) {
432-
(void)munmap(*ProfileBuffer, ProfileFileSize);
430+
if (__llvm_profile_check_compatibility(ProfileBuffer->Addr,
431+
ProfileFileSize)) {
432+
(void)lprofReleaseBuffer(ProfileBuffer, ProfileFileSize);
433433
PROF_WARN("Unable to merge profile data: %s\n",
434434
"source profile file is not compatible.");
435435
return -1;
@@ -444,7 +444,7 @@ static int mmapProfileForMerging(FILE *ProfileFile, uint64_t ProfileFileSize,
444444
*/
445445
static int doProfileMerging(FILE *ProfileFile, int *MergeDone) {
446446
uint64_t ProfileFileSize;
447-
char *ProfileBuffer;
447+
ManagedMemory ProfileBuffer;
448448

449449
/* Get the size of the profile on disk. */
450450
if (getProfileFileSizeForMerging(ProfileFile, &ProfileFileSize) == -1)
@@ -460,9 +460,9 @@ static int doProfileMerging(FILE *ProfileFile, int *MergeDone) {
460460
return -1;
461461

462462
/* Now start merging */
463-
if (__llvm_profile_merge_from_buffer(ProfileBuffer, ProfileFileSize)) {
463+
if (__llvm_profile_merge_from_buffer(ProfileBuffer.Addr, ProfileFileSize)) {
464464
PROF_ERR("%s\n", "Invalid profile data to merge");
465-
(void)munmap(ProfileBuffer, ProfileFileSize);
465+
(void)lprofReleaseBuffer(&ProfileBuffer, ProfileFileSize);
466466
return -1;
467467
}
468468

@@ -471,7 +471,7 @@ static int doProfileMerging(FILE *ProfileFile, int *MergeDone) {
471471
(void)COMPILER_RT_FTRUNCATE(ProfileFile,
472472
__llvm_profile_get_size_for_buffer());
473473

474-
(void)munmap(ProfileBuffer, ProfileFileSize);
474+
(void)lprofReleaseBuffer(&ProfileBuffer, ProfileFileSize);
475475
*MergeDone = 1;
476476

477477
return 0;
@@ -672,13 +672,13 @@ static void initializeProfileForContinuousMode(void) {
672672
} else {
673673
/* The merged profile has a non-zero length. Check that it is compatible
674674
* with the data in this process. */
675-
char *ProfileBuffer;
675+
ManagedMemory ProfileBuffer;
676676
if (mmapProfileForMerging(File, ProfileFileSize, &ProfileBuffer) == -1) {
677677
lprofUnlockFileHandle(File);
678678
fclose(File);
679679
return;
680680
}
681-
(void)munmap(ProfileBuffer, ProfileFileSize);
681+
(void)lprofReleaseBuffer(&ProfileBuffer, ProfileFileSize);
682682
}
683683
} else {
684684
File = fopen(Filename, FileOpenMode);
@@ -1257,12 +1257,12 @@ COMPILER_RT_VISIBILITY int __llvm_profile_set_file_object(FILE *File,
12571257
} else {
12581258
/* The merged profile has a non-zero length. Check that it is compatible
12591259
* with the data in this process. */
1260-
char *ProfileBuffer;
1260+
ManagedMemory ProfileBuffer;
12611261
if (mmapProfileForMerging(File, ProfileFileSize, &ProfileBuffer) == -1) {
12621262
lprofUnlockFileHandle(File);
12631263
return 1;
12641264
}
1265-
(void)munmap(ProfileBuffer, ProfileFileSize);
1265+
(void)lprofReleaseBuffer(&ProfileBuffer, ProfileFileSize);
12661266
}
12671267
mmapForContinuousMode(0, File);
12681268
lprofUnlockFileHandle(File);

compiler-rt/lib/profile/InstrProfilingUtil.c

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
#include <unistd.h>
2222
#endif
2323

24+
#ifdef _AIX
25+
#include <sys/statfs.h>
26+
// <sys/vmount.h> depends on `uint` to be a typedef from <sys/types.h> to
27+
// `uint_t`; however, <sys/types.h> does not always declare `uint`. We provide
28+
// the typedef prior to including <sys/vmount.h> to work around this issue.
29+
typedef uint_t uint;
30+
#include <sys/vmount.h>
31+
#endif
32+
2433
#ifdef COMPILER_RT_HAS_UNAME
2534
#include <sys/utsname.h>
2635
#endif
@@ -258,6 +267,121 @@ COMPILER_RT_VISIBILITY FILE *lprofOpenFileEx(const char *ProfileName) {
258267
return f;
259268
}
260269

270+
#if defined(_AIX)
271+
// Return 1 (true) if the file descriptor Fd represents a file that is on a
272+
// local filesystem, otherwise return 0.
273+
static int isLocalFilesystem(int Fd) {
274+
struct statfs Vfs;
275+
if (fstatfs(Fd, &Vfs) != 0) {
276+
PROF_ERR("%s: fstatfs(%d) failed: %s\n", __func__, Fd, strerror(errno));
277+
return 0;
278+
}
279+
280+
int Ret;
281+
size_t BufSize = 2048u;
282+
char *Buf;
283+
int Tries = 3;
284+
while (Tries--) {
285+
Buf = malloc(BufSize);
286+
// mntctl returns -1 if `Buf` is `NULL`.
287+
Ret = mntctl(MCTL_QUERY, BufSize, Buf);
288+
if (Ret != 0)
289+
break;
290+
BufSize = *(unsigned int *)Buf;
291+
free(Buf);
292+
}
293+
294+
if (Ret != -1) {
295+
// Look for the correct vmount entry.
296+
char *CurObjPtr = Buf;
297+
while (Ret--) {
298+
struct vmount *Vp = (struct vmount *)CurObjPtr;
299+
_Static_assert(sizeof(Vfs.f_fsid) == sizeof(Vp->vmt_fsid),
300+
"fsid length mismatch");
301+
if (memcmp(&Vfs.f_fsid, &Vp->vmt_fsid, sizeof Vfs.f_fsid) == 0) {
302+
int Answer = (Vp->vmt_flags & MNT_REMOTE) == 0;
303+
free(Buf);
304+
return Answer;
305+
}
306+
CurObjPtr += Vp->vmt_length;
307+
}
308+
}
309+
310+
free(Buf);
311+
// There was an error in mntctl or vmount entry not found; "remote" is the
312+
// conservative answer.
313+
return 0;
314+
}
315+
#endif
316+
317+
static int isMmapSafe(int Fd) {
318+
if (getenv("LLVM_PROFILE_NO_MMAP")) // For testing purposes.
319+
return 0;
320+
#ifdef _AIX
321+
return isLocalFilesystem(Fd);
322+
#else
323+
return 1;
324+
#endif
325+
}
326+
327+
COMPILER_RT_VISIBILITY void lprofGetFileContentBuffer(FILE *F, uint64_t Length,
328+
ManagedMemory *Buf) {
329+
Buf->Status = MS_INVALID;
330+
if (isMmapSafe(fileno(F))) {
331+
Buf->Addr =
332+
mmap(NULL, Length, PROT_READ, MAP_SHARED | MAP_FILE, fileno(F), 0);
333+
if (Buf->Addr == MAP_FAILED)
334+
PROF_ERR("%s: mmap failed: %s\n", __func__, strerror(errno))
335+
else
336+
Buf->Status = MS_MMAP;
337+
return;
338+
}
339+
340+
if (getenv("LLVM_PROFILE_VERBOSE"))
341+
PROF_NOTE("%s\n", "could not use mmap; using fread instead");
342+
343+
void *Buffer = malloc(Length);
344+
if (!Buffer) {
345+
PROF_ERR("%s: malloc failed: %s\n", __func__, strerror(errno));
346+
return;
347+
}
348+
if (ftell(F) != 0) {
349+
PROF_ERR("%s: expecting ftell to return zero\n", __func__);
350+
free(Buffer);
351+
return;
352+
}
353+
354+
// Read the entire file into memory.
355+
size_t BytesRead = fread(Buffer, 1, Length, F);
356+
if (BytesRead != (size_t)Length) {
357+
PROF_ERR("%s: fread failed%s\n", __func__,
358+
feof(F) ? ": end of file reached" : "");
359+
free(Buffer);
360+
return;
361+
}
362+
363+
// Reading was successful, record the result in the Buf parameter.
364+
Buf->Addr = Buffer;
365+
Buf->Status = MS_MALLOC;
366+
}
367+
368+
COMPILER_RT_VISIBILITY
369+
void lprofReleaseBuffer(ManagedMemory *Buf, size_t Length) {
370+
switch (Buf->Status) {
371+
case MS_MALLOC:
372+
free(Buf->Addr);
373+
break;
374+
case MS_MMAP:
375+
(void)munmap(Buf->Addr, Length);
376+
break;
377+
default:
378+
PROF_ERR("%s: Buffer has invalid state: %d\n", __func__, Buf->Status);
379+
break;
380+
}
381+
Buf->Addr = NULL;
382+
Buf->Status = MS_INVALID;
383+
}
384+
261385
COMPILER_RT_VISIBILITY const char *lprofGetPathPrefix(int *PrefixStrip,
262386
size_t *PrefixLen) {
263387
const char *Prefix = getenv("GCOV_PREFIX");

compiler-rt/lib/profile/InstrProfilingUtil.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ int lprofUnlockFileHandle(FILE *F);
3131
* lock for exclusive access. The caller will block
3232
* if the lock is already held by another process. */
3333
FILE *lprofOpenFileEx(const char *Filename);
34+
35+
enum MemoryStatus {
36+
MS_INVALID, // Addr is not a valid address
37+
MS_MMAP, // Addr was mmap'ed
38+
MS_MALLOC // Addr was malloc'ed
39+
};
40+
typedef struct {
41+
void *Addr;
42+
enum MemoryStatus Status;
43+
} ManagedMemory;
44+
45+
/* Read the content of a file using mmap or fread into a buffer.
46+
* Certain files (e.g. NFS mounted) cannot be opened reliably with mmap,
47+
* so we use fread in those cases. The corresponding lprofReleaseBuffer
48+
* will free/munmap the buffer.
49+
*/
50+
void lprofGetFileContentBuffer(FILE *F, uint64_t FileSize, ManagedMemory *Buf);
51+
void lprofReleaseBuffer(ManagedMemory *FileBuffer, size_t Length);
52+
3453
/* PS4 doesn't have setenv/getenv/fork. Define a shim. */
3554
#if __ORBIS__
3655
#include <sys/types.h>

compiler-rt/test/profile/Posix/instrprof-fork.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
// RUN: %clang_pgogen=%t.profdir -o %t -O2 %s
44
// RUN: %run %t
55
// RUN: llvm-profdata show --all-functions --counts %t.profdir/default_*.profraw | FileCheck %s
6+
// RUN: rm -fr %t.profdir
7+
// RUN: env LLVM_PROFILE_NO_MMAP=1 %run %t
8+
// RUN: llvm-profdata show --all-functions --counts %t.profdir/default_*.profraw | FileCheck %s
9+
610
//
711
// CHECK: func1:
8-
// CHECK: Block counts: [4]
12+
// CHECK: Block counts: [21]
913
// CHECK: func2:
10-
// CHECK: Block counts: [1]
14+
// CHECK: Block counts: [10]
1115

1216
#include <sys/wait.h>
1317
#include <unistd.h>
@@ -16,17 +20,23 @@ __attribute__((noinline)) void func1() {}
1620
__attribute__((noinline)) void func2() {}
1721

1822
int main(void) {
19-
// child | parent
20-
int status; // func1 func2 | func1 func2
21-
func1(); // +1 | +1 (*)
22-
pid_t pid = fork(); // |
23-
if (pid == -1) // |
24-
return 1; // |
25-
if (pid == 0) // |
26-
func2(); // +1 |
27-
func1(); // +1 | +1
28-
if (pid) // ------------+------------
29-
wait(&status); // 2 1 | 2 0
30-
return 0; // (*) the child inherits counter values prior to fork
31-
// from the parent in non-continuous mode.
23+
// child | parent
24+
// func1 func2 | func1 func2
25+
func1(); // +10 | +1 (*)
26+
int i = 10; // |
27+
while (i-- > 0) { // |
28+
pid_t pid = fork(); // |
29+
if (pid == -1) // |
30+
return 1; // |
31+
if (pid == 0) { // |
32+
func2(); // +10 |
33+
func1(); // +10 |
34+
return 0; // |
35+
} // |
36+
} // ------------+------------
37+
int status; // 20 10 | 1 0
38+
i = 10; // (*) the child inherits counter values prior to fork
39+
while (i-- > 0) // from the parent in non-continuous mode.
40+
wait(&status);
41+
return 0;
3242
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: mkdir -p %t.d && cd %t.d
2+
// RUN: rm -f *.profraw
3+
// RUN: %clang_pgogen %s -o a.out
4+
5+
// Need to run a.out twice. On the second time, a merge will occur, which will
6+
// trigger an mmap.
7+
// RUN: ./a.out
8+
// RUN: llvm-profdata show default_*.profraw --all-functions --counts --memop-sizes 2>&1 | FileCheck %s -check-prefix=PROFDATA
9+
// RUN: env LLVM_PROFILE_NO_MMAP=1 LLVM_PROFILE_VERBOSE=1 ./a.out 2>&1 | FileCheck %s
10+
// RUN: llvm-profdata show default_*.profraw --all-functions --counts --memop-sizes 2>&1 | FileCheck %s -check-prefix=PROFDATA2
11+
12+
// CHECK: could not use mmap; using fread instead
13+
// PROFDATA: Block counts: [1]
14+
// PROFDATA: [ 0, 0, 1 ]
15+
// PROFDATA: Maximum function count: 1
16+
// PROFDATA2: Block counts: [2]
17+
// PROFDATA2: [ 0, 0, 2 ]
18+
// PROFDATA2: Maximum function count: 2
19+
20+
int ar[8];
21+
int main() {
22+
__builtin_memcpy(ar, ar + 2, ar[0]);
23+
__builtin_memcpy(ar, ar + 2, ar[2]);
24+
return ar[2];
25+
}

0 commit comments

Comments
 (0)