diff --git a/src/backend/executor/README b/src/backend/executor/README index 1db9642be854..02745c23ed97 100644 --- a/src/backend/executor/README +++ b/src/backend/executor/README @@ -133,9 +133,14 @@ is used by the function evaluation step, thus avoiding extra work to copy the result values around. The last entry in a completed ExprState->steps array is always an -EEOP_DONE step; this removes the need to test for end-of-array while -iterating. Also, if the expression contains any variable references (to -user columns of the ExprContext's INNER, OUTER, or SCAN tuples), the steps +EEOP_DONE_RETURN or EEOP_DONE_NO_RETURN step; this removes the need to +test for end-of-array while iterating. The former is used when the +expression returns a value directly, the latter when side-effects of +expression initialization are the goal (e.g. for projection or +aggregate transition value computation). + +Also, if the expression contains any variable references (to user +columns of the ExprContext's INNER, OUTER, or SCAN tuples), the steps array begins with EEOP_*_FETCHSOME steps that ensure that the relevant tuples have been deconstructed to make the required columns directly available (cf. slot_getsomeattrs()). This allows individual Var-fetching diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 03566c4d1816..f1569879b529 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -8,7 +8,7 @@ * using ExecInitExpr() et al. This converts the tree into a flat array * of ExprEvalSteps, which may be thought of as instructions in a program. * At runtime, we'll execute steps, starting with the first, until we reach - * an EEOP_DONE opcode. + * an EEOP_DONE_{RETURN|NO_RETURN} opcode. * * This file contains the "compilation" logic. It is independent of the * specific execution technology we use (switch statement, computed goto, @@ -162,7 +162,7 @@ ExecInitExpr(Expr *node, PlanState *parent) ExecInitExprRec(node, state, &state->resvalue, &state->resnull); /* Finally, append a DONE step */ - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -199,7 +199,7 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) ExecInitExprRec(node, state, &state->resvalue, &state->resnull); /* Finally, append a DONE step */ - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -291,7 +291,7 @@ ExecInitQual(List *qual, PlanState *parent) * have yielded TRUE, and since its result is stored in the desired output * location, we're done. */ - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -503,7 +503,7 @@ ExecBuildProjectionInfo(List *targetList, } } - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_NO_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -742,7 +742,7 @@ ExecBuildUpdateProjection(List *targetList, } } - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_NO_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -1714,7 +1714,7 @@ ExecInitExprRec(Expr *node, ExprState *state, else { /* Not trivial, so append a DONE step */ - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(elemstate, &scratch); /* and ready the subexpression */ ExecReadyExpr(elemstate); @@ -2788,7 +2788,15 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, if (pgstat_track_functions <= flinfo->fn_stats) { if (flinfo->fn_strict && nargs > 0) - scratch->opcode = EEOP_FUNCEXPR_STRICT; + { + /* Choose nargs optimized implementation if available. */ + if (nargs == 1) + scratch->opcode = EEOP_FUNCEXPR_STRICT_1; + else if (nargs == 2) + scratch->opcode = EEOP_FUNCEXPR_STRICT_2; + else + scratch->opcode = EEOP_FUNCEXPR_STRICT; + } else scratch->opcode = EEOP_FUNCEXPR; } @@ -3892,6 +3900,8 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, { if (strictnulls) scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_NULLS; + else if (strictargs && pertrans->numTransInputs == 1) + scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_ARGS_1; else scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_ARGS; scratch.d.agg_strict_input_check.nulls = strictnulls; @@ -3968,6 +3978,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, as->d.jump.jumpdone = state->steps_len; } else if (as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_ARGS || + as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_ARGS_1 || as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_NULLS) { Assert(as->d.agg_strict_input_check.jumpnull == -1); @@ -3991,7 +4002,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, scratch.resvalue = NULL; scratch.resnull = NULL; - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_NO_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -4258,7 +4269,7 @@ ExecBuildHash32FromAttrs(TupleDesc desc, const TupleTableSlotOps *ops, scratch.resvalue = NULL; scratch.resnull = NULL; - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -4431,7 +4442,7 @@ ExecBuildHash32Expr(TupleDesc desc, const TupleTableSlotOps *ops, scratch.resvalue = NULL; scratch.resnull = NULL; - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -4586,7 +4597,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, scratch.resvalue = NULL; scratch.resnull = NULL; - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); @@ -4722,7 +4733,7 @@ ExecBuildParamSetEqual(TupleDesc desc, scratch.resvalue = NULL; scratch.resnull = NULL; - scratch.opcode = EEOP_DONE; + scratch.opcode = EEOP_DONE_RETURN; ExprEvalPushStep(state, &scratch); ExecReadyExpr(state); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 1c3477b03c93..8a72b5e70a4e 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -246,7 +246,8 @@ ExecReadyInterpretedExpr(ExprState *state) /* Simple validity checks on expression */ Assert(state->steps_len >= 1); - Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE); + Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE_RETURN || + state->steps[state->steps_len - 1].opcode == EEOP_DONE_NO_RETURN); /* * Don't perform redundant initialization. This is unreachable in current @@ -365,7 +366,9 @@ ExecReadyInterpretedExpr(ExprState *state) return; } else if (step0 == EEOP_CASE_TESTVAL && - step1 == EEOP_FUNCEXPR_STRICT) + (step1 == EEOP_FUNCEXPR_STRICT || + step1 == EEOP_FUNCEXPR_STRICT_1 || + step1 == EEOP_FUNCEXPR_STRICT_2)) { state->evalfunc_private = ExecJustApplyFuncToCase; return; @@ -469,7 +472,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) */ #if defined(EEO_USE_COMPUTED_GOTO) static const void *const dispatch_table[] = { - &&CASE_EEOP_DONE, + &&CASE_EEOP_DONE_RETURN, + &&CASE_EEOP_DONE_NO_RETURN, &&CASE_EEOP_INNER_FETCHSOME, &&CASE_EEOP_OUTER_FETCHSOME, &&CASE_EEOP_SCAN_FETCHSOME, @@ -496,6 +500,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_CONST, &&CASE_EEOP_FUNCEXPR, &&CASE_EEOP_FUNCEXPR_STRICT, + &&CASE_EEOP_FUNCEXPR_STRICT_1, + &&CASE_EEOP_FUNCEXPR_STRICT_2, &&CASE_EEOP_FUNCEXPR_FUSAGE, &&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE, &&CASE_EEOP_BOOL_AND_STEP_FIRST, @@ -573,6 +579,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, + &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS_1, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_NULLS, &&CASE_EEOP_AGG_PLAIN_PERGROUP_NULLCHECK, &&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL, @@ -612,9 +619,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_SWITCH() { - EEO_CASE(EEOP_DONE) + EEO_CASE(EEOP_DONE_RETURN) { - goto out; + *isnull = state->resnull; + return state->resvalue; + } + + EEO_CASE(EEOP_DONE_NO_RETURN) + { + Assert(isnull == NULL); + return (Datum) 0; } EEO_CASE(EEOP_INNER_FETCHSOME) @@ -916,6 +930,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + /* strict function call with more than two arguments */ EEO_CASE(EEOP_FUNCEXPR_STRICT) { FunctionCallInfo fcinfo = op->d.func.fcinfo_data; @@ -923,6 +938,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) int nargs = op->d.func.nargs; Datum d; + Assert(nargs > 2); + /* strict function, so check for NULL args */ for (int argno = 0; argno < nargs; argno++) { @@ -941,6 +958,54 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + /* strict function call with one argument */ + EEO_CASE(EEOP_FUNCEXPR_STRICT_1) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + NullableDatum *args = fcinfo->args; + + Assert(op->d.func.nargs == 1); + + /* strict function, so check for NULL args */ + if (args[0].isnull) + *op->resnull = true; + else + { + Datum d; + + fcinfo->isnull = false; + d = op->d.func.fn_addr(fcinfo); + *op->resvalue = d; + *op->resnull = fcinfo->isnull; + } + + EEO_NEXT(); + } + + /* strict function call with two arguments */ + EEO_CASE(EEOP_FUNCEXPR_STRICT_2) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + NullableDatum *args = fcinfo->args; + + Assert(op->d.func.nargs == 2); + + /* strict function, so check for NULL args */ + if (args[0].isnull || args[1].isnull) + *op->resnull = true; + else + { + Datum d; + + fcinfo->isnull = false; + d = op->d.func.fn_addr(fcinfo); + *op->resvalue = d; + *op->resnull = fcinfo->isnull; + } + + EEO_NEXT(); + } + EEO_CASE(EEOP_FUNCEXPR_FUSAGE) { /* not common enough to inline */ @@ -1973,11 +2038,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) * input is not NULL. */ + /* when checking more than one argument */ EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_ARGS) { NullableDatum *args = op->d.agg_strict_input_check.args; int nargs = op->d.agg_strict_input_check.nargs; + Assert(nargs > 1); + for (int argno = 0; argno < nargs; argno++) { if (args[argno].isnull) @@ -1986,6 +2054,19 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + /* special case for just one argument */ + EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_ARGS_1) + { + NullableDatum *args = op->d.agg_strict_input_check.args; + PG_USED_FOR_ASSERTS_ONLY int nargs = op->d.agg_strict_input_check.nargs; + + Assert(nargs == 1); + + if (args[0].isnull) + EEO_JUMP(op->d.agg_strict_input_check.jumpnull); + EEO_NEXT(); + } + EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_NULLS) { bool *nulls = op->d.agg_strict_input_check.nulls; @@ -2188,13 +2269,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { /* unreachable */ Assert(false); - goto out; + goto out_error; } } -out: - *isnull = state->resnull; - return state->resvalue; +out_error: + pg_unreachable(); + return (Datum) 0; } /* diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index ceb8c8a8039a..b4a7698a0b3f 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -816,11 +816,8 @@ advance_transition_function(AggState *aggstate, static void advance_aggregates(AggState *aggstate) { - bool dummynull; - - ExecEvalExprSwitchContext(aggstate->phase->evaltrans, - aggstate->tmpcontext, - &dummynull); + ExecEvalExprNoReturnSwitchContext(aggstate->phase->evaltrans, + aggstate->tmpcontext); } /* diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index f0f5c3bd49f9..890bcb0b0a79 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -321,7 +321,7 @@ llvm_compile_expr(ExprState *state) switch (opcode) { - case EEOP_DONE: + case EEOP_DONE_RETURN: { LLVMValueRef v_tmpisnull; LLVMValueRef v_tmpvalue; @@ -335,6 +335,10 @@ llvm_compile_expr(ExprState *state) break; } + case EEOP_DONE_NO_RETURN: + LLVMBuildRet(b, l_sizet_const(0)); + break; + case EEOP_INNER_FETCHSOME: case EEOP_OUTER_FETCHSOME: case EEOP_SCAN_FETCHSOME: @@ -658,12 +662,16 @@ llvm_compile_expr(ExprState *state) case EEOP_FUNCEXPR: case EEOP_FUNCEXPR_STRICT: + case EEOP_FUNCEXPR_STRICT_1: + case EEOP_FUNCEXPR_STRICT_2: { FunctionCallInfo fcinfo = op->d.func.fcinfo_data; LLVMValueRef v_fcinfo_isnull; LLVMValueRef v_retval; - if (opcode == EEOP_FUNCEXPR_STRICT) + if (opcode == EEOP_FUNCEXPR_STRICT || + opcode == EEOP_FUNCEXPR_STRICT_1 || + opcode == EEOP_FUNCEXPR_STRICT_2) { LLVMBasicBlockRef b_nonull; LLVMBasicBlockRef *b_checkargnulls; @@ -2478,6 +2486,7 @@ llvm_compile_expr(ExprState *state) } case EEOP_AGG_STRICT_INPUT_CHECK_ARGS: + case EEOP_AGG_STRICT_INPUT_CHECK_ARGS_1: case EEOP_AGG_STRICT_INPUT_CHECK_NULLS: { int nargs = op->d.agg_strict_input_check.nargs; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 191d8fe34ded..75366203706c 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -65,8 +65,11 @@ typedef struct ExprEvalRowtypeCache */ typedef enum ExprEvalOp { - /* entire expression has been evaluated completely, return */ - EEOP_DONE, + /* entire expression has been evaluated, return value */ + EEOP_DONE_RETURN, + + /* entire expression has been evaluated, no return value */ + EEOP_DONE_NO_RETURN, /* apply slot_getsomeattrs on corresponding tuple slot */ EEOP_INNER_FETCHSOME, @@ -113,11 +116,13 @@ typedef enum ExprEvalOp /* * Evaluate function call (including OpExprs etc). For speed, we - * distinguish in the opcode whether the function is strict and/or - * requires usage stats tracking. + * distinguish in the opcode whether the function is strict with 1, 2, or + * more arguments and/or requires usage stats tracking. */ EEOP_FUNCEXPR, EEOP_FUNCEXPR_STRICT, + EEOP_FUNCEXPR_STRICT_1, + EEOP_FUNCEXPR_STRICT_2, EEOP_FUNCEXPR_FUSAGE, EEOP_FUNCEXPR_STRICT_FUSAGE, @@ -273,6 +278,7 @@ typedef enum ExprEvalOp EEOP_AGG_STRICT_DESERIALIZE, EEOP_AGG_DESERIALIZE, EEOP_AGG_STRICT_INPUT_CHECK_ARGS, + EEOP_AGG_STRICT_INPUT_CHECK_ARGS_1, EEOP_AGG_STRICT_INPUT_CHECK_NULLS, EEOP_AGG_PLAIN_PERGROUP_NULLCHECK, EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index d12e3f451d22..0d2ffabda684 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -379,6 +379,34 @@ ExecEvalExpr(ExprState *state, } #endif +/* + * ExecEvalExprNoReturn + * + * Like ExecEvalExpr(), but for cases where no return value is expected, + * because the side-effects of expression evaluation are what's desired. This + * is e.g. used for projection and aggregate transition computation. + + * Evaluate expression identified by "state" in the execution context + * given by "econtext". + * + * The caller should already have switched into the temporary memory context + * econtext->ecxt_per_tuple_memory. The convenience entry point + * ExecEvalExprNoReturnSwitchContext() is provided for callers who don't + * prefer to do the switch in an outer loop. + */ +#ifndef FRONTEND +static inline void +ExecEvalExprNoReturn(ExprState *state, + ExprContext *econtext) +{ + PG_USED_FOR_ASSERTS_ONLY Datum retDatum; + + retDatum = state->evalfunc(state, econtext, NULL); + + Assert(retDatum == (Datum) 0); +} +#endif + /* * ExecEvalExprSwitchContext * @@ -400,6 +428,25 @@ ExecEvalExprSwitchContext(ExprState *state, } #endif +/* + * ExecEvalExprNoReturnSwitchContext + * + * Same as ExecEvalExprNoReturn, but get into the right allocation context + * explicitly. + */ +#ifndef FRONTEND +static inline void +ExecEvalExprNoReturnSwitchContext(ExprState *state, + ExprContext *econtext) +{ + MemoryContext oldContext; + + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + ExecEvalExprNoReturn(state, econtext); + MemoryContextSwitchTo(oldContext); +} +#endif + /* * ExecProject * @@ -419,7 +466,6 @@ ExecProject(ProjectionInfo *projInfo) ExprContext *econtext = projInfo->pi_exprContext; ExprState *state = &projInfo->pi_state; TupleTableSlot *slot = state->resultslot; - bool isnull; /* * Clear any former contents of the result slot. This makes it safe for @@ -427,8 +473,8 @@ ExecProject(ProjectionInfo *projInfo) */ ExecClearTuple(slot); - /* Run the expression, discarding scalar result from the last column. */ - (void) ExecEvalExprSwitchContext(state, econtext, &isnull); + /* Run the expression */ + ExecEvalExprNoReturnSwitchContext(state, econtext); /* * Successfully formed a result row. Mark the result slot as containing a