From 3cf02cab36bc9b2420f98ff08c17dea082a84f59 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Mon, 22 Sep 2025 17:01:29 +0900 Subject: [PATCH v2 5/8] WIP: Add EEOPs and helpers for TupleBatch processing Introduce new EEOP cases to fetch attributes into TupleBatch vectors: - EEOP_{INNER,OUTER,SCAN}_FETCHSOME_BATCH - EEOP_BUILD_{INNER,OUTER,SCAN}_BATCH_VECTOR Add ExecBuild{Inner,Outer,Scan}BatchVector() helpers to populate column vectors (values, nulls, nrows, hasnull) from a TupleBatch. Extend ExprContext with inner_batch, outer_batch, and scan_batch fields so expression programs can access active batches directly. Add slot_getsomeattrs_batch() to prefetch attributes across all slots in a TupleBatch, similar to slot_getsomeattrs() for one slot. --- src/backend/executor/execExprInterp.c | 127 +++++++++++++++++++++++++- src/backend/executor/execTuples.c | 32 +++++++ src/backend/jit/llvm/llvmjit_expr.c | 86 +++++++++++++++++ src/backend/jit/llvm/llvmjit_types.c | 4 + src/include/executor/execExpr.h | 45 ++++++++- src/include/executor/tuptable.h | 2 + src/include/nodes/execnodes.h | 24 +++-- 7 files changed, 310 insertions(+), 10 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 0e1a74976f7..68629ad7991 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -59,6 +59,7 @@ #include "access/heaptoast.h" #include "catalog/pg_type.h" #include "commands/sequence.h" +#include "executor/execBatch.h" #include "executor/execExpr.h" #include "executor/nodeSubplan.h" #include "funcapi.h" @@ -188,6 +189,11 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate int setno); static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull); +static pg_attribute_always_inline void ExecBuildBatchVector(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext, + TupleBatch *b); + /* * ScalarArrayOpExprHashEntry * Hash table entry type used during EEOP_HASHED_SCALARARRAYOP @@ -446,7 +452,6 @@ ExecReadyInterpretedExpr(ExprState *state) state->evalfunc_private = ExecInterpExpr; } - /* * Evaluate expression identified by "state" in the execution context * given by "econtext". *isnull is set to the is-null flag for the result, @@ -466,6 +471,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) TupleTableSlot *scanslot; TupleTableSlot *oldslot; TupleTableSlot *newslot; + TupleBatch *innerbatch; + TupleBatch *outerbatch; + TupleBatch *scanbatch; /* * This array has to be in the same order as enum ExprEvalOp. @@ -479,6 +487,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_SCAN_FETCHSOME, &&CASE_EEOP_OLD_FETCHSOME, &&CASE_EEOP_NEW_FETCHSOME, + &&CASE_EEOP_INNER_FETCHSOME_BATCH, + &&CASE_EEOP_OUTER_FETCHSOME_BATCH, + &&CASE_EEOP_SCAN_FETCHSOME_BATCH, &&CASE_EEOP_INNER_VAR, &&CASE_EEOP_OUTER_VAR, &&CASE_EEOP_SCAN_VAR, @@ -592,6 +603,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_AGG_PRESORTED_DISTINCT_MULTI, &&CASE_EEOP_AGG_ORDERED_TRANS_DATUM, &&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE, + &&CASE_EEOP_BUILD_INNER_BATCH_VECTOR, + &&CASE_EEOP_BUILD_OUTER_BATCH_VECTOR, + &&CASE_EEOP_BUILD_SCAN_BATCH_VECTOR, &&CASE_EEOP_LAST }; @@ -612,6 +626,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) scanslot = econtext->ecxt_scantuple; oldslot = econtext->ecxt_oldtuple; newslot = econtext->ecxt_newtuple; + innerbatch = econtext->inner_batch; + outerbatch = econtext->outer_batch; + scanbatch = econtext->scan_batch; #if defined(EEO_USE_COMPUTED_GOTO) EEO_DISPATCH(); @@ -658,6 +675,36 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_INNER_FETCHSOME_BATCH) + { + CheckOpSlotCompatibility(op, innerslot); + + Assert(innerbatch); + slot_getsomeattrs_batch(innerbatch, op->d.fetch_batch.last_var); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_OUTER_FETCHSOME_BATCH) + { + CheckOpSlotCompatibility(op, outerslot); + + Assert(outerbatch); + slot_getsomeattrs_batch(outerbatch, op->d.fetch_batch.last_var); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SCAN_FETCHSOME_BATCH) + { + CheckOpSlotCompatibility(op, scanslot); + + Assert(scanbatch); + slot_getsomeattrs_batch(scanbatch, op->d.fetch_batch.last_var); + + EEO_NEXT(); + } + EEO_CASE(EEOP_OLD_FETCHSOME) { CheckOpSlotCompatibility(op, oldslot); @@ -2265,6 +2312,30 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_BUILD_INNER_BATCH_VECTOR) + { + /* too complex for an inline implementation */ + ExecBuildInnerBatchVector(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BUILD_OUTER_BATCH_VECTOR) + { + /* too complex for an inline implementation */ + ExecBuildOuterBatchVector(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BUILD_SCAN_BATCH_VECTOR) + { + /* too complex for an inline implementation */ + ExecBuildScanBatchVector(state, op, econtext); + + EEO_NEXT(); + } + EEO_CASE(EEOP_LAST) { /* unreachable */ @@ -5914,3 +5985,57 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans, MemoryContextSwitchTo(oldContext); } + +void +ExecBuildInnerBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + Assert(econtext->inner_batch); + ExecBuildBatchVector(state, op, econtext, econtext->inner_batch); +} + +void +ExecBuildOuterBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + Assert(econtext->outer_batch); + ExecBuildBatchVector(state, op, econtext, econtext->outer_batch); +} + +void +ExecBuildScanBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + Assert(econtext->scan_batch); + ExecBuildBatchVector(state, op, econtext, econtext->scan_batch); +} + +static pg_attribute_always_inline void +ExecBuildBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext, + TupleBatch *b) +{ + struct BatchVector *bv = op->d.batch_vector.bv; + int i = 0; + + if (bv->ncols == 0) + return; + + /* Fetch each requested attribute into column vectors. */ + TupleBatchRewind(b); + while (TupleBatchHasMore(b)) + { + TupleTableSlot *slot = TupleBatchGetNextSlot(b); + + for (int j = 0; j < bv->ncols; j++) + { + AttrNumber attno = bv->attnos[j]; + Datum *cols = bv->cols[j]; + bool *nulls = bv->nulls[j]; + + Assert(attno <= slot->tts_nvalid); + cols[i] = slot->tts_values[attno - 1]; + nulls[i] = slot->tts_isnull[attno - 1]; + if (!bv->hasnull && nulls[i]) + bv->hasnull = true; + } + i++; + } + bv->nrows = i; +} diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 8e02d68824f..86d5dea8f8b 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -2111,6 +2111,38 @@ slot_getsomeattrs_int(TupleTableSlot *slot, int attnum) } } +void +slot_getsomeattrs_batch(struct TupleBatch *b, int attnum) +{ + while (TupleBatchHasMore(b)) + { + TupleTableSlot *slot = TupleBatchGetNextSlot(b); + + /* Check for caller errors */ + Assert(attnum > 0); + + if (unlikely(attnum > slot->tts_tupleDescriptor->natts)) + elog(ERROR, "invalid attribute number %d", attnum); + + /* XXX - there should perhaps also be a batch-level att_nvalid */ + if (attnum < slot->tts_nvalid) + continue; + + /* Fetch as many attributes as possible from the underlying tuple. */ + slot->tts_ops->getsomeattrs(slot, attnum); + + /* + * If the underlying tuple doesn't have enough attributes, tuple + * descriptor must have the missing attributes. + */ + if (unlikely(slot->tts_nvalid < attnum)) + { + slot_getmissingattrs(slot, slot->tts_nvalid, attnum); + slot->tts_nvalid = attnum; + } + } +} + /* ---------------------------------------------------------------- * ExecTypeFromTL * diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 712b35df7e5..848f0b52d6f 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -109,6 +109,11 @@ llvm_compile_expr(ExprState *state) LLVMValueRef v_newslot; LLVMValueRef v_resultslot; + /* batches */ + LLVMValueRef v_innerbatch; + LLVMValueRef v_outerbatch; + LLVMValueRef v_scanbatch; + /* nulls/values of slots */ LLVMValueRef v_innervalues; LLVMValueRef v_innernulls; @@ -221,6 +226,21 @@ llvm_compile_expr(ExprState *state) v_state, FIELDNO_EXPRSTATE_RESULTSLOT, "v_resultslot"); + v_innerbatch = l_load_struct_gep(b, + StructExprContext, + v_econtext, + FIELDNO_EXPRCONTEXT_OUTERBATCH, + "v_innerbatch"); + v_outerbatch = l_load_struct_gep(b, + StructExprContext, + v_econtext, + FIELDNO_EXPRCONTEXT_OUTERBATCH, + "v_outerbatch"); + v_scanbatch = l_load_struct_gep(b, + StructExprContext, + v_econtext, + FIELDNO_EXPRCONTEXT_SCANBATCH, + "v_scanbatch"); /* build global values/isnull pointers */ v_scanvalues = l_load_struct_gep(b, @@ -439,6 +459,54 @@ llvm_compile_expr(ExprState *state) break; } + case EEOP_INNER_FETCHSOME_BATCH: + { + LLVMValueRef params[2]; + + params[0] = v_innerbatch; + params[1] = l_int32_const(lc, op->d.fetch_batch.last_var); + + l_call(b, + llvm_pg_var_func_type("slot_getsomeattrs_batch"), + llvm_pg_func(mod, "slot_getsomeattrs_batch"), + params, lengthof(params), ""); + + LLVMBuildBr(b, opblocks[opno + 1]); + break; + } + + case EEOP_OUTER_FETCHSOME_BATCH: + { + LLVMValueRef params[2]; + + params[0] = v_outerbatch; + params[1] = l_int32_const(lc, op->d.fetch_batch.last_var); + + l_call(b, + llvm_pg_var_func_type("slot_getsomeattrs_batch"), + llvm_pg_func(mod, "slot_getsomeattrs_batch"), + params, lengthof(params), ""); + + LLVMBuildBr(b, opblocks[opno + 1]); + break; + } + + case EEOP_SCAN_FETCHSOME_BATCH: + { + LLVMValueRef params[2]; + + params[0] = v_scanbatch; + params[1] = l_int32_const(lc, op->d.fetch_batch.last_var); + + l_call(b, + llvm_pg_var_func_type("slot_getsomeattrs_batch"), + llvm_pg_func(mod, "slot_getsomeattrs_batch"), + params, lengthof(params), ""); + + LLVMBuildBr(b, opblocks[opno + 1]); + break; + } + case EEOP_INNER_VAR: case EEOP_OUTER_VAR: case EEOP_SCAN_VAR: @@ -2940,6 +3008,24 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_BUILD_INNER_BATCH_VECTOR: + build_EvalXFunc(b, mod, "ExecBuildInnerBatchVector", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_BUILD_OUTER_BATCH_VECTOR: + build_EvalXFunc(b, mod, "ExecBuildOuterBatchVector", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_BUILD_SCAN_BATCH_VECTOR: + build_EvalXFunc(b, mod, "ExecBuildScanBatchVector", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_LAST: Assert(false); break; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 167cd554b9c..6bb527c3f6f 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -179,7 +179,11 @@ void *referenced_functions[] = MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, + slot_getsomeattrs_batch, strlen, varsize_any, ExecInterpExprStillValid, + ExecBuildInnerBatchVector, + ExecBuildOuterBatchVector, + ExecBuildScanBatchVector, }; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 75366203706..99c86bac702 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -78,6 +78,11 @@ typedef enum ExprEvalOp EEOP_OLD_FETCHSOME, EEOP_NEW_FETCHSOME, + /* apply slot_getsomeattrs_batch() to corresponding batch */ + EEOP_INNER_FETCHSOME_BATCH, + EEOP_OUTER_FETCHSOME_BATCH, + EEOP_SCAN_FETCHSOME_BATCH, + /* compute non-system Var value */ EEOP_INNER_VAR, EEOP_OUTER_VAR, @@ -292,11 +297,15 @@ typedef enum ExprEvalOp EEOP_AGG_ORDERED_TRANS_DATUM, EEOP_AGG_ORDERED_TRANS_TUPLE, + /* ExprContext.*_batch -> BatchVector */ + EEOP_BUILD_INNER_BATCH_VECTOR, + EEOP_BUILD_OUTER_BATCH_VECTOR, + EEOP_BUILD_SCAN_BATCH_VECTOR, + /* non-existent operation, used e.g. to check array lengths */ EEOP_LAST } ExprEvalOp; - typedef struct ExprEvalStep { /* @@ -331,6 +340,12 @@ typedef struct ExprEvalStep const TupleTableSlotOps *kind; } fetch; + struct + { + /* attribute number up to which to fetch (inclusive) */ + int last_var; + } fetch_batch; + /* for EEOP_INNER/OUTER/SCAN/OLD/NEW_[SYS]VAR */ struct { @@ -769,6 +784,12 @@ typedef struct ExprEvalStep void *json_coercion_cache; ErrorSaveContext *escontext; } jsonexpr_coercion; + + /* for batch vector construction */ + struct + { + struct BatchVector *bv; + } batch_vector; } d; } ExprEvalStep; @@ -917,4 +938,26 @@ extern void ExecEvalAggOrderedTransDatum(ExprState *state, ExprEvalStep *op, extern void ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +/* ---------- BatchVector stuff ------------- */ + +/* Vector fetch spec for a list of simple Vars. */ +typedef struct BatchVector +{ + /* immutable after BatchVectorCreate */ + AttrNumber *attnos; /* [ncols] */ + int ncols; + int maxrows; + int last_var; + + /* per batch state */ + Datum **cols; /* [ncols][maxbatch] */ + bool **nulls; /* [ncols][maxbatch] */ + bool hasnull; /* is any datum in cols NULL? */ + int nrows; /* #rows loaded into cols/nulls */ +} BatchVector; + +extern void ExecBuildInnerBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecBuildOuterBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecBuildScanBatchVector(ExprState *state, ExprEvalStep *op, ExprContext *econtext); + #endif /* EXEC_EXPR_H */ diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index 095e4cc82e3..2e2192fb3cf 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -347,6 +347,8 @@ extern Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot); extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum); extern void slot_getsomeattrs_int(TupleTableSlot *slot, int attnum); +struct TupleBatch; +extern void slot_getsomeattrs_batch(struct TupleBatch *b, int attnum); #ifndef FRONTEND diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 9b81b842161..fdfe8b4ddaf 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -277,6 +277,14 @@ typedef struct ExprContext #define FIELDNO_EXPRCONTEXT_OUTERTUPLE 3 TupleTableSlot *ecxt_outertuple; + /* For batched evaluation using batch-aware EEOPs */ +#define FIELDNO_EXPRCONTEXT_INNERBATCH 4 + TupleBatch *inner_batch; +#define FIELDNO_EXPRCONTEXT_OUTERBATCH 5 + TupleBatch *outer_batch; +#define FIELDNO_EXPRCONTEXT_SCANBATCH 6 + TupleBatch *scan_batch; + /* Memory contexts for expression evaluation --- see notes above */ MemoryContext ecxt_per_query_memory; MemoryContext ecxt_per_tuple_memory; @@ -289,27 +297,27 @@ typedef struct ExprContext * Values to substitute for Aggref nodes in the expressions of an Agg * node, or for WindowFunc nodes within a WindowAgg node. */ -#define FIELDNO_EXPRCONTEXT_AGGVALUES 8 +#define FIELDNO_EXPRCONTEXT_AGGVALUES 11 Datum *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */ -#define FIELDNO_EXPRCONTEXT_AGGNULLS 9 +#define FIELDNO_EXPRCONTEXT_AGGNULLS 12 bool *ecxt_aggnulls; /* null flags for aggs/windowfuncs */ /* Value to substitute for CaseTestExpr nodes in expression */ -#define FIELDNO_EXPRCONTEXT_CASEDATUM 10 +#define FIELDNO_EXPRCONTEXT_CASEDATUM 13 Datum caseValue_datum; -#define FIELDNO_EXPRCONTEXT_CASENULL 11 +#define FIELDNO_EXPRCONTEXT_CASENULL 14 bool caseValue_isNull; /* Value to substitute for CoerceToDomainValue nodes in expression */ -#define FIELDNO_EXPRCONTEXT_DOMAINDATUM 12 +#define FIELDNO_EXPRCONTEXT_DOMAINDATUM 15 Datum domainValue_datum; -#define FIELDNO_EXPRCONTEXT_DOMAINNULL 13 +#define FIELDNO_EXPRCONTEXT_DOMAINNULL 16 bool domainValue_isNull; /* Tuples that OLD/NEW Var nodes in RETURNING may refer to */ -#define FIELDNO_EXPRCONTEXT_OLDTUPLE 14 +#define FIELDNO_EXPRCONTEXT_OLDTUPLE 17 TupleTableSlot *ecxt_oldtuple; -#define FIELDNO_EXPRCONTEXT_NEWTUPLE 15 +#define FIELDNO_EXPRCONTEXT_NEWTUPLE 18 TupleTableSlot *ecxt_newtuple; /* Link to containing EState (NULL if a standalone ExprContext) */ -- 2.43.0