[PATCH] Solaris/x64 support

  • From: Dmitri Shubin <sbn@xxxxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Wed, 12 Sep 2012 16:35:00 +0400

Hello!

Here is the patch that adds (partial) Solaris/x64 support.
I tested it on Solaris 10 and Solaris 11.

Some caveats:
 - only static library is supported;
- when building executable (e.g. luajit interpreter itself) it's text segment should be shifted as high as possible (using mapfile), currently constant 0x70280000 is hardcoded; - mmap(MAP_FIXED) is used to allocate pages in low 2GiB so one should verify that no one else in the process does similar thing; - if executable is built using SunStudio (i.e. stack unwinder from libc.so is used) SOLARIS_STD_UNWINDER macro should be defined; - page munmap()'ing is quite inefficient, although we didn't see any real performance problems with it.

Attached patch against 76ab3709da40dd3cc3ab9223dde33050430cd789
diff --git a/src/Makefile b/src/Makefile
index bfb03e9..eefb0c5 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -310,6 +310,12 @@ else
     ifneq (PS3,$(TARGET_SYS))
       TARGET_XLDFLAGS+= -Wl,-E
     endif
+  else
+    TARGET_XSHLDFLAGS= -shared -fPIC -Wl,-h,$(TARGET_SONAME) -Wl,-zdefs
+    ifeq (x64,$(TARGET_LJARCH))
+      TARGET_XLDFLAGS+= -Wl,-M,luajit-solaris64.mapfile
+      TARGET_XLIBS+= -lumem
+    endif
   endif
   ifeq (Linux,$(TARGET_SYS))
     TARGET_XLIBS+= -ldl
diff --git a/src/Makefile.dep b/src/Makefile.dep
index 94a963b..ff78695 100644
--- a/src/Makefile.dep
+++ b/src/Makefile.dep
@@ -66,7 +66,7 @@ lj_ccallback.o: lj_ccallback.c lj_obj.h lua.h luaconf.h 
lj_def.h \
  lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_tab.h lj_state.h lj_frame.h \
  lj_bc.h lj_ctype.h lj_cconv.h lj_ccall.h lj_ccallback.h lj_target.h \
  lj_target_*.h lj_mcode.h lj_jit.h lj_ir.h lj_trace.h lj_dispatch.h \
- lj_traceerr.h lj_vm.h
+ lj_traceerr.h lj_vm.h lj_alloc.h
 lj_cconv.o: lj_cconv.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \
  lj_err.h lj_errmsg.h lj_tab.h lj_ctype.h lj_gc.h lj_cdata.h lj_cconv.h \
  lj_ccallback.h
@@ -126,7 +126,7 @@ lj_lib.o: lj_lib.c lauxlib.h lua.h luaconf.h lj_obj.h 
lj_def.h lj_arch.h \
  lj_dispatch.h lj_jit.h lj_ir.h lj_vm.h lj_strscan.h lj_lib.h
 lj_mcode.o: lj_mcode.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \
  lj_gc.h lj_jit.h lj_ir.h lj_mcode.h lj_trace.h lj_dispatch.h lj_bc.h \
- lj_traceerr.h lj_vm.h
+ lj_traceerr.h lj_vm.h lj_alloc.h
 lj_meta.o: lj_meta.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \
  lj_err.h lj_errmsg.h lj_str.h lj_tab.h lj_meta.h lj_frame.h lj_bc.h \
  lj_vm.h lj_strscan.h
diff --git a/src/lj_alloc.c b/src/lj_alloc.c
index 82b4e5b..52bb93c 100644
--- a/src/lj_alloc.c
+++ b/src/lj_alloc.c
@@ -34,6 +34,16 @@
 
 #ifndef LUAJIT_USE_SYSMALLOC
 
+#if LJ_TARGET_SOLARIS
+#include <sys/vmem.h>
+#include <atomic.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <umem.h>
+#include <unistd.h>
+#endif
+
 #define MAX_SIZE_T             (~(size_t)0)
 #define MALLOC_ALIGNMENT       ((size_t)8U)
 
@@ -235,6 +245,240 @@ static LJ_AINLINE void *CALL_MMAP(size_t size)
   return CMFAIL;
 }
 
+#elif LJ_TARGET_SOLARIS
+
+#define MMAP_REGION_START      ((uintptr_t)0x10000)
+#define MMAP_REGION_END                ((uintptr_t)0x70280000)
+
+struct lj_memseg
+{
+  struct lj_memseg *next;
+  void *ptr;
+  size_t size;
+};
+
+static volatile vmem_t *vmp = NULL;
+static volatile umem_cache_t *memseg_cache = NULL;
+
+static void INIT_MMAP(void)
+{
+  vmem_t *new_vmp;
+  umem_cache_t *new_memseg_cache;
+  size_t pagesize;
+  void *p;
+  static char memseg_cache_name[] = "luajit_memseg";
+
+  if (vmp != NULL) {
+    return;
+  }
+
+  pagesize = sysconf(_SC_PAGESIZE);
+
+  /* init umem */
+  if ((p = umem_alloc(1, UMEM_DEFAULT)) != NULL) {
+    umem_free(p, 1);
+  }
+
+  /* reserve virtual space */
+  if (mmap((void *) MMAP_REGION_START,
+           MMAP_REGION_END - MMAP_REGION_START,
+           PROT_NONE,
+           MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE | MAP_FIXED,
+           -1, 0) == MAP_FAILED) {
+    fprintf(stderr, "INIT_MMAP(): mmap() failed: %s [%d]\n", strerror(errno), 
errno);
+    return;
+  }
+
+  /* create vmem arena */
+  new_vmp = vmem_create(
+      "luajit",
+      (void *) MMAP_REGION_START,           /* start */
+      MMAP_REGION_END - MMAP_REGION_START,  /* size */
+      pagesize,                             /* quantum */
+      NULL, NULL, NULL, 0,
+      VM_NOSLEEP | VMC_NO_QCACHE | VMC_IDENTIFIER);
+
+  if (new_vmp == NULL) {
+    fprintf(stderr, "INIT_MMAP(): vmem arena creation failed\n");
+    return;
+  }
+
+  if (atomic_cas_ptr(&vmp, NULL, new_vmp) != NULL) {
+    /* someone was faster */
+    vmem_destroy(new_vmp);
+  }
+
+  /* create memseg cache */
+  if (memseg_cache != NULL) {
+    return;
+  }
+
+  new_memseg_cache = umem_cache_create(memseg_cache_name, sizeof(struct 
lj_memseg), 0,
+      NULL, NULL, NULL, NULL, NULL, 0);
+
+  if (new_memseg_cache == NULL) {
+    fprintf(stderr, "INIT_MMAP(): memseg cache creation failed\n");
+    return;
+  }
+
+  if (atomic_cas_ptr(&memseg_cache, NULL, new_memseg_cache) != NULL) {
+    /* someone was faster */
+    umem_cache_destroy(new_memseg_cache);
+  }
+}
+
+void *
+lj_mmap(size_t size, int prot)
+{
+  void *addr;
+
+  /* allocate address space */
+  addr = vmem_alloc((vmem_t *) vmp, size, VM_NOSLEEP);
+  if (LJ_UNLIKELY(addr == NULL)) {
+    fprintf(stderr, "lj_mmap(): vmem_alloc(%lu) failed\n", size);
+    return MAP_FAILED;
+  }
+
+  /* back by pages */
+  if (LJ_UNLIKELY(mmap(addr, size, prot, MMAP_FLAGS | MAP_FIXED, -1, 0) == 
MAP_FAILED)) {
+    fprintf(stderr, "lj_mmap(): mmap(%p, %lu, %d) failed: %s [%d]\n", addr, 
size, prot,
+            strerror(errno), errno);
+    vmem_free((vmem_t *) vmp, addr, size);
+    return MAP_FAILED;
+  }
+
+  return addr;
+}
+
+
+struct munmap_ctx
+{
+  void *ptr;
+  size_t size;
+  struct lj_memseg head;
+  int partial;
+};
+
+static void
+munmap_walker(void *arg, void *ptr, size_t size)
+{
+  struct munmap_ctx *ctx = (struct munmap_ctx *)arg;
+  struct lj_memseg *seg;
+  if (ctx->partial ||
+      ((char *)ptr + size <= (char *)ctx->ptr) || 
+      ((char *)ctx->ptr + ctx->size <= (char *)ptr)) {
+    return;
+  }
+
+  if ((char *)ptr < (char *)ctx->ptr ||
+      (char *)ctx->ptr + ctx->size < (char *)ptr + size) {
+    /* found partial free, remember it in ctx */
+    ctx->partial = 1;
+    ctx->ptr = ptr;
+    ctx->size = size;
+    return;
+  }
+
+  if (ctx->head.ptr == NULL) {
+    seg = &ctx->head;
+  }
+  else {
+    seg = umem_cache_alloc((umem_cache_t *) memseg_cache, UMEM_DEFAULT);
+    if (seg == NULL) {
+      fprintf(stderr, "munmap_walker: umem_cache_alloc() returned NULL\n");
+      return;
+    }
+  }
+
+  seg->ptr = ptr;
+  seg->size = size;
+  seg->next = ctx->head.next;
+
+  ctx->head.next = seg;
+}
+
+int
+lj_munmap(void *ptr, size_t size)
+{
+  struct munmap_ctx ctx;
+  struct lj_memseg *seg;
+
+  ctx.ptr = ptr;
+  ctx.size = size;
+  ctx.head.ptr = NULL;
+  ctx.head.next = &ctx.head;
+  ctx.partial = 0;
+
+  vmem_walk((vmem_t *) vmp, VMEM_ALLOC, &munmap_walker, &ctx);
+
+  if (LJ_UNLIKELY(ctx.head.ptr == NULL)) {  // oops!
+    fprintf(stderr, "lj_munmap: no segment found for (%p, %lu)\n", ptr, size);
+    return 0;
+  }
+
+  if (size == ctx.head.size) {
+    if (LJ_UNLIKELY(munmap(ptr, size) != 0)) {
+      fprintf(stderr, "munmap(%p, %lu) failed: %s [%d]\n", ptr, size, 
strerror(errno), errno);
+      return -1;
+    }
+    vmem_free((vmem_t *) vmp, ptr, size);
+    return 0;
+  }
+
+  if (ctx.partial) {
+    fprintf(stderr, "lj_munmap(%p, %lu): partial free of segment (%p, %lu)\n",
+      ptr, size, ctx.ptr, ctx.size);
+
+    /* free allocated segs */
+    for (seg = ctx.head.next; seg != &ctx.head;) {
+      struct lj_memseg *next_seg = seg->next;
+      umem_cache_free((umem_cache_t *) memseg_cache, seg);
+      seg = next_seg;
+    }
+
+    return -1;
+  }
+
+  /* ok, no partial free */
+  for (seg = &ctx.head;;) {
+    struct lj_memseg *next_seg = seg->next;
+
+    if (LJ_UNLIKELY(munmap(seg->ptr, seg->size) != 0)) {
+      fprintf(stderr, "munmap(%p, %lu) failed: %s [%d]\n", ptr, size, 
strerror(errno), errno);
+    }
+
+    vmem_free((vmem_t *) vmp, seg->ptr, seg->size);
+
+    if (next_seg == &ctx.head) {
+      break;
+    }
+
+    if (seg != &ctx.head) {
+        umem_cache_free((umem_cache_t *) memseg_cache, seg);
+    }
+
+    seg = next_seg;
+  }
+
+  return 0;
+}
+
+static LJ_AINLINE void *CALL_MMAP(size_t size)
+{
+  int olderr = errno;
+  void *ptr = lj_mmap(size, MMAP_PROT);
+  errno = olderr;
+  return (ptr != MAP_FAILED ? ptr : CMFAIL);
+}
+
+static LJ_AINLINE int CALL_MUNMAP(void *ptr, size_t size)
+{
+  int olderr = errno;
+  int ret = lj_munmap(ptr, size);
+  errno = olderr;
+  return ret;
+}
+
 #else
 
 #error "NYI: need an equivalent of MAP_32BIT for this 64 bit OS"
@@ -254,9 +498,12 @@ static LJ_AINLINE void *CALL_MMAP(size_t size)
 
 #endif
 
-#define INIT_MMAP()            ((void)0)
 #define DIRECT_MMAP(s)         CALL_MMAP(s)
 
+#if !LJ_TARGET_SOLARIS || !LJ_64
+
+#define INIT_MMAP()            ((void)0)
+
 static LJ_AINLINE int CALL_MUNMAP(void *ptr, size_t size)
 {
   int olderr = errno;
@@ -264,6 +511,7 @@ static LJ_AINLINE int CALL_MUNMAP(void *ptr, size_t size)
   errno = olderr;
   return ret;
 }
+#endif
 
 #if LJ_TARGET_LINUX
 /* Need to define _GNU_SOURCE to get the mremap prototype. */
diff --git a/src/lj_alloc.h b/src/lj_alloc.h
index f87a7cf..1b462e5 100644
--- a/src/lj_alloc.h
+++ b/src/lj_alloc.h
@@ -12,6 +12,12 @@
 LJ_FUNC void *lj_alloc_create(void);
 LJ_FUNC void lj_alloc_destroy(void *msp);
 LJ_FUNC void *lj_alloc_f(void *msp, void *ptr, size_t osize, size_t nsize);
+
+#if LJ_TARGET_SOLARIS && LJ_64
+LJ_FUNC void *lj_mmap(size_t size, int prot);
+LJ_FUNC int lj_munmap(void *ptr, size_t size);
+#endif
+
 #endif
 
 #endif
diff --git a/src/lj_arch.h b/src/lj_arch.h
index a203282..05180ee 100644
--- a/src/lj_arch.h
+++ b/src/lj_arch.h
@@ -33,6 +33,7 @@
 #define LUAJIT_OS_OSX          3
 #define LUAJIT_OS_BSD          4
 #define LUAJIT_OS_POSIX                5
+#define LUAJIT_OS_SOLARIS      6
 
 /* Select native target if no target defined. */
 #ifndef LUAJIT_TARGET
@@ -69,9 +70,10 @@
 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
       defined(__NetBSD__) || defined(__OpenBSD__)
 #define LUAJIT_OS      LUAJIT_OS_BSD
-#elif (defined(__sun__) && defined(__svr4__)) || defined(__solaris__) || \
-      defined(__CYGWIN__)
+#elif defined(__CYGWIN__)
 #define LUAJIT_OS      LUAJIT_OS_POSIX
+#elif (defined(__sun__) && defined(__svr4__)) || defined(__solaris__)
+#define LUAJIT_OS      LUAJIT_OS_SOLARIS
 #else
 #define LUAJIT_OS      LUAJIT_OS_OTHER
 #endif
@@ -89,6 +91,8 @@
 #define LJ_OS_NAME     "BSD"
 #elif LUAJIT_OS == LUAJIT_OS_POSIX
 #define LJ_OS_NAME     "POSIX"
+#elif LUAJUT_OS == LUAJIT_OS_SOLARIS
+#define LJ_OS_NAME     "Solaris"
 #else
 #define LJ_OS_NAME     "Other"
 #endif
@@ -97,6 +101,7 @@
 #define LJ_TARGET_LINUX                (LUAJIT_OS == LUAJIT_OS_LINUX)
 #define LJ_TARGET_OSX          (LUAJIT_OS == LUAJIT_OS_OSX)
 #define LJ_TARGET_IOS          (LJ_TARGET_OSX && LUAJIT_TARGET == 
LUAJIT_ARCH_ARM)
+#define LJ_TARGET_SOLARIS      (LUAJIT_OS == LUAJIT_OS_SOLARIS)
 #define LJ_TARGET_POSIX                (LUAJIT_OS > LUAJIT_OS_WINDOWS)
 #define LJ_TARGET_DLOPEN       LJ_TARGET_POSIX
 
diff --git a/src/lj_ccallback.c b/src/lj_ccallback.c
index 430643e..0da6ade 100644
--- a/src/lj_ccallback.c
+++ b/src/lj_ccallback.c
@@ -20,6 +20,7 @@
 #include "lj_mcode.h"
 #include "lj_trace.h"
 #include "lj_vm.h"
+#include "lj_alloc.h"
 
 /* -- Target-specific handling of callback slots -------------------------- */
 
@@ -228,8 +229,12 @@ static void callback_mcode_new(CTState *cts)
   if (!p)
     lj_err_caller(cts->L, LJ_ERR_FFI_CBACKOV);
 #elif LJ_TARGET_POSIX
+#if LJ_TARGET_SOLARIS && LJ_64
+  p = lj_mmap(sz, (PROT_READ|PROT_WRITE));
+#else
   p = mmap(NULL, sz, (PROT_READ|PROT_WRITE), MAP_PRIVATE|MAP_ANONYMOUS,
           -1, 0);
+#endif
   if (p == MAP_FAILED)
     lj_err_caller(cts->L, LJ_ERR_FFI_CBACKOV);
 #else
@@ -259,7 +264,11 @@ void lj_ccallback_mcode_free(CTState *cts)
   VirtualFree(p, 0, MEM_RELEASE);
   UNUSED(sz);
 #elif LJ_TARGET_POSIX
+#if LJ_TARGET_SOLARIS && LJ_64
+  lj_munmap(p, sz);
+#else
   munmap(p, sz);
+#endif
 #else
   lj_mem_free(cts->g, p, sz);
 #endif
diff --git a/src/lj_def.h b/src/lj_def.h
index b52d5d1..ec5fba0 100644
--- a/src/lj_def.h
+++ b/src/lj_def.h
@@ -113,7 +113,11 @@ typedef uintptr_t BloomFilter;
 
 #if defined(__GNUC__)
 
+#if SOLARIS_STD_UNWINDER
+#define LJ_NORET
+#else
 #define LJ_NORET       __attribute__((noreturn))
+#endif
 #define LJ_ALIGN(n)    __attribute__((aligned(n)))
 #define LJ_INLINE      inline
 #define LJ_AINLINE     inline __attribute__((always_inline))
diff --git a/src/lj_err.c b/src/lj_err.c
index 60d8fe1..7148cd6 100644
--- a/src/lj_err.c
+++ b/src/lj_err.c
@@ -235,7 +235,11 @@ LJ_FUNCA int lj_err_unwind_dwarf(int version, int actions,
   if (version != 1)
     return _URC_FATAL_PHASE1_ERROR;
   UNUSED(uexclass);
+#if SOLARIS_STD_UNWINDER
+  cf = (void *)((char *)_Unwind_GetCFA(ctx) - 80);
+#else
   cf = (void *)_Unwind_GetCFA(ctx);
+#endif
   L = cframe_L(cf);
   if ((actions & _UA_SEARCH_PHASE)) {
 #if LJ_UNWIND_EXT
diff --git a/src/lj_mcode.c b/src/lj_mcode.c
index 34405b5..c97c087 100644
--- a/src/lj_mcode.c
+++ b/src/lj_mcode.c
@@ -16,6 +16,7 @@
 #endif
 #if LJ_HASJIT || LJ_HASFFI
 #include "lj_vm.h"
+#include "lj_alloc.h"
 #endif
 
 /* -- OS-specific functions ----------------------------------------------- */
@@ -98,7 +99,13 @@ static void mcode_setprot(void *p, size_t sz, DWORD prot)
 
 static void *mcode_alloc_at(jit_State *J, uintptr_t hint, size_t sz, int prot)
 {
+/* Solaris/X64 ignores hint and returns memory above 1<<48 so use custom
+   mmap()-er that maps to low 2G */
+#if LJ_TARGET_SOLARIS && LJ_64
+  void *p = lj_mmap(sz, prot);
+#else
   void *p = mmap((void *)hint, sz, prot, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+#endif
   if (p == MAP_FAILED && !hint)
     lj_trace_err(J, LJ_TRERR_MCODEAL);
   return p;
@@ -107,7 +114,11 @@ static void *mcode_alloc_at(jit_State *J, uintptr_t hint, 
size_t sz, int prot)
 static void mcode_free(jit_State *J, void *p, size_t sz)
 {
   UNUSED(J);
+#if LJ_TARGET_SOLARIS && LJ_64
+  lj_munmap(p, sz);
+#else
   munmap(p, sz);
+#endif
 }
 
 static void mcode_setprot(void *p, size_t sz, int prot)
@@ -223,8 +234,8 @@ static void *mcode_alloc(jit_State *J, size_t sz)
       if (mcode_validptr(p)) {
        if ((uintptr_t)p + sz - target < range || target - (uintptr_t)p < range)
          return p;
-       mcode_free(J, p, sz);  /* Free badly placed area. */
       }
+      mcode_free(J, p, sz);  /* Free badly placed area. */
     }
     /* Next try probing pseudo-random addresses. */
     do {
diff --git a/src/luajit-solaris64.mapfile b/src/luajit-solaris64.mapfile
new file mode 100644
index 0000000..348b010
--- /dev/null
+++ b/src/luajit-solaris64.mapfile
@@ -0,0 +1,2 @@
+# 2G - 256M
+text = V0x70280000;
diff --git a/src/luajit.c b/src/luajit.c
index 22628aa..f66febe 100644
--- a/src/luajit.c
+++ b/src/luajit.c
@@ -565,10 +565,11 @@ static int pmain(lua_State *L)
   return 0;
 }
 
+static struct Smain s;
+
 int main(int argc, char **argv)
 {
   int status;
-  struct Smain s;
   lua_State *L = lua_open();  /* create state */
   if (L == NULL) {
     l_message(argv[0], "cannot create state: not enough memory");
diff --git a/src/vm_x86.dasc b/src/vm_x86.dasc
index c455795..d8551fe 100644
--- a/src/vm_x86.dasc
+++ b/src/vm_x86.dasc
@@ -6117,7 +6117,11 @@ static void emit_asm_debug(BuildCtx *ctx)
        ".LEFDE1:\n\n", (int)ctx->codesz - fcofs);
 #endif
 #if (defined(__sun__) && defined(__svr4__)) || defined(__solaris_)
+#if LJ_64
+    fprintf(ctx->fp, "\t.section .eh_frame,\"a\",@unwind\n");
+#else
     fprintf(ctx->fp, "\t.section .eh_frame,\"aw\",@progbits\n");
+#endif
 #else
     fprintf(ctx->fp, "\t.section .eh_frame,\"a\",@progbits\n");
 #endif

Other related posts:

  • » [PATCH] Solaris/x64 support - Dmitri Shubin