aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/Makefile3
-rw-r--r--src/backend/jit/Makefile22
-rw-r--r--src/backend/jit/jit.c156
-rw-r--r--src/backend/jit/llvm/Makefile55
-rw-r--r--src/backend/jit/llvm/llvmjit.c113
-rw-r--r--src/backend/jit/llvm/llvmjit_error.cpp141
-rw-r--r--src/backend/tcop/postgres.c3
-rw-r--r--src/backend/utils/misc/guc.c22
-rw-r--r--src/backend/utils/misc/postgresql.conf.sample2
-rw-r--r--src/backend/utils/resowner/resowner.c48
10 files changed, 564 insertions, 1 deletions
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267339a..6450c5a9597 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,8 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
main nodes optimizer port postmaster regex replication rewrite \
- statistics storage tcop tsearch utils $(top_builddir)/src/timezone
+ statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
+ jit
include $(srcdir)/common.mk
diff --git a/src/backend/jit/Makefile b/src/backend/jit/Makefile
new file mode 100644
index 00000000000..cdb9009ec1b
--- /dev/null
+++ b/src/backend/jit/Makefile
@@ -0,0 +1,22 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for JIT code that's provider independent.
+#
+# Note that the LLVM JIT provider is recursed into by src/Makefile,
+# not from here.
+#
+# IDENTIFICATION
+# src/backend/jit/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/jit
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS += -DDLSUFFIX=\"$(DLSUFFIX)\"
+
+OBJS = jit.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c
new file mode 100644
index 00000000000..6c842a0fe9f
--- /dev/null
+++ b/src/backend/jit/jit.c
@@ -0,0 +1,156 @@
+/*-------------------------------------------------------------------------
+ *
+ * jit.c
+ * Provider independent JIT infrastructure.
+ *
+ * Code related to loading JIT providers, redirecting calls into JIT providers
+ * and error handling. No code specific to a specific JIT implementation
+ * should end up here.
+ *
+ *
+ * Copyright (c) 2016-2018, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/jit/jit.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+#include "fmgr.h"
+#include "jit/jit.h"
+#include "miscadmin.h"
+#include "utils/resowner_private.h"
+#include "utils/fmgrprotos.h"
+
+
+/* GUCs */
+bool jit_enabled = true;
+char *jit_provider = "llvmjit";
+
+static JitProviderCallbacks provider;
+static bool provider_successfully_loaded = false;
+static bool provider_failed_loading = false;
+
+
+static bool provider_init(void);
+static bool file_exists(const char *name);
+
+
+/*
+ * SQL level function returning whether JIT is available in the current
+ * backend. Will attempt to load JIT provider if necessary.
+ */
+Datum
+pg_jit_available(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(provider_init());
+}
+
+
+/*
+ * Return whether a JIT provider has successfully been loaded, caching the
+ * result.
+ */
+static bool
+provider_init(void)
+{
+ char path[MAXPGPATH];
+ JitProviderInit init;
+
+ /* don't even try to load if not enabled */
+ if (!jit_enabled)
+ return false;
+
+ /*
+ * Don't retry loading after failing - attempting to load JIT provider
+ * isn't cheap.
+ */
+ if (provider_failed_loading)
+ return false;
+ if (provider_successfully_loaded)
+ return true;
+
+ /*
+ * Check whether shared library exists. We do that check before actually
+ * attempting to load the shared library (via load_external_function()),
+ * because that'd error out in case the shlib isn't available.
+ */
+ snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path, jit_provider, DLSUFFIX);
+ elog(DEBUG1, "probing availability of JIT provider at %s", path);
+ if (!file_exists(path))
+ {
+ elog(DEBUG1,
+ "provider not available, disabling JIT for current session");
+ provider_failed_loading = true;
+ return false;
+ }
+
+ /*
+ * If loading functions fails, signal failure. We do so because
+ * load_external_function() might error out despite the above check if
+ * e.g. the library's dependencies aren't installed. We want to signal
+ * ERROR in that case, so the user is notified, but we don't want to
+ * continually retry.
+ */
+ provider_failed_loading = true;
+
+ /* and initialize */
+ init = (JitProviderInit)
+ load_external_function(path, "_PG_jit_provider_init", true, NULL);
+ init(&provider);
+
+ provider_successfully_loaded = true;
+ provider_failed_loading = false;
+
+ elog(DEBUG1, "successfully loaded JIT provider in current session");
+
+ return true;
+}
+
+/*
+ * Reset JIT provider's error handling. This'll be called after an error has
+ * been thrown and the main-loop has re-established control.
+ */
+void
+jit_reset_after_error(void)
+{
+ if (provider_successfully_loaded)
+ provider.reset_after_error();
+}
+
+/*
+ * Release resources required by one JIT context.
+ */
+void
+jit_release_context(JitContext *context)
+{
+ if (provider_successfully_loaded)
+ provider.release_context(context);
+
+ ResourceOwnerForgetJIT(context->resowner, PointerGetDatum(context));
+ pfree(context);
+}
+
+static bool
+file_exists(const char *name)
+{
+ struct stat st;
+
+ AssertArg(name != NULL);
+
+ if (stat(name, &st) == 0)
+ return S_ISDIR(st.st_mode) ? false : true;
+ else if (!(errno == ENOENT || errno == ENOTDIR))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not access file \"%s\": %m", name)));
+
+ return false;
+}
diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile
new file mode 100644
index 00000000000..856b94e12b4
--- /dev/null
+++ b/src/backend/jit/llvm/Makefile
@@ -0,0 +1,55 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile the LLVM JIT provider, building it into a shared library.
+#
+# Note that this file is recursed into from src/Makefile, not by the
+# parent directory..
+#
+# IDENTIFICATION
+# src/backend/jit/llvm/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/jit/llvm
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+ifneq ($(with_llvm), yes)
+ $(error "not building with LLVM support")
+endif
+
+PGFILEDESC = "llvmjit - JIT using LLVM"
+NAME = llvmjit
+
+# All files in this directy use LLVM.
+CFLAGS += $(LLVM_CFLAGS)
+CXXFLAGS += $(LLVM_CXXFLAGS)
+override CPPFLAGS := $(LLVM_CPPFLAGS) $(CPPFLAGS)
+SHLIB_LINK += $(LLVM_LIBS)
+SHLIB_PREREQS += submake-generated-headers
+
+# Because this module includes C++ files, we need to use a C++
+# compiler for linking. Makefile.shlib uses $(COMPILER) to build
+# loadable modules.
+override COMPILER = $(CXX) $(CFLAGS)
+
+OBJS=$(WIN32RES)
+
+# Infrastructure
+OBJS += llvmjit.o llvmjit_error.o
+# Code generation
+OBJS +=
+
+all: all-shared-lib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+include $(top_srcdir)/src/Makefile.shlib
+
+clean distclean maintainer-clean: clean-lib
+ rm -f $(OBJS)
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
new file mode 100644
index 00000000000..9c579229153
--- /dev/null
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -0,0 +1,113 @@
+/*-------------------------------------------------------------------------
+ *
+ * llvmjit.c
+ * Core part of the LLVM JIT provider.
+ *
+ * Copyright (c) 2016-2018, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/jit/llvm/llvmjit.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "jit/llvmjit.h"
+
+#include "miscadmin.h"
+
+#include "utils/memutils.h"
+#include "utils/resowner_private.h"
+#include "storage/ipc.h"
+
+
+#include <llvm-c/Target.h>
+
+
+static bool llvm_session_initialized = false;
+
+
+static void llvm_release_context(JitContext *context);
+static void llvm_session_initialize(void);
+static void llvm_shutdown(int code, Datum arg);
+
+
+PG_MODULE_MAGIC;
+
+
+/*
+ * Initialize LLVM JIT provider.
+ */
+void
+_PG_jit_provider_init(JitProviderCallbacks *cb)
+{
+ cb->reset_after_error = llvm_reset_after_error;
+ cb->release_context = llvm_release_context;
+}
+
+/*
+ * Create a context for JITing work.
+ *
+ * The context, including subsidiary resources, will be cleaned up either when
+ * the context is explicitly released, or when the lifetime of
+ * CurrentResourceOwner ends (usually the end of the current [sub]xact).
+ */
+LLVMJitContext *
+llvm_create_context(int jitFlags)
+{
+ LLVMJitContext *context;
+
+ llvm_assert_in_fatal_section();
+
+ llvm_session_initialize();
+
+ ResourceOwnerEnlargeJIT(CurrentResourceOwner);
+
+ context = MemoryContextAllocZero(TopMemoryContext,
+ sizeof(LLVMJitContext));
+ context->base.flags = jitFlags;
+
+ /* ensure cleanup */
+ context->base.resowner = CurrentResourceOwner;
+ ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+
+ return context;
+}
+
+/*
+ * Release resources required by one llvm context.
+ */
+static void
+llvm_release_context(JitContext *context)
+{
+}
+
+/*
+ * Per session initialization.
+ */
+static void
+llvm_session_initialize(void)
+{
+ MemoryContext oldcontext;
+
+ if (llvm_session_initialized)
+ return;
+
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ LLVMInitializeNativeTarget();
+ LLVMInitializeNativeAsmPrinter();
+ LLVMInitializeNativeAsmParser();
+
+ before_shmem_exit(llvm_shutdown, 0);
+
+ llvm_session_initialized = true;
+
+ MemoryContextSwitchTo(oldcontext);
+}
+
+static void
+llvm_shutdown(int code, Datum arg)
+{
+}
diff --git a/src/backend/jit/llvm/llvmjit_error.cpp b/src/backend/jit/llvm/llvmjit_error.cpp
new file mode 100644
index 00000000000..edc1c479d01
--- /dev/null
+++ b/src/backend/jit/llvm/llvmjit_error.cpp
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * llvmjit_error.cpp
+ * LLVM error related handling that requires interfacing with C++
+ *
+ * Unfortunately neither (re)setting the C++ new handler, nor the LLVM OOM
+ * handler are exposed to C. Therefore this file wraps the necesary code.
+ *
+ * Copyright (c) 2016-2018, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/jit/llvm/llvmjit_error.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+extern "C"
+{
+#include "postgres.h"
+}
+
+#include <llvm/Support/ErrorHandling.h>
+
+#include "jit/llvmjit.h"
+
+
+static int fatal_new_handler_depth = 0;
+static std::new_handler old_new_handler = NULL;
+
+static void fatal_system_new_handler(void);
+#if LLVM_VERSION_MAJOR > 4
+static void fatal_llvm_new_handler(void *user_data, const std::string& reason, bool gen_crash_diag);
+#endif
+static void fatal_llvm_error_handler(void *user_data, const std::string& reason, bool gen_crash_diag);
+
+
+/*
+ * Enter a section in which C++ and LLVM errors are treated as FATAL errors.
+ *
+ * This is necessary for LLVM as LLVM's error handling for such cases
+ * (exit()ing, throwing std::bad_alloc() if compiled with exceptions, abort())
+ * isn't compatible with postgres error handling. Thus in section where LLVM
+ * code, not LLVM generated functions!, is executing, standard new, LLVM OOM
+ * and LLVM fatal errors (some OOM errors masquerade as those) are redirected
+ * to our own error handlers.
+ *
+ * These error handlers FATAL, because there's no reliable way from within
+ * LLVM to throw an error that's guaranteed not to corrupt LLVM's state.
+ *
+ * To avoid disturbing extensions using C++ and/or LLVM, these handlers are
+ * unset when not executing LLVM code. There is no need to call
+ * llvm_leave_fatal_on_oom() when ERRORing out, error recovery resets the
+ * handlers in that case.
+ */
+void
+llvm_enter_fatal_on_oom(void)
+{
+ if (fatal_new_handler_depth == 0)
+ {
+ old_new_handler = std::set_new_handler(fatal_system_new_handler);
+#if LLVM_VERSION_MAJOR > 4
+ llvm::install_bad_alloc_error_handler(fatal_llvm_new_handler);
+#endif
+ llvm::install_fatal_error_handler(fatal_llvm_error_handler);
+ }
+ fatal_new_handler_depth++;
+}
+
+/*
+ * Leave fatal error section started with llvm_enter_fatal_on_oom().
+ */
+void
+llvm_leave_fatal_on_oom(void)
+{
+ fatal_new_handler_depth--;
+ if (fatal_new_handler_depth == 0)
+ {
+ std::set_new_handler(old_new_handler);
+#if LLVM_VERSION_MAJOR > 4
+ llvm::remove_bad_alloc_error_handler();
+#endif
+ llvm::remove_fatal_error_handler();
+ }
+}
+
+/*
+ * Reset fatal error handling. This should only be called in error recovery
+ * loops like PostgresMain()'s.
+ */
+void
+llvm_reset_after_error(void)
+{
+ if (fatal_new_handler_depth != 0)
+ {
+ std::set_new_handler(old_new_handler);
+#if LLVM_VERSION_MAJOR > 4
+ llvm::remove_bad_alloc_error_handler();
+#endif
+ llvm::remove_fatal_error_handler();
+ }
+ fatal_new_handler_depth = 0;
+}
+
+void
+llvm_assert_in_fatal_section(void)
+{
+ Assert(fatal_new_handler_depth > 0);
+}
+
+static void
+fatal_system_new_handler(void)
+{
+ ereport(FATAL,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory"),
+ errdetail("while in LLVM")));
+}
+
+#if LLVM_VERSION_MAJOR > 4
+static void
+fatal_llvm_new_handler(void *user_data,
+ const std::string& reason,
+ bool gen_crash_diag)
+{
+ ereport(FATAL,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory"),
+ errdetail("While in LLVM: %s", reason.c_str())));
+}
+#endif
+
+static void
+fatal_llvm_error_handler(void *user_data,
+ const std::string& reason,
+ bool gen_crash_diag)
+{
+ ereport(FATAL,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("fatal llvm error: %s",
+ reason.c_str())));
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 6dc2095b9a7..f7aa4a74844 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -42,6 +42,7 @@
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "jit/jit.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
@@ -3950,6 +3951,8 @@ PostgresMain(int argc, char *argv[],
/* We also want to cleanup temporary slots on error. */
ReplicationSlotCleanup();
+ jit_reset_after_error();
+
/*
* Now return to normal top-level context and clear ErrorContext for
* next time.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 153373ead02..afb1007842a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -42,6 +42,7 @@
#include "commands/variable.h"
#include "commands/trigger.h"
#include "funcapi.h"
+#include "jit/jit.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -1714,6 +1715,16 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
+ {
+ {"jit", PGC_USERSET, QUERY_TUNING_OTHER,
+ gettext_noop("Allow JIT compilation."),
+ NULL
+ },
+ &jit_enabled,
+ true,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -3707,6 +3718,17 @@ static struct config_string ConfigureNamesString[] =
check_wal_consistency_checking, assign_wal_consistency_checking, NULL
},
+ {
+ {"jit_provider", PGC_POSTMASTER, FILE_LOCATIONS,
+ gettext_noop("JIT provider to use."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &jit_provider,
+ "llvmjit",
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 048bf4cccd7..91eacacdc97 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -606,6 +606,8 @@
#dynamic_library_path = '$libdir'
+#jit = on # allow JIT compilation
+#jit_provider = 'llvmjit' # JIT implementation to use
#------------------------------------------------------------------------------
# LOCK MANAGEMENT
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index e09a4f1ddb4..bce021e1001 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -21,6 +21,7 @@
#include "postgres.h"
#include "access/hash.h"
+#include "jit/jit.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "utils/memutils.h"
@@ -124,6 +125,7 @@ typedef struct ResourceOwnerData
ResourceArray snapshotarr; /* snapshot references */
ResourceArray filearr; /* open temporary files */
ResourceArray dsmarr; /* dynamic shmem segments */
+ ResourceArray jitarr; /* JIT contexts */
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
int nlocks; /* number of owned locks */
@@ -437,6 +439,7 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL));
ResourceArrayInit(&(owner->filearr), FileGetDatum(-1));
ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL));
+ ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL));
return owner;
}
@@ -538,6 +541,14 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
PrintDSMLeakWarning(res);
dsm_detach(res);
}
+
+ /* Ditto for JIT contexts */
+ while (ResourceArrayGetAny(&(owner->jitarr), &foundres))
+ {
+ JitContext *context = (JitContext *) PointerGetDatum(foundres);
+
+ jit_release_context(context);
+ }
}
else if (phase == RESOURCE_RELEASE_LOCKS)
{
@@ -685,6 +696,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner->snapshotarr.nitems == 0);
Assert(owner->filearr.nitems == 0);
Assert(owner->dsmarr.nitems == 0);
+ Assert(owner->jitarr.nitems == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
/*
@@ -711,6 +723,7 @@ ResourceOwnerDelete(ResourceOwner owner)
ResourceArrayFree(&(owner->snapshotarr));
ResourceArrayFree(&(owner->filearr));
ResourceArrayFree(&(owner->dsmarr));
+ ResourceArrayFree(&(owner->jitarr));
pfree(owner);
}
@@ -1253,3 +1266,38 @@ PrintDSMLeakWarning(dsm_segment *seg)
elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
dsm_segment_handle(seg));
}
+
+/*
+ * Make sure there is room for at least one more entry in a ResourceOwner's
+ * JIT context reference array.
+ *
+ * This is separate from actually inserting an entry because if we run out of
+ * memory, it's critical to do so *before* acquiring the resource.
+ */
+void
+ResourceOwnerEnlargeJIT(ResourceOwner owner)
+{
+ ResourceArrayEnlarge(&(owner->jitarr));
+}
+
+/*
+ * Remember that a JIT context is owned by a ResourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlargeJIT()
+ */
+void
+ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle)
+{
+ ResourceArrayAdd(&(owner->jitarr), handle);
+}
+
+/*
+ * Forget that a JIT context is owned by a ResourceOwner
+ */
+void
+ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle)
+{
+ if (!ResourceArrayRemove(&(owner->jitarr), handle))
+ elog(ERROR, "JIT context %p is not owned by resource owner %s",
+ DatumGetPointer(handle), owner->name);
+}