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
This commit is contained in:
Andres Freund 2018-03-20 02:20:46 -07:00
parent 7ced1d1247
commit 2a0faed9d7
13 changed files with 2890 additions and 3 deletions

View File

@ -36,6 +36,7 @@
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@ -623,6 +624,9 @@ ExecCheck(ExprState *state, ExprContext *econtext)
static void
ExecReadyExpr(ExprState *state)
{
if (jit_compile_expr(state))
return;
ExecReadyInterpretedExpr(state);
}

View File

@ -25,6 +25,7 @@
#include "executor/executor.h"
#include "executor/nodeValuesscan.h"
#include "jit/jit.h"
#include "utils/expandeddatum.h"
@ -98,6 +99,7 @@ ValuesNext(ValuesScanState *node)
bool *isnull;
ListCell *lc;
int resind;
int saved_jit_flags;
/*
* Get rid of any prior cycle's leftovers. We use ReScanExprContext
@ -128,7 +130,15 @@ ValuesNext(ValuesScanState *node)
oldsubplans = node->ss.ps.subPlan;
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);
econtext->ecxt_estate->es_jit_flags = saved_jit_flags;
node->ss.ps.subPlan = oldsubplans;

View File

@ -24,6 +24,7 @@
#include "fmgr.h"
#include "executor/execExpr.h"
#include "jit/jit.h"
#include "miscadmin.h"
#include "utils/resowner_private.h"
@ -35,6 +36,7 @@ bool jit_enabled = true;
char *jit_provider = "llvmjit";
bool jit_debugging_support = false;
bool jit_dump_bitcode = false;
bool jit_expressions = true;
bool jit_profiling_support = false;
double jit_above_cost = 100000;
double jit_optimize_above_cost = 500000;
@ -143,6 +145,41 @@ jit_release_context(JitContext *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
file_exists(const char *name)
{

View File

@ -39,7 +39,7 @@ OBJS=$(WIN32RES)
# Infrastructure
OBJS += llvmjit.o llvmjit_error.o llvmjit_wrap.o
# Code generation
OBJS +=
OBJS += llvmjit_expr.o
all: all-shared-lib llvmjit_types.bc

View File

@ -14,6 +14,7 @@
#include "postgres.h"
#include "jit/llvmjit.h"
#include "jit/llvmjit_emit.h"
#include "miscadmin.h"
@ -114,6 +115,7 @@ _PG_jit_provider_init(JitProviderCallbacks *cb)
{
cb->reset_after_error = llvm_reset_after_error;
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)
}
}
/*
* 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.
*/

File diff suppressed because it is too large Load Diff

View File

@ -544,6 +544,12 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
if (jit_optimize_above_cost >= 0 &&
top_plan->total_cost > jit_optimize_above_cost)
result->jitFlags |= PGJIT_OPT3;
/*
* Decide which operations should be JITed.
*/
if (jit_expressions)
result->jitFlags |= PGJIT_EXPR;
}
return result;

View File

@ -59,7 +59,8 @@ static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple proc
static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple);
static void record_C_func(HeapTuple procedureTuple,
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,
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
* finfo->fn_oid is not valid yet.
@ -565,7 +655,7 @@ struct fmgr_security_definer_cache
* the actual arguments, etc.) intact. This is not re-entrant, but then
* the fcinfo itself can't be used reentrantly anyway.
*/
static Datum
extern Datum
fmgr_security_definer(PG_FUNCTION_ARGS)
{
Datum result;

View File

@ -1761,6 +1761,17 @@ static struct config_bool ConfigureNamesBool[] =
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,
gettext_noop("Register JIT compiled function with perf profiler."),

View File

@ -113,6 +113,8 @@ extern void fmgr_info_cxt(Oid functionId, FmgrInfo *finfo,
extern void fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
MemoryContext destcxt);
extern void fmgr_symbol(Oid functionId, char **mod, char **fn);
/*
* This macro initializes all the fields of a FunctionCallInfoData except
* for the arg[] and argnull[] arrays. Performance testing has shown that

View File

@ -19,6 +19,8 @@
#define PGJIT_NONE 0
#define PGJIT_PERFORM 1 << 0
#define PGJIT_OPT3 1 << 1
/* reserved for PGJIT_INLINE */
#define PGJIT_EXPR 1 << 3
typedef struct JitContext
@ -47,11 +49,14 @@ extern void _PG_jit_provider_init(JitProviderCallbacks *cb);
typedef void (*JitProviderInit) (JitProviderCallbacks *cb);
typedef void (*JitProviderResetAfterErrorCB) (void);
typedef void (*JitProviderReleaseContextCB) (JitContext *context);
struct ExprState;
typedef bool (*JitProviderCompileExprCB) (struct ExprState *state);
struct JitProviderCallbacks
{
JitProviderResetAfterErrorCB reset_after_error;
JitProviderReleaseContextCB release_context;
JitProviderCompileExprCB compile_expr;
};
@ -60,6 +65,7 @@ extern bool jit_enabled;
extern char *jit_provider;
extern bool jit_debugging_support;
extern bool jit_dump_bitcode;
extern bool jit_expressions;
extern bool jit_profiling_support;
extern double jit_above_cost;
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_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 */

View File

@ -29,6 +29,7 @@ extern "C"
#endif
#include "fmgr.h"
#include "jit/jit.h"
#include "nodes/pg_list.h"
@ -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 LLVMValueRef llvm_get_decl(LLVMModuleRef mod, LLVMValueRef f);
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

View File

@ -363,6 +363,7 @@ CommitTimestampShared
CommonEntry
CommonTableExpr
CompareScalarsContext
CompiledExprState
CompositeIOData
CompositeTypeStmt
CompoundAffixFlag