Replace EEOP_DONE with special steps for return/no return
authorDaniel Gustafsson <dgustafsson@postgresql.org>
Tue, 11 Mar 2025 11:02:38 +0000 (12:02 +0100)
committerDaniel Gustafsson <dgustafsson@postgresql.org>
Tue, 11 Mar 2025 11:02:38 +0000 (12:02 +0100)
Knowing when the side-effects of an expression is the intended result
of the execution, rather than the returnvalue, is important for being
able generate more efficient JITed code. This replaces EEOP_DONE with
two new steps: EEOP_DONE_RETURN and EEOP_DONE_NO_RETURN.  Expressions
which return a value should use the former step; expressions used for
their side-effects which don't return value should use the latter.

Author: Andres Freund <andres@anarazel.de>
Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Andreas Karlsson <andreas@proxel.se>
Discussion: https://postgr.es/m/415721CE-7D2E-4B74-B5D9-1950083BA03E@yesql.se
Discussion: https://postgr.es/m/20191023163849.sosqbfs5yenocez3@alap3.anarazel.de

src/backend/executor/README
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/executor/nodeAgg.c
src/backend/jit/llvm/llvmjit_expr.c
src/include/executor/execExpr.h
src/include/executor/executor.h

index 1db9642be85402c8dad234db75cb56880faf1cfd..02745c23ed974e33e0955a4bb1058ed6f14155e5 100644 (file)
@@ -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
index 03566c4d18166f7379eef32a593fd2bee255c8a9..0175b152980194c7db498d276d4635135a964d9d 100644 (file)
@@ -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);
@@ -3991,7 +3991,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 +4258,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 +4431,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 +4586,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 +4722,7 @@ ExecBuildParamSetEqual(TupleDesc desc,
 
    scratch.resvalue = NULL;
    scratch.resnull = NULL;
-   scratch.opcode = EEOP_DONE;
+   scratch.opcode = EEOP_DONE_RETURN;
    ExprEvalPushStep(state, &scratch);
 
    ExecReadyExpr(state);
index 1c3477b03c9397f4422926522f50492cb08df063..491ecad8dc664c8e12f8269a51432a4cb4bbbcc6 100644 (file)
@@ -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
@@ -469,7 +470,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,
@@ -612,9 +614,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)
@@ -2188,13 +2197,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;
 }
 
 /*
index ceb8c8a8039a706b415c63528689084761ffe301..b4a7698a0b3f7623e7069b0ef6ba97b0a2ff1bf4 100644 (file)
@@ -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);
 }
 
 /*
index f0f5c3bd49f9710c4b0e1639b0e641c3aace43eb..4080b01c17e01cc8558fe1c6500d67a7781c1654 100644 (file)
@@ -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:
index 191d8fe34dedb0fe53378ebee96d2d373e1c2962..fabdf145a8ee6bb9a1507846b86d391fe7c5827a 100644 (file)
@@ -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,
index d12e3f451d22ba9e0c470c2115a27f08a91f40ce..0d2ffabda684a4a35d93c454da00d809856a18aa 100644 (file)
@@ -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