Darwin: Rewrite host PCH support [PR 55610].
authorIain Sandoe <iain@sandoe.co.uk>
Sat, 13 Nov 2021 12:39:09 +0000 (12:39 +0000)
committerIain Sandoe <iain@sandoe.co.uk>
Thu, 2 Dec 2021 19:44:44 +0000 (19:44 +0000)
We need to revise the PCH memory allocation scheme to enable
support for PIE on aarch64.  The rewrite uses a similar scheme
to the one used on Linux.

We attempt to identify VM segments for each arch/OS version that
are always available to the compiler (note this is not general,
it only needs to work for the cc1* exes).

If we cannot find the preferred segment we fall back to allowing
the kernel to supply one - this is more likely to fail when the
PCH read-in occurs (but that is trapped).

In doing this we obviate the need to unmap any part of the
compiler __DATA segment - thus fixing PR 55610.

Signed-off-by: Iain Sandoe <iain@sandoe.co.uk>
gcc/ChangeLog:

PR target/55610
* config/host-darwin.c (TRY_EMPTY_VM_SPACE,
SAFE_ALLOC_SIZE): New.
(darwin_gt_pch_get_address): Rewrite to use nominated
memory segments rather than part of the compiler __DATA
segment.
(darwin_gt_pch_use_address): Likewise.

gcc/config/host-darwin.c

index 14a01fe..559b919 100644 (file)
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
+#include "options.h"
 #include "diagnostic-core.h"
 #include "config/host-darwin.h"
-
-/* Yes, this is really supposed to work.  */
-/* This allows for a pagesize of 16384, which we have on Darwin20, but should
-   continue to work OK for pagesize 4096 which we have on earlier versions.
-   The size is 1 (binary) Gb.  */
-static char pch_address_space[65536*16384] __attribute__((aligned (16384)));
-
-/* Return the address of the PCH address space, if the PCH will fit in it.  */
+#include <errno.h>
+
+/* For Darwin (macOS only) platforms, without ASLR (PIE) enabled on the
+   binaries, the following VM addresses are expected to be available.
+   NOTE, that for aarch64, ASLR is always enabled - but the VM address
+   mentioned below is available (at least on Darwin20).
+
+   The spaces should all have 512Mb available c.f. PCH files for large
+   C++ or Objective-C in the range of 150Mb for 64b hosts.
+
+   We also try to steer clear of places already used for sanitizers.  */
+
+#define PAGE_SZ 4096
+#if defined(__x86_64) && defined(__LP64__)
+# define TRY_EMPTY_VM_SPACE    0x180000000000ULL
+# define SAFE_ALLOC_SIZE       0x20000000
+#elif defined(__x86_64)
+# define TRY_EMPTY_VM_SPACE    0x00006fe00000ULL
+# define SAFE_ALLOC_SIZE       0x20000000
+#elif defined(__i386)
+# define TRY_EMPTY_VM_SPACE    0x00006fe00000ULL
+# define SAFE_ALLOC_SIZE       0x20000000
+#elif defined(__POWERPC__) && defined(__LP64__)
+# define TRY_EMPTY_VM_SPACE    0x180000000000ULL
+# define SAFE_ALLOC_SIZE       0x20000000
+#elif defined(__POWERPC__)
+# define TRY_EMPTY_VM_SPACE    0x00006fe00000ULL
+# define SAFE_ALLOC_SIZE       0x20000000
+#elif defined(__aarch64__)
+# undef PAGE_SZ
+# define PAGE_SZ 16384
+# define TRY_EMPTY_VM_SPACE    0x180000000000ULL
+# define SAFE_ALLOC_SIZE       0x20000000
+#else
+# error "unknown Darwin target"
+#endif
+
+/* Try to map a known position in the VM.  The current PCH implementation
+   can adjust values at write-time, but not at read-time thus we need to
+   pick up the same position when reading as we got at write-time.  */
 
 void *
-darwin_gt_pch_get_address (size_t sz, int fd ATTRIBUTE_UNUSED)
+darwin_gt_pch_get_address (size_t sz, int fd)
 {
-  if (sz <= sizeof (pch_address_space))
-    return pch_address_space;
-  else
-    return NULL;
+  if (sz > SAFE_ALLOC_SIZE)
+    {
+      error ("PCH memory request exceeds the available space");
+      return NULL;
+    }
+
+  /* Now try with the constraint that we really want this address...  */
+  void *addr = mmap ((void *)TRY_EMPTY_VM_SPACE, sz, PROT_READ | PROT_WRITE,
+                    MAP_PRIVATE | MAP_FIXED, fd, 0);
+
+  if (addr != (void *) MAP_FAILED)
+    munmap (addr, sz);
+
+  /* This ought to be the only alternative to failure, but there are comments
+     that suggest some versions of mmap can be buggy and return a different
+     value.  */
+  if (addr == (void *) TRY_EMPTY_VM_SPACE)
+    return addr;
+
+  warning (OPT_Winvalid_pch, "PCH memory [fixed at %p] is not available %m",
+          (void *) TRY_EMPTY_VM_SPACE);
+
+  /* OK try to find a space without the constraint.  */
+  addr = mmap ((void *) TRY_EMPTY_VM_SPACE, sz, PROT_READ | PROT_WRITE,
+              MAP_PRIVATE, fd, 0);
+
+  /* If we failed this time, that means there is *no* large enough free
+     space.  */
+  if (addr == (void *) MAP_FAILED)
+    {
+      error ("no memory is available for PCH : %m");
+      return NULL;
+    }
+
+  /* Unmap the area before returning.  */
+  munmap (addr, sz);
+
+  /* If we got the exact area we requested, then that's great.  */
+  if (TRY_EMPTY_VM_SPACE && addr == (void *) TRY_EMPTY_VM_SPACE)
+    return addr;
+
+  warning (OPT_Winvalid_pch, "PCH memory at %p is not available",
+         (void *) TRY_EMPTY_VM_SPACE);
+
+  /* Otherwise, we need to try again but put some buffer space first.  */
+  size_t buffer_size = 32 * 1024 * 1024;
+  void *buffer = mmap (0, buffer_size, PROT_NONE,
+                      MAP_PRIVATE | MAP_ANON, -1, 0);
+  addr = mmap ((void *)TRY_EMPTY_VM_SPACE, sz, PROT_READ | PROT_WRITE,
+               MAP_PRIVATE, fd, 0);
+  if (buffer != (void *) MAP_FAILED)
+    munmap (buffer, buffer_size);
+
+  if (addr == (void *) MAP_FAILED)
+    {
+      error ("PCH memory not available %m");
+      return NULL;
+    }
+
+  warning (OPT_Winvalid_pch, "PCH memory at %p used instead", addr);
+  munmap (addr, sz);
+  return addr;
 }
 
-/* Check ADDR and SZ for validity, and deallocate (using munmap) that part of
-   pch_address_space beyond SZ.  */
+/* Try to mmap the PCH file at ADDR for SZ bytes at OFF offset in the file.
+   If we succeed return 1, if we cannot mmap the desired address, then we
+   fail with -1.  */
 
 int
 darwin_gt_pch_use_address (void *addr, size_t sz, int fd, size_t off)
 {
-  const size_t pagesize = getpagesize();
-  void *mmap_result;
-  int ret;
+  void *mapped_addr;
+
+  /* We're called with size == 0 if we're not planning to load a PCH
+     file at all.  This allows the hook to free any static space that
+     we might have allocated at link time.  */
+  if (sz == 0)
+    return -1;
 
-  gcc_assert ((size_t)pch_address_space % pagesize == 0
-             && sizeof (pch_address_space) % pagesize == 0);
-  
-  ret = (addr == pch_address_space && sz <= sizeof (pch_address_space));
-  if (! ret)
-    sz = 0;
+  gcc_checking_assert (!(off % PAGE_SZ));
+  if (addr != (void *) TRY_EMPTY_VM_SPACE)
+    warning (OPT_Winvalid_pch, "PCH at %p does not use the default position",
+            addr);
 
-  /* Round the size to a whole page size.  Normally this is a no-op.  */
-  sz = (sz + pagesize - 1) / pagesize * pagesize;
+  /* Try to map the file with MAP_PRIVATE and FIXED.  */
+  mapped_addr = mmap (addr, sz, PROT_READ | PROT_WRITE,
+                     MAP_PRIVATE | MAP_FIXED, fd, (off_t) off);
 
-  if (munmap (pch_address_space + sz, sizeof (pch_address_space) - sz) != 0)
-    fatal_error (input_location,
-                "could not unmap %<pch_address_space%>: %m");
+  /* Hopefully, we succeed.  */
+  if (mapped_addr == addr)
+    return 1;
 
-  if (ret)
+  warning (OPT_Winvalid_pch, "PCH private mmap of written position (%p)"
+          " failed [errno %d] %m", addr, errno);
+
+  if (mapped_addr != (void *) MAP_FAILED)
+    munmap (mapped_addr, sz);
+
+  /* Try to make an anonymous private mmap at the desired location.  */
+  mapped_addr = mmap (addr, sz, PROT_READ | PROT_WRITE,
+                     MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, (off_t)0);
+
+  if (mapped_addr != addr)
     {
-      mmap_result = mmap (addr, sz,
-                         PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED,
-                         fd, off);
+      warning (OPT_Winvalid_pch, "PCH anon mmap at written position (%p)"
+              " failed [errno %d] %m", addr, errno);
+      if (mapped_addr != (void *) MAP_FAILED)
+           munmap (mapped_addr, sz);
+      return -1;
+    }
+
+  if (lseek (fd, off, SEEK_SET) == (off_t) -1)
+    return -1;
 
-      /* The file might not be mmap-able.  */
-      ret = mmap_result != (void *) MAP_FAILED;
+  while (sz)
+    {
+      ssize_t nbytes;
 
-      /* Sanity check for broken MAP_FIXED.  */
-      gcc_assert (!ret || mmap_result == addr);
+      nbytes = read (fd, addr, MIN (sz, (size_t) -1 >> 1));
+      if (nbytes <= 0)
+       return -1;
+      addr = (char *) addr + nbytes;
+      sz -= nbytes;
     }
 
-  return ret;
+  return 1;
 }