Commit 2a0faed9 authored by Andres Freund's avatar Andres Freund

Add expression compilation support to LLVM JIT provider.

In addition to the interpretation of expressions (which back
evaluation of WHERE clauses, target list projection, aggregates
transition values etc) support compiling expressions to native code,
using the infrastructure added in earlier commits.

To avoid duplicating a lot of code, only support emitting code for
cases that are likely to be performance critical. For expression steps
that aren't deemed that, use the existing interpreter.

The generated code isn't great - some architectural changes are
required to address that. But this already yields a significant
speedup for some analytics queries, particularly with WHERE clauses
filtering a lot, or computing multiple aggregates.

Author: Andres Freund
Tested-By: Thomas Munro
Discussion: https://postgr.es/m/20170901064131.tazjxwus3k2w3ybh@alap3.anarazel.de

Disable JITing for VALUES() nodes.

VALUES() nodes are only ever executed once. This is primarily helpful
for debugging, when forcing JITing even for cheap queries.

Author: Andres Freund
Discussion: https://postgr.es/m/20170901064131.tazjxwus3k2w3ybh@alap3.anarazel.de
parent 7ced1d12
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "executor/execExpr.h" #include "executor/execExpr.h"
#include "executor/nodeSubplan.h" #include "executor/nodeSubplan.h"
#include "funcapi.h" #include "funcapi.h"
#include "jit/jit.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
...@@ -623,6 +624,9 @@ ExecCheck(ExprState *state, ExprContext *econtext) ...@@ -623,6 +624,9 @@ ExecCheck(ExprState *state, ExprContext *econtext)
static void static void
ExecReadyExpr(ExprState *state) ExecReadyExpr(ExprState *state)
{ {
if (jit_compile_expr(state))
return;
ExecReadyInterpretedExpr(state); ExecReadyInterpretedExpr(state);
} }
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "executor/executor.h" #include "executor/executor.h"
#include "executor/nodeValuesscan.h" #include "executor/nodeValuesscan.h"
#include "jit/jit.h"
#include "utils/expandeddatum.h" #include "utils/expandeddatum.h"
...@@ -98,6 +99,7 @@ ValuesNext(ValuesScanState *node) ...@@ -98,6 +99,7 @@ ValuesNext(ValuesScanState *node)
bool *isnull; bool *isnull;
ListCell *lc; ListCell *lc;
int resind; int resind;
int saved_jit_flags;
/* /*
* Get rid of any prior cycle's leftovers. We use ReScanExprContext * Get rid of any prior cycle's leftovers. We use ReScanExprContext
...@@ -128,7 +130,15 @@ ValuesNext(ValuesScanState *node) ...@@ -128,7 +130,15 @@ ValuesNext(ValuesScanState *node)
oldsubplans = node->ss.ps.subPlan; oldsubplans = node->ss.ps.subPlan;
node->ss.ps.subPlan = NIL; node->ss.ps.subPlan = NIL;
/*
* As the expressions are only ever used once, disable JIT for
* them. This is worthwhile because it's common to insert significant
* amounts of data via VALUES().
*/
saved_jit_flags = econtext->ecxt_estate->es_jit_flags;
econtext->ecxt_estate->es_jit_flags = PGJIT_NONE;
exprstatelist = ExecInitExprList(exprlist, &node->ss.ps); exprstatelist = ExecInitExprList(exprlist, &node->ss.ps);
econtext->ecxt_estate->es_jit_flags = saved_jit_flags;
node->ss.ps.subPlan = oldsubplans; node->ss.ps.subPlan = oldsubplans;
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "fmgr.h" #include "fmgr.h"
#include "executor/execExpr.h"
#include "jit/jit.h" #include "jit/jit.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "utils/resowner_private.h" #include "utils/resowner_private.h"
...@@ -35,6 +36,7 @@ bool jit_enabled = true; ...@@ -35,6 +36,7 @@ bool jit_enabled = true;
char *jit_provider = "llvmjit"; char *jit_provider = "llvmjit";
bool jit_debugging_support = false; bool jit_debugging_support = false;
bool jit_dump_bitcode = false; bool jit_dump_bitcode = false;
bool jit_expressions = true;
bool jit_profiling_support = false; bool jit_profiling_support = false;
double jit_above_cost = 100000; double jit_above_cost = 100000;
double jit_optimize_above_cost = 500000; double jit_optimize_above_cost = 500000;
...@@ -143,6 +145,41 @@ jit_release_context(JitContext *context) ...@@ -143,6 +145,41 @@ jit_release_context(JitContext *context)
pfree(context); pfree(context);
} }
/*
* Ask provider to JIT compile an expression.
*
* Returns true if successful, false if not.
*/
bool
jit_compile_expr(struct ExprState *state)
{
/*
* We can easily create a one-off context for functions without an
* associated PlanState (and thus EState). But because there's no executor
* shutdown callback that could deallocate the created function, they'd
* live to the end of the transactions, where they'd be cleaned up by the
* resowner machinery. That can lead to a noticeable amount of memory
* usage, and worse, trigger some quadratic behaviour in gdb. Therefore,
* at least for now, don't create a JITed function in those circumstances.
*/
if (!state->parent)
return false;
/* if no jitting should be performed at all */
if (!(state->parent->state->es_jit_flags & PGJIT_PERFORM))
return false;
/* or if expressions aren't JITed */
if (!(state->parent->state->es_jit_flags & PGJIT_EXPR))
return false;
/* this also takes !jit_enabled into account */
if (provider_init())
return provider.compile_expr(state);
return false;
}
static bool static bool
file_exists(const char *name) file_exists(const char *name)
{ {
......
...@@ -39,7 +39,7 @@ OBJS=$(WIN32RES) ...@@ -39,7 +39,7 @@ OBJS=$(WIN32RES)
# Infrastructure # Infrastructure
OBJS += llvmjit.o llvmjit_error.o llvmjit_wrap.o OBJS += llvmjit.o llvmjit_error.o llvmjit_wrap.o
# Code generation # Code generation
OBJS += OBJS += llvmjit_expr.o
all: all-shared-lib llvmjit_types.bc all: all-shared-lib llvmjit_types.bc
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "postgres.h" #include "postgres.h"
#include "jit/llvmjit.h" #include "jit/llvmjit.h"
#include "jit/llvmjit_emit.h"
#include "miscadmin.h" #include "miscadmin.h"
...@@ -114,6 +115,7 @@ _PG_jit_provider_init(JitProviderCallbacks *cb) ...@@ -114,6 +115,7 @@ _PG_jit_provider_init(JitProviderCallbacks *cb)
{ {
cb->reset_after_error = llvm_reset_after_error; cb->reset_after_error = llvm_reset_after_error;
cb->release_context = llvm_release_context; cb->release_context = llvm_release_context;
cb->compile_expr = llvm_compile_expr;
} }
/* /*
...@@ -339,6 +341,68 @@ llvm_copy_attributes(LLVMValueRef v_from, LLVMValueRef v_to) ...@@ -339,6 +341,68 @@ llvm_copy_attributes(LLVMValueRef v_from, LLVMValueRef v_to)
} }
} }
/*
* Return a callable LLVMValueRef for fcinfo.
*/
LLVMValueRef
llvm_function_reference(LLVMJitContext *context,
LLVMBuilderRef builder,
LLVMModuleRef mod,
FunctionCallInfo fcinfo)
{
char *modname;
char *basename;
char *funcname;
LLVMValueRef v_fn;
fmgr_symbol(fcinfo->flinfo->fn_oid, &modname, &basename);
if (modname != NULL && basename != NULL)
{
/* external function in loadable library */
funcname = psprintf("pgextern.%s.%s", modname, basename);
}
else if (basename != NULL)
{
/* internal function */
funcname = psprintf("%s", basename);
}
else
{
/*
* Function we don't know to handle, return pointer. We do so by
* creating a global constant containing a pointer to the function.
* Makes IR more readable.
*/
LLVMValueRef v_fn_addr;
funcname = psprintf("pgoidextern.%u",
fcinfo->flinfo->fn_oid);
v_fn = LLVMGetNamedGlobal(mod, funcname);
if (v_fn != 0)
return LLVMBuildLoad(builder, v_fn, "");
v_fn_addr = l_ptr_const(fcinfo->flinfo->fn_addr, TypePGFunction);
v_fn = LLVMAddGlobal(mod, TypePGFunction, funcname);
LLVMSetInitializer(v_fn, v_fn_addr);
LLVMSetGlobalConstant(v_fn, true);
return LLVMBuildLoad(builder, v_fn, "");
return v_fn;
}
/* check if function already has been added */
v_fn = LLVMGetNamedFunction(mod, funcname);
if (v_fn != 0)
return v_fn;
v_fn = LLVMAddFunction(mod, funcname, LLVMGetElementType(TypePGFunction));
return v_fn;
}
/* /*
* Optimize code in module using the flags set in context. * Optimize code in module using the flags set in context.
*/ */
......
This diff is collapsed.
...@@ -544,6 +544,12 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) ...@@ -544,6 +544,12 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
if (jit_optimize_above_cost >= 0 && if (jit_optimize_above_cost >= 0 &&
top_plan->total_cost > jit_optimize_above_cost) top_plan->total_cost > jit_optimize_above_cost)
result->jitFlags |= PGJIT_OPT3; result->jitFlags |= PGJIT_OPT3;
/*
* Decide which operations should be JITed.
*/
if (jit_expressions)
result->jitFlags |= PGJIT_EXPR;
} }
return result; return result;
......
...@@ -59,7 +59,8 @@ static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple proc ...@@ -59,7 +59,8 @@ static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple proc
static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple); static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple);
static void record_C_func(HeapTuple procedureTuple, static void record_C_func(HeapTuple procedureTuple,
PGFunction user_fn, const Pg_finfo_record *inforec); PGFunction user_fn, const Pg_finfo_record *inforec);
static Datum fmgr_security_definer(PG_FUNCTION_ARGS); /* extern so it's callable via JIT */
extern Datum fmgr_security_definer(PG_FUNCTION_ARGS);
/* /*
...@@ -260,6 +261,95 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, ...@@ -260,6 +261,95 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
ReleaseSysCache(procedureTuple); ReleaseSysCache(procedureTuple);
} }
/*
* Return module and C function name providing implementation of functionId.
*
* If *mod == NULL and *fn == NULL, no C symbol is known to implement
* function.
*
* If *mod == NULL and *fn != NULL, the function is implemented by a symbol in
* the main binary.
*
* If *mod != NULL and *fn !=NULL the function is implemented in an extension
* shared object.
*
* The returned module and function names are pstrdup'ed into the current
* memory context.
*/
void
fmgr_symbol(Oid functionId, char **mod, char **fn)
{
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
bool isnull;
Datum prosrcattr;
Datum probinattr;
/* Otherwise we need the pg_proc entry */
procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId));
if (!HeapTupleIsValid(procedureTuple))
elog(ERROR, "cache lookup failed for function %u", functionId);
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
/*
*/
if (procedureStruct->prosecdef ||
!heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
FmgrHookIsNeeded(functionId))
{
*mod = NULL; /* core binary */
*fn = pstrdup("fmgr_security_definer");
ReleaseSysCache(procedureTuple);
return;
}
/* see fmgr_info_cxt_security for the individual cases */
switch (procedureStruct->prolang)
{
case INTERNALlanguageId:
prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple,
Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
*mod = NULL; /* core binary */
*fn = TextDatumGetCString(prosrcattr);
break;
case ClanguageId:
prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple,
Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc for C function %u", functionId);
probinattr = SysCacheGetAttr(PROCOID, procedureTuple,
Anum_pg_proc_probin, &isnull);
if (isnull)
elog(ERROR, "null probin for C function %u", functionId);
/*
* No need to check symbol presence / API version here, already
* checked in fmgr_info_cxt_security.
*/
*mod = TextDatumGetCString(probinattr);
*fn = TextDatumGetCString(prosrcattr);
break;
case SQLlanguageId:
*mod = NULL; /* core binary */
*fn = pstrdup("fmgr_sql");
break;
default:
*mod = NULL;
*fn = NULL; /* unknown, pass pointer */
break;
}
ReleaseSysCache(procedureTuple);
}
/* /*
* Special fmgr_info processing for C-language functions. Note that * Special fmgr_info processing for C-language functions. Note that
* finfo->fn_oid is not valid yet. * finfo->fn_oid is not valid yet.
...@@ -565,7 +655,7 @@ struct fmgr_security_definer_cache ...@@ -565,7 +655,7 @@ struct fmgr_security_definer_cache
* the actual arguments, etc.) intact. This is not re-entrant, but then * the actual arguments, etc.) intact. This is not re-entrant, but then
* the fcinfo itself can't be used reentrantly anyway. * the fcinfo itself can't be used reentrantly anyway.
*/ */
static Datum extern Datum
fmgr_security_definer(PG_FUNCTION_ARGS) fmgr_security_definer(PG_FUNCTION_ARGS)
{ {
Datum result; Datum result;
......
...@@ -1761,6 +1761,17 @@ static struct config_bool ConfigureNamesBool[] = ...@@ -1761,6 +1761,17 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL NULL, NULL, NULL
}, },
{
{"jit_expressions", PGC_USERSET, DEVELOPER_OPTIONS,
gettext_noop("Allow JIT compilation of expressions."),
NULL,
GUC_NOT_IN_SAMPLE
},
&jit_expressions,
true,
NULL, NULL, NULL
},
{ {
{"jit_profiling_support", PGC_SU_BACKEND, DEVELOPER_OPTIONS, {"jit_profiling_support", PGC_SU_BACKEND, DEVELOPER_OPTIONS,
gettext_noop("Register JIT compiled function with perf profiler."), gettext_noop("Register JIT compiled function with perf profiler."),
......
...@@ -113,6 +113,8 @@ extern void fmgr_info_cxt(Oid functionId, FmgrInfo *finfo, ...@@ -113,6 +113,8 @@ extern void fmgr_info_cxt(Oid functionId, FmgrInfo *finfo,
extern void fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo, extern void fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
MemoryContext destcxt); MemoryContext destcxt);
extern void fmgr_symbol(Oid functionId, char **mod, char **fn);
/* /*
* This macro initializes all the fields of a FunctionCallInfoData except * This macro initializes all the fields of a FunctionCallInfoData except
* for the arg[] and argnull[] arrays. Performance testing has shown that * for the arg[] and argnull[] arrays. Performance testing has shown that
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#define PGJIT_NONE 0 #define PGJIT_NONE 0
#define PGJIT_PERFORM 1 << 0 #define PGJIT_PERFORM 1 << 0
#define PGJIT_OPT3 1 << 1 #define PGJIT_OPT3 1 << 1
/* reserved for PGJIT_INLINE */
#define PGJIT_EXPR 1 << 3
typedef struct JitContext typedef struct JitContext
...@@ -47,11 +49,14 @@ extern void _PG_jit_provider_init(JitProviderCallbacks *cb); ...@@ -47,11 +49,14 @@ extern void _PG_jit_provider_init(JitProviderCallbacks *cb);
typedef void (*JitProviderInit) (JitProviderCallbacks *cb); typedef void (*JitProviderInit) (JitProviderCallbacks *cb);
typedef void (*JitProviderResetAfterErrorCB) (void); typedef void (*JitProviderResetAfterErrorCB) (void);
typedef void (*JitProviderReleaseContextCB) (JitContext *context); typedef void (*JitProviderReleaseContextCB) (JitContext *context);
struct ExprState;
typedef bool (*JitProviderCompileExprCB) (struct ExprState *state);
struct JitProviderCallbacks struct JitProviderCallbacks
{ {
JitProviderResetAfterErrorCB reset_after_error; JitProviderResetAfterErrorCB reset_after_error;
JitProviderReleaseContextCB release_context; JitProviderReleaseContextCB release_context;
JitProviderCompileExprCB compile_expr;
}; };
...@@ -60,6 +65,7 @@ extern bool jit_enabled; ...@@ -60,6 +65,7 @@ extern bool jit_enabled;
extern char *jit_provider; extern char *jit_provider;
extern bool jit_debugging_support; extern bool jit_debugging_support;
extern bool jit_dump_bitcode; extern bool jit_dump_bitcode;
extern bool jit_expressions;
extern bool jit_profiling_support; extern bool jit_profiling_support;
extern double jit_above_cost; extern double jit_above_cost;
extern double jit_optimize_above_cost; extern double jit_optimize_above_cost;
...@@ -68,4 +74,11 @@ extern double jit_optimize_above_cost; ...@@ -68,4 +74,11 @@ extern double jit_optimize_above_cost;
extern void jit_reset_after_error(void); extern void jit_reset_after_error(void);
extern void jit_release_context(JitContext *context); extern void jit_release_context(JitContext *context);
/*
* Functions for attempting to JIT code. Callers must accept that these might
* not be able to perform JIT (i.e. return false).
*/
extern bool jit_compile_expr(struct ExprState *state);
#endif /* JIT_H */ #endif /* JIT_H */
...@@ -29,6 +29,7 @@ extern "C" ...@@ -29,6 +29,7 @@ extern "C"
#endif #endif
#include "fmgr.h"
#include "jit/jit.h" #include "jit/jit.h"
#include "nodes/pg_list.h" #include "nodes/pg_list.h"
...@@ -91,8 +92,19 @@ extern void *llvm_get_function(LLVMJitContext *context, const char *funcname); ...@@ -91,8 +92,19 @@ extern void *llvm_get_function(LLVMJitContext *context, const char *funcname);
extern void llvm_split_symbol_name(const char *name, char **modname, char **funcname); extern void llvm_split_symbol_name(const char *name, char **modname, char **funcname);
extern LLVMValueRef llvm_get_decl(LLVMModuleRef mod, LLVMValueRef f); extern LLVMValueRef llvm_get_decl(LLVMModuleRef mod, LLVMValueRef f);
extern void llvm_copy_attributes(LLVMValueRef from, LLVMValueRef to); extern void llvm_copy_attributes(LLVMValueRef from, LLVMValueRef to);
extern LLVMValueRef llvm_function_reference(LLVMJitContext *context,
LLVMBuilderRef builder,
LLVMModuleRef mod,
FunctionCallInfo fcinfo);
/*
****************************************************************************
* Code ceneration functions.
****************************************************************************
*/
extern bool llvm_compile_expr(struct ExprState *state);
/* /*
**************************************************************************** ****************************************************************************
* Extensions / Backward compatibility section of the LLVM C API * Extensions / Backward compatibility section of the LLVM C API
......
...@@ -363,6 +363,7 @@ CommitTimestampShared ...@@ -363,6 +363,7 @@ CommitTimestampShared
CommonEntry CommonEntry
CommonTableExpr CommonTableExpr
CompareScalarsContext CompareScalarsContext
CompiledExprState
CompositeIOData CompositeIOData
CompositeTypeStmt CompositeTypeStmt
CompoundAffixFlag CompoundAffixFlag
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment