Attempt to fix #2512 and #2063; add +RTS -xm<address> -RTS option
authorSimon Marlow <marlowsd@gmail.com>
Mon, 17 Nov 2008 12:05:56 +0000 (12:05 +0000)
committerSimon Marlow <marlowsd@gmail.com>
Mon, 17 Nov 2008 12:05:56 +0000 (12:05 +0000)
On x86_64, the RTS needs to allocate memory in the low 2Gb of the
address space.  On Linux we can do this with MAP_32BIT, but sometimes
this doesn't work (#2512) and other OSs don't support it at all
(#2063).  So to work around this:

  - Try MAP_32BIT first, if available.

  - Otherwise, try allocating memory from a fixed address (by default
    1Gb)

  - We now provide an option to configure the address to allocate
    from.  This allows a workaround on machines where the default
    breaks, and also provides a way for people to test workarounds
    that we can incorporate in future releases.

docs/users_guide/runtime_control.xml
includes/RtsFlags.h
rts/Linker.c
rts/RtsFlags.c

index 5039526..e0dd420 100644 (file)
          own signal handlers.</para>
        </listitem>
      </varlistentry>
+
+     <varlistentry>
+       <term><option>-xm<replaceable>address</replaceable></option>
+       <indexterm><primary><option>-xm</option></primary><secondary>RTS
+       option</secondary></indexterm></term>
+       <listitem>
+         <para>
+           WARNING: this option is for working around memory
+           allocation problems only.  Do not use unless GHCi fails
+           with a message like &ldquo;<literal>failed to mmap() memory below 2Gb</literal>&rdquo;.  If you need to use this option to get GHCi working
+           on your machine, please file a bug.
+         </para>
+         
+         <para>
+           On 64-bit machines, the RTS needs to allocate memory in the
+           low 2Gb of the address space.  Support for this across
+           different operating systems is patchy, and sometimes fails.
+           This option is there to give the RTS a hint about where it
+           should be able to allocate memory in the low 2Gb of the
+           address space.  For example, <literal>+RTS -xm20000000
+           -RTS</literal> would hint that the RTS should allocate
+           starting at the 0.5Gb mark.  The default is to use the OS's
+           built-in support for allocating memory in the low 2Gb if
+           available (e.g. <literal>mmap</literal>
+           with <literal>MAP_32BIT</literal> on Linux), or
+           otherwise <literal>-xm40000000</literal>.
+         </para>
+       </listitem>
+     </varlistentry>
     </variablelist>
   </sect2>
 
index 11133ef..55b00bb 100644 (file)
@@ -121,6 +121,8 @@ struct CONCURRENT_FLAGS {
 struct MISC_FLAGS {
     int tickInterval;     /* in milliseconds */
     rtsBool install_signal_handlers;
+    StgWord linkerMemBase;       /* address to ask the OS for memory
+                                  * for the linker, NULL ==> off */
 };
 
 #ifdef PAR
index cdeb569..357023d 100644 (file)
@@ -28,6 +28,7 @@
 #include "Sparks.h"
 #include "RtsTypeable.h"
 #include "Timer.h"
+#include "Trace.h"
 
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -168,6 +169,50 @@ static void machoInitSymbolsWithoutUnderscore( void );
  */
 #define X86_64_ELF_NONPIC_HACK 1
 
+/* Link objects into the lower 2Gb on x86_64.  GHC assumes the
+ * small memory model on this architecture (see gcc docs,
+ * -mcmodel=small).
+ *
+ * MAP_32BIT not available on OpenBSD/amd64
+ */
+#if defined(x86_64_HOST_ARCH) && defined(MAP_32BIT)
+#define TRY_MAP_32BIT MAP_32BIT
+#else
+#define TRY_MAP_32BIT 0
+#endif
+
+/*
+ * Due to the small memory model (see above), on x86_64 we have to map
+ * all our non-PIC object files into the low 2Gb of the address space
+ * (why 2Gb and not 4Gb?  Because all addresses must be reachable
+ * using a 32-bit signed PC-relative offset). On Linux we can do this
+ * using the MAP_32BIT flag to mmap(), however on other OSs
+ * (e.g. *BSD, see #2063, and also on Linux inside Xen, see #2512), we
+ * can't do this.  So on these systems, we have to pick a base address
+ * in the low 2Gb of the address space and try to allocate memory from
+ * there.
+ *
+ * We pick a default address based on the OS, but also make this
+ * configurable via an RTS flag (+RTS -xm)
+ */
+#if defined(x86_64_HOST_ARCH)
+
+#if defined(MAP_32BIT)
+// Try to use MAP_32BIT
+#define MMAP_32BIT_BASE_DEFAULT 0
+#else
+// A guess: 1Gb.
+#define MMAP_32BIT_BASE_DEFAULT 0x40000000
+#endif
+
+static void *mmap_32bit_base = MMAP_32BIT_BASE_DEFAULT;
+#endif
+
+/* MAP_ANONYMOUS is MAP_ANON on some systems, e.g. OpenBSD */
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
 /* -----------------------------------------------------------------------------
  * Built-in symbols from the RTS
  */
@@ -994,6 +1039,13 @@ initLinker( void )
     dl_prog_handle = dlopen(NULL, RTLD_LAZY);
 #   endif /* RTLD_DEFAULT */
 #   endif
+
+#if defined(x86_64_HOST_ARCH)
+    if (RtsFlags.MiscFlags.linkerMemBase != 0) {
+        // User-override for mmap_32bit_base
+        mmap_32bit_base = (void*)RtsFlags.MiscFlags.linkerMemBase;
+    }
+#endif
 }
 
 /* -----------------------------------------------------------------------------
@@ -1240,6 +1292,61 @@ void ghci_enquire ( char* addr )
 static unsigned int PLTSize(void);
 #endif
 
+static void *
+mmapForLinker (size_t bytes, nat flags, int fd)
+{
+   void *map_addr = NULL;
+   void *result;
+
+mmap_again:
+
+#if defined(x86_64_HOST_ARCH)
+   if (mmap_32bit_base != 0) {
+       map_addr = mmap_32bit_base;
+   }
+#endif
+
+   result = mmap(map_addr, bytes, PROT_EXEC|PROT_READ|PROT_WRITE,
+                   MAP_PRIVATE|TRY_MAP_32BIT|flags, fd, 0);
+
+   if (result == MAP_FAILED) {
+       sysErrorBelch("mmap");
+       stg_exit(EXIT_FAILURE);
+   }
+   
+#if defined(x86_64_HOST_ARCH)
+   if (mmap_32bit_base != 0) {
+       if (result == map_addr) {
+           mmap_32bit_base = map_addr + bytes;
+       } else {
+           if ((W_)result > 0x80000000) {
+               // oops, we were given memory over 2Gb
+               // ... try allocating memory somewhere else?;
+               barf("loadObj: failed to mmap() memory below 2Gb; asked for %lu bytes at 0x%p, got 0x%p.  Try specifying an address with +RTS -xm<addr> -RTS", bytes, map_addr, result);
+           } else {
+               // hmm, we were given memory somewhere else, but it's
+               // still under 2Gb so we can use it.  Next time, ask
+               // for memory right after the place we just got some
+               mmap_32bit_base = (void*)result + bytes;
+           }
+       }
+   } else {
+       if ((W_)result > 0x80000000) {
+           // oops, we were given memory over 2Gb
+           // ... try allocating memory somewhere else?;
+           debugTrace(DEBUG_linker,"MAP_32BIT didn't work; gave us %lu bytes at 0x%p", bytes, result);
+           munmap(result, bytes);
+           
+           // Set a base address and try again... (guess: 1Gb)
+           mmap_32bit_base = (void*)0x40000000;
+           goto mmap_again;
+       }
+   }
+#endif
+
+   return result;
+}
+
 /* -----------------------------------------------------------------------------
  * Load an obj (populate the global symbol table, but don't resolve yet)
  *
@@ -1253,7 +1360,6 @@ loadObj( char *path )
    int r, n;
 #ifdef USE_MMAP
    int fd, pagesize;
-   void *map_addr = NULL;
 #else
    FILE *f;
 #endif
@@ -1340,27 +1446,14 @@ loadObj( char *path )
 
    n = ROUND_UP(oc->fileSize, pagesize);
 
-   /* Link objects into the lower 2Gb on x86_64.  GHC assumes the
-    * small memory model on this architecture (see gcc docs,
-    * -mcmodel=small).
-    *
-    * MAP_32BIT not available on OpenBSD/amd64
-    */
-#if defined(x86_64_HOST_ARCH) && defined(MAP_32BIT)
-#define EXTRA_MAP_FLAGS MAP_32BIT
-#else
-#define EXTRA_MAP_FLAGS 0
-#endif
-
-   /* MAP_ANONYMOUS is MAP_ANON on some systems, e.g. OpenBSD */
-#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
-#define MAP_ANONYMOUS MAP_ANON
-#endif
-
+#ifdef ia64_HOST_ARCH
    oc->image = mmap(map_addr, n, PROT_EXEC|PROT_READ|PROT_WRITE,
-                   MAP_PRIVATE|EXTRA_MAP_FLAGS, fd, 0);
+                   MAP_PRIVATE|TRY_MAP_32BIT, fd, 0);
    if (oc->image == MAP_FAILED)
       barf("loadObj: can't map `%s'", path);
+#else
+   oc->image = mmapForLinker(n, 0, fd);
+#endif
 
    close(fd);
 
@@ -1632,21 +1725,8 @@ static int ocAllocateSymbolExtras( ObjectCode* oc, int count, int first )
      */
     if( m > n ) // we need to allocate more pages
     {
-        oc->symbol_extras = mmap (NULL, sizeof(SymbolExtra) * count,
-                                  PROT_EXEC|PROT_READ|PROT_WRITE,
-                                  MAP_PRIVATE|MAP_ANONYMOUS|EXTRA_MAP_FLAGS,
-                                  0, 0);
-        if (oc->symbol_extras == MAP_FAILED)
-        {
-            errorBelch( "Unable to mmap() for jump islands\n" );
-            return 0;
-        }
-#ifdef x86_64_HOST_ARCH
-        if ((StgWord)oc->symbol_extras > 0x80000000)
-        {
-            barf("mmap() returned memory outside 2Gb");
-        }
-#endif
+        oc->symbol_extras = mmapForLinker(sizeof(SymbolExtra) * count, 
+                                          MAP_ANONYMOUS, 0);
     }
     else
     {
index 497cd88..1cbd569 100644 (file)
@@ -208,6 +208,7 @@ void initRtsFlagsDefaults(void)
     RtsFlags.ConcFlags.ctxtSwitchTime  = 20;  /* In milliseconds */
 
     RtsFlags.MiscFlags.install_signal_handlers = rtsTrue;
+    RtsFlags.MiscFlags.linkerMemBase    = 0;
 
 #ifdef THREADED_RTS
     RtsFlags.ParFlags.nNodes           = 1;
@@ -476,6 +477,10 @@ usage_text[] = {
 #if defined(GRAN)  /* ToDo: fill in decent Docu here */
 "  -b...     All GranSim options start with -b; see GranSim User's Guide for details",
 #endif
+#if defined(x86_64_HOST_ARCH)
+"  -xm       Base address to mmap memory in the GHCi linker",
+"            (hex; must be <80000000)",
+#endif
 #if defined(USE_PAPI)
 "  -aX       CPU performance counter measurements using PAPI",
 "            (use with the -s<file> option).  X is one of:",
@@ -1259,7 +1264,22 @@ error = rtsTrue;
                     }
                     break;
 
-                  case 'c': /* Debugging tool: show current cost centre on an exception */
+#if defined(x86_64_HOST_ARCH)
+                case 'm': /* linkerMemBase */
+                    if (rts_argv[arg][3] != '\0') {
+                        RtsFlags.MiscFlags.linkerMemBase
+                            = strtol(rts_argv[arg]+3, (char **) NULL, 16);
+                        if (RtsFlags.MiscFlags.linkerMemBase > 0x80000000) {
+                            errorBelch("-xm: value must be <80000000");
+                            error = rtsTrue;
+                        }
+                    } else {
+                        RtsFlags.MiscFlags.linkerMemBase = 0;
+                    }
+                    break;
+#endif
+
+                case 'c': /* Debugging tool: show current cost centre on an exception */
                     PROFILING_BUILD_ONLY(
                        RtsFlags.ProfFlags.showCCSOnException = rtsTrue;
                        );