Basic JIT provider and error handling infrastructure.

This commit introduces:

1) JIT provider abstraction, which allows JIT functionality to be
   implemented in separate shared libraries. That's desirable because
   it allows to install JIT support as a separate package, and because
   it allows experimentation with different forms of JITing.
2) JITContexts which can be, using functions introduced in follow up
   commits, used to emit JITed functions, and have them be cleaned up
   on error.
3) The outline of a LLVM JIT provider, which will be fleshed out in
   subsequent commits.

Documentation for GUCs added, and for JIT in general, will be added in
later commits.

Author: Andres Freund, with architectural input from Jeff Davis
Discussion: https://postgr.es/m/20170901064131.tazjxwus3k2w3ybh@alap3.anarazel.de
This commit is contained in:
Andres Freund 2018-03-21 19:28:28 -07:00
parent 4317cc68a2
commit 432bb9e04d
18 changed files with 679 additions and 2 deletions

View File

@ -31,6 +31,10 @@ SUBDIRS = \
test/isolation \
test/perl
ifeq ($(with_llvm), yes)
SUBDIRS += backend/jit/llvm
endif
# There are too many interdependencies between the subdirectories, so
# don't attempt parallel make here.
.NOTPARALLEL:

View File

@ -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

22
src/backend/jit/Makefile Normal file
View File

@ -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

156
src/backend/jit/jit.c Normal file
View File

@ -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;
}

View File

@ -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)

View File

@ -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)
{
}

View File

@ -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())));
}

View File

@ -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.

View File

@ -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

View File

@ -606,6 +606,8 @@
#dynamic_library_path = '$libdir'
#jit = on # allow JIT compilation
#jit_provider = 'llvmjit' # JIT implementation to use
#------------------------------------------------------------------------------
# LOCK MANAGEMENT

View File

@ -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);
}

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201803212
#define CATALOG_VERSION_NO 201803213
#endif

View File

@ -3371,6 +3371,8 @@ DATA(insert OID = 3935 ( pg_sleep_for PGNSP PGUID 14 1 0 0 0 f f f t f v s 1 0
DESCR("sleep for the specified interval");
DATA(insert OID = 3936 ( pg_sleep_until PGNSP PGUID 14 1 0 0 0 f f f t f v s 1 0 2278 "1184" _null_ _null_ _null_ _null_ _null_ "select pg_catalog.pg_sleep(extract(epoch from $1) operator(pg_catalog.-) extract(epoch from pg_catalog.clock_timestamp()))" _null_ _null_ _null_ ));
DESCR("sleep until the specified time");
DATA(insert OID = 315 ( pg_jit_available PGNSP PGUID 12 1 0 0 0 f f f t f v s 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_jit_available _null_ _null_ _null_ ));
DESCR("Is JIT compilation available in this session?");
DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 0 f f f t f i s 1 0 25 "16" _null_ _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ ));
DESCR("convert boolean to text");

46
src/include/jit/jit.h Normal file
View File

@ -0,0 +1,46 @@
/*-------------------------------------------------------------------------
* jit.h
* Provider independent JIT infrastructure.
*
* Copyright (c) 2016-2018, PostgreSQL Global Development Group
*
* src/include/jit/jit.h
*
*-------------------------------------------------------------------------
*/
#ifndef JIT_H
#define JIT_H
#include "utils/resowner.h"
typedef struct JitContext
{
int flags;
ResourceOwner resowner;
} JitContext;
typedef struct JitProviderCallbacks JitProviderCallbacks;
extern void _PG_jit_provider_init(JitProviderCallbacks *cb);
typedef void (*JitProviderInit) (JitProviderCallbacks *cb);
typedef void (*JitProviderResetAfterErrorCB) (void);
typedef void (*JitProviderReleaseContextCB) (JitContext *context);
struct JitProviderCallbacks
{
JitProviderResetAfterErrorCB reset_after_error;
JitProviderReleaseContextCB release_context;
};
/* GUCs */
extern bool jit_enabled;
extern char *jit_provider;
extern void jit_reset_after_error(void);
extern void jit_release_context(JitContext *context);
#endif /* JIT_H */

51
src/include/jit/llvmjit.h Normal file
View File

@ -0,0 +1,51 @@
/*-------------------------------------------------------------------------
* llvmjit.h
* LLVM JIT provider.
*
* Copyright (c) 2016-2018, PostgreSQL Global Development Group
*
* src/include/jit/llvmjit.h
*
*-------------------------------------------------------------------------
*/
#ifndef LLVMJIT_H
#define LLVMJIT_H
#ifndef USE_LLVM
#error "llvmjit.h should only be included by code dealing with llvm"
#endif
#include <llvm-c/Types.h>
/*
* File needs to be includable by both C and C++ code, and include other
* headers doing the same. Therefore wrap C portion in our own extern "C" if
* in C++ mode.
*/
#ifdef __cplusplus
extern "C"
{
#endif
#include "jit/jit.h"
typedef struct LLVMJitContext
{
JitContext base;
} LLVMJitContext;
extern void llvm_enter_fatal_on_oom(void);
extern void llvm_leave_fatal_on_oom(void);
extern void llvm_reset_after_error(void);
extern void llvm_assert_in_fatal_section(void);
extern LLVMJitContext *llvm_create_context(int jitFlags);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* LLVMJIT_H */

View File

@ -88,4 +88,11 @@ extern void ResourceOwnerRememberDSM(ResourceOwner owner,
extern void ResourceOwnerForgetDSM(ResourceOwner owner,
dsm_segment *);
/* support for JITContext management */
extern void ResourceOwnerEnlargeJIT(ResourceOwner owner);
extern void ResourceOwnerRememberJIT(ResourceOwner owner,
Datum handle);
extern void ResourceOwnerForgetJIT(ResourceOwner owner,
Datum handle);
#endif /* RESOWNER_PRIVATE_H */

View File

@ -5,3 +5,4 @@
/ecpg/test/expected/
/snowball/libstemmer/
/pl/plperl/ppport\.h$
/jit/llvmjit\.h$

View File

@ -1055,6 +1055,8 @@ IterateForeignScan_function
IterateJsonStringValuesState
JEntry
JHashState
JitContext
JitProviderCallbacks
JOBOBJECTINFOCLASS
JOBOBJECT_BASIC_LIMIT_INFORMATION
JOBOBJECT_BASIC_UI_RESTRICTIONS
@ -1099,6 +1101,7 @@ LDAPMessage
LDAPURLDesc
LDAP_TIMEVAL
LINE
LLVMJitContext
LOCALLOCK
LOCALLOCKOWNER
LOCALLOCKTAG