Subject: Re: LLVM 22

From: Thomas Munro <[email-masked]>
Date: Sun, 11 Jan 2026 20:09:58 +1300

On Sun, Jan 4, 2026 at 6:02 PM Thomas Munro <[email-masked]> wrote:
> On Sat, Jan 3, 2026 at 3:02 PM Thomas Munro <[email-masked]> wrote:
> > 1.  We won't need our local llvm::backport::SectionMemoryManager for
> > LLVM 22, so it will be nice to draw a line under that messy business.
> > See commit message for details.
>
> While that's true, there is a problem with the patch I posted:
> "ReserveAlloc" is not enabled when called from C.  I can't actually
> reproduce the issue locally due to lack of RAM connected to an ARM
> CPU, or I'd have noticed that...  I'll attempt to do something about
> that upstream[1], let's see...  if not, we can still use the new
> in-tree SectionMemoryManager, but we'll still need some C++ glue code.

That was successful, so here is an update.

A new unrelated assertion started firing in LLVM main/22 a few days ago:

    v_nullbytemask = l_int8_const(lc, 1 << ((attnum) & 0x07));
    Assertion failed: (llvm::isUIntN(BitWidth, val) && "Value is not
an N-bit unsigned value")

Here is a fix for that.
From 8f799057689934dd6099ba911a1450499a61f7b6 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email-masked]>
Date: Wed, 31 Dec 2025 17:35:04 +1300
Subject: [PATCH v2 1/3] jit: Skip local SectionMemoryManager for LLVM 22.

LLVM 22 has the fix that we copied into our tree in commit 9044fc1d and
a new function to reach it[1][2], so we only need to use our local copy
for LLVM < 22.

The only change that we don't have from the version that was committed
is the addition of the LLVM_ABI macro to control shared library exports,
but that isn't defined before LLVM 20 (our copy has to work with earlier
versions), and isn't appropriate here as our copy is private and in our
own library.

[1] https://github.com/llvm/llvm-project/pull/71968
[2] https://github.com/llvm/llvm-project/pull/174307

Backpatch-through: 14
Discussion: https://postgr.es/m/CA%2BhUKGJTumad75o8Zao-LFseEbt%3DenbUFCM7LZVV%3Dc8yg2i7dg%40mail.gmail.com
---
 src/backend/jit/llvm/SectionMemoryManager.cpp | 22 ++++++-------------
 src/backend/jit/llvm/llvmjit.c                |  5 ++++-
 src/include/jit/SectionMemoryManager.h        |  2 +-
 src/include/jit/llvmjit_backport.h            |  4 ++--
 4 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp
index 2171db5f382..e113ce81a3d 100644
--- a/src/backend/jit/llvm/SectionMemoryManager.cpp
+++ b/src/backend/jit/llvm/SectionMemoryManager.cpp
@@ -1,25 +1,17 @@
 /*
- * This file is from https://github.com/llvm/llvm-project/pull/71968
- * with minor modifications to avoid name clash and work with older
- * LLVM versions.  The llvm::backport::SectionMemoryManager class is a
- * drop-in replacement for llvm::SectionMemoryManager, for use with
- * llvm::RuntimeDyld.  It fixes a memory layout bug on large memory
- * ARM systems (see pull request for details).  If the LLVM project
- * eventually commits the change, we may need to resynchronize our
- * copy with any further modifications, but they would be unlikely to
- * backport it into the LLVM versions that we target so we would still
- * need this copy.
+ * This file is from LLVM 22 (originally pull request #71968), with minor
+ * modifications to avoid name clash and work with older LLVM versions.  It
+ * replaces llvm::SectionMemoryManager, and is injected into llvm::RuntimeDyld
+ * to fix a memory layout bug on large memory ARM systems on LLVM < 22.
  *
- * In the future we will switch to using JITLink instead of
- * RuntimeDyld where possible, and later remove this code (.cpp, .h,
- * .LICENSE) after all LLVM versions that we target allow it.
+ * We can remove this code (.cpp, .h, .LICENSE) once LLVM 22 is our minimum
+ * supported version or we've switched to JITLink for at least Aarch64.
  *
  * This file is a modified copy of a part of the LLVM source code that
  * we would normally access from the LLVM library.  It is therefore
  * covered by the license at https://llvm.org/LICENSE.txt, reproduced
  * verbatim in SectionMemoryManager.LICENSE in fulfillment of clause
- * 4a.  The bugfix changes from the pull request are also covered, per
- * clause 5.
+ * 4a.
  */
 
 //===- SectionMemoryManager.cpp - Memory manager for MCJIT/RtDyld *- C++ -*-==//
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 8d009dd5cf7..8fb282f6cce 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -1172,7 +1172,10 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error)
 static LLVMOrcObjectLayerRef
 llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple)
 {
-#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
+#if LLVM_VERSION_MAJOR >= 22
+	LLVMOrcObjectLayerRef objlayer =
+		LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManagerReserveAlloc(ES, true);
+#elif defined(USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER)
 	LLVMOrcObjectLayerRef objlayer =
 		LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES);
 #else
diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h
index 924a99b0d33..4c9bd1c7f01 100644
--- a/src/include/jit/SectionMemoryManager.h
+++ b/src/include/jit/SectionMemoryManager.h
@@ -1,5 +1,5 @@
 /*
- * This is a copy LLVM source code modified by the PostgreSQL project.
+ * This is a copy of LLVM source code modified by the PostgreSQL project.
  * See SectionMemoryManager.cpp for notes on provenance and license.
  */
 
diff --git a/src/include/jit/llvmjit_backport.h b/src/include/jit/llvmjit_backport.h
index cba8eafc4f3..71cfdfc832f 100644
--- a/src/include/jit/llvmjit_backport.h
+++ b/src/include/jit/llvmjit_backport.h
@@ -8,14 +8,14 @@
 #include <llvm/Config/llvm-config.h>
 
 /*
- * LLVM's RuntimeDyld can produce code that crashes on larger memory ARM
+ * Pre-LLVM 22 RuntimeDyld can produce code that crashes on large memory ARM
  * systems, because llvm::SectionMemoryManager allocates multiple pieces of
  * memory that can be placed too far apart for the generated code.  See
  * src/backend/jit/llvm/SectionMemoryManager.cpp for the patched replacement
  * class llvm::backport::SectionMemoryManager that we use as a workaround.
  * This header controls whether we use it.
  */
-#if defined(__aarch64__)
+#if defined(__aarch64__) && LLVM_VERSION_MAJOR < 22
 #define USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER
 #endif
 
-- 
2.52.0

From 9db2fb14b57863fb1b5ab8e211e38c4ed1628c77 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email-masked]>
Date: Wed, 19 Nov 2025 00:00:13 +1300
Subject: [PATCH v2 2/3] jit: Stop using lifetime.end intrinsic for LLVM 22.

The lifetime.end intrinsic can now only be used for stack memory
allocated with alloca.  We were using it to tell the optimizer that we
are no longer interested in the arguments and null flag in a
FunctionCallInfo struct, so it could avoid actually storing them if it
managed to inline the function and keep everything in registers.  It
can't figure that out by itself because it's part of the ExecEvalStep
struct and we scribble on it directly rather than building a new one on
the stack.

Instead, store the special poison value (undef would work too).  This
generates no actual code, but tells the optimizer that we are not
interested in the values.

Deform functions use LLVMBuildAlloca() for a stack variable, but that
memory is reclaimed implicitly by the ret instruction.  This code should
probably do the same, but the change is non-trivial and not studied yet.

https://github.com/llvm/llvm-project/pull/149310
https://llvm.org/docs/LangRef.html#llvm-lifetime-end-intrinsic
https://llvm.org/docs/LangRef.html#i-alloca

Backpatch-through: 14
Reviewed-by: Matheus Alcantara <[email-masked]>
Discussion: https://postgr.es/m/CA%2BhUKGJTumad75o8Zao-LFseEbt%3DenbUFCM7LZVV%3Dc8yg2i7dg%40mail.gmail.com
---
 src/backend/jit/llvm/llvmjit_expr.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 650f1d42a93..b742cce38cf 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -62,7 +62,9 @@ static LLVMValueRef build_EvalXFuncInt(LLVMBuilderRef b, LLVMModuleRef mod,
 									   LLVMValueRef v_state,
 									   ExprEvalStep *op,
 									   int natts, LLVMValueRef *v_args);
+#if LLVM_VERSION_MAJOR < 22
 static LLVMValueRef create_LifetimeEnd(LLVMModuleRef mod);
+#endif
 
 /* macro making it easier to call ExecEval* functions */
 #define build_EvalXFunc(b, mod, funcname, v_state, op, ...) \
@@ -3007,14 +3009,11 @@ BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b,
 			LLVMModuleRef mod, FunctionCallInfo fcinfo,
 			LLVMValueRef *v_fcinfo_isnull)
 {
-	LLVMContextRef lc;
 	LLVMValueRef v_fn;
 	LLVMValueRef v_fcinfo_isnullp;
 	LLVMValueRef v_retval;
 	LLVMValueRef v_fcinfo;
 
-	lc = LLVMGetModuleContext(mod);
-
 	v_fn = llvm_function_reference(context, b, mod, fcinfo);
 
 	v_fcinfo = l_ptr_const(fcinfo, l_ptr(StructFunctionCallInfoData));
@@ -3031,10 +3030,16 @@ BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b,
 		*v_fcinfo_isnull = l_load(b, TypeStorageBool, v_fcinfo_isnullp, "");
 
 	/*
-	 * Add lifetime-end annotation, signaling that writes to memory don't have
-	 * to be retained (important for inlining potential).
+	 * Signal that writes to FunctionCallInfoData don't have to be retained
+	 * (important for inlining potential).
 	 */
+#if LLVM_VERSION_MAJOR >= 22
+	for (int i = 0; i < fcinfo->nargs; ++i)
+		LLVMBuildStore(b, LLVMGetPoison(StructNullableDatum), l_funcvaluep(b, v_fcinfo, i));
+	LLVMBuildStore(b, LLVMGetPoison(TypeStorageBool), v_fcinfo_isnullp);
+#else
 	{
+		LLVMContextRef lc = LLVMGetModuleContext(mod);
 		LLVMValueRef v_lifetime = create_LifetimeEnd(mod);
 		LLVMValueRef params[2];
 
@@ -3046,6 +3051,7 @@ BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b,
 		params[1] = l_ptr_const(&fcinfo->isnull, l_ptr(LLVMInt8TypeInContext(lc)));
 		l_call(b, LLVMGetFunctionType(v_lifetime), v_lifetime, params, lengthof(params), "");
 	}
+#endif
 
 	return v_retval;
 }
@@ -3083,6 +3089,7 @@ build_EvalXFuncInt(LLVMBuilderRef b, LLVMModuleRef mod, const char *funcname,
 	return v_ret;
 }
 
+#if LLVM_VERSION_MAJOR < 22
 static LLVMValueRef
 create_LifetimeEnd(LLVMModuleRef mod)
 {
@@ -3112,3 +3119,4 @@ create_LifetimeEnd(LLVMModuleRef mod)
 
 	return fn;
 }
+#endif
-- 
2.52.0

From e2551375e0093b8e24821a316edf42c6ff2d7ac7 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email-masked]>
Date: Sun, 11 Jan 2026 12:44:25 +1300
Subject: [PATCH v2 3/3] jit: Fix integer constants for LLVM 22.

LLVM 22 fails an assertion where we do:

  l_int8_const(lc, 1 << ((attnum) & 0x07))

Assertion failed: (llvm::isUIntN(BitWidth, val) && "Value is not an
N-bit unsigned value")

That's because it no longer truncates values to fit the type[1].
Unfortunately our l_intN_const() wrapper functions take signed intN_t
arguments that are internally coerced to unsigned long long, triggering
sign extension.  That was OK with automatic truncation, but now it fails
a bit-width test.

Cast to the corresponding unsigned type first to avoid that.

[1] https://github.com/llvm/llvm-project/commit/a83c89495ba6fe0134dcaa02372c320cc7ff0dbf

Backpatch-through: 14
Reviewed-by:
Discussion: https://postgr.es/m/CA%2BhUKGJTumad75o8Zao-LFseEbt%3DenbUFCM7LZVV%3Dc8yg2i7dg%40mail.gmail.com
---
 src/include/jit/llvmjit_emit.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/include/jit/llvmjit_emit.h b/src/include/jit/llvmjit_emit.h
index 089945391ee..83b67dffd25 100644
--- a/src/include/jit/llvmjit_emit.h
+++ b/src/include/jit/llvmjit_emit.h
@@ -47,7 +47,7 @@ l_ptr(LLVMTypeRef t)
 static inline LLVMValueRef
 l_int8_const(LLVMContextRef lc, int8 i)
 {
-	return LLVMConstInt(LLVMInt8TypeInContext(lc), i, false);
+	return LLVMConstInt(LLVMInt8TypeInContext(lc), (uint8) i, false);
 }
 
 /*
@@ -56,7 +56,7 @@ l_int8_const(LLVMContextRef lc, int8 i)
 static inline LLVMValueRef
 l_int16_const(LLVMContextRef lc, int16 i)
 {
-	return LLVMConstInt(LLVMInt16TypeInContext(lc), i, false);
+	return LLVMConstInt(LLVMInt16TypeInContext(lc), (uint16) i, false);
 }
 
 /*
@@ -65,7 +65,7 @@ l_int16_const(LLVMContextRef lc, int16 i)
 static inline LLVMValueRef
 l_int32_const(LLVMContextRef lc, int32 i)
 {
-	return LLVMConstInt(LLVMInt32TypeInContext(lc), i, false);
+	return LLVMConstInt(LLVMInt32TypeInContext(lc), (uint32) i, false);
 }
 
 /*
@@ -74,7 +74,7 @@ l_int32_const(LLVMContextRef lc, int32 i)
 static inline LLVMValueRef
 l_int64_const(LLVMContextRef lc, int64 i)
 {
-	return LLVMConstInt(LLVMInt64TypeInContext(lc), i, false);
+	return LLVMConstInt(LLVMInt64TypeInContext(lc), (uint64) i, false);
 }
 
 /*
-- 
2.52.0

---

From: Anthonin Bonnefoy <[email-masked]>
Date: Wed, 14 Jan 2026 17:12:45 +0100

Hi,

I've tried to generate multiple bitcode for a simple 'select aid % 2
FROM pgbench_accounts limit 10;' query. To keep bitcode simple, I've
modified the passes to use "default<O0>,mem2reg,inline" when we have
JIT inline without optimization (as described in [0]). I've tried the
following
- LLVM21: With lifetime
- LLVM21: Without lifetime
- LLVM22: With Poison
- LLVM22: Without Poison

In the 4 scenarios, the generated bc were the same with the exact same
instructions. Removing the lifetime end or the poison value doesn't
seem to change anything at this level of optimisation.

I'm not sure how to interpret this. Maybe the test is incorrect and a
different function needs to be called to possibly trigger the issue?
Or the poison/lifetime is only useful when going through the O3
optimisation pass?

[0]: https://www.postgresql.org/message-id/CAO6_XqrNjJnbn15ctPv7o4yEAT9fWa-dK15RSyun6QNw9YDtKg%40mail.gmail.com

---

From: Thomas Munro <[email-masked]>
Date: Thu, 22 Jan 2026 15:24:56 +1300

> an N-bit unsigned value")

22 was branched and RC1 is out, but that particular change was
reverted from 22[1].  It had already been through a commit/revert
cycle before and at a wild guess, it probably caused too much work
elsewhere with not enough notice.  It's still present in main, so
consider the v2-0003 patch booted out of here and into the
not-yet-created LLVM 23 thread...

[1] https://github.com/llvm/llvm-project/commit/16bf1c5d6b7f8fda16da5df5a2b195a6b10d08ed

---

From: Andres Freund <[email-masked]>
Date: Wed, 28 Jan 2026 20:27:29 -0500

Hi,

> Or the poison/lifetime is only useful when going through the O3

I think it's the latter - at -O0 there's nothing that could use the
information.

The goal of the lifetime annotations was to allow llvm to remove stores an
loads of FunctionCallInfo->{args,isnull}. After we stored e.g. fcinfo->isnull
before a function call and then checked it after the function call, we don't
need it anymore.  I think that can only matter when the called function is
actually inlined, otherwise there's no way that LLVM can see the store is
unnecessary.

Unfortunately there's an issue with modern LLVM, regardless of lifetime or
poison.  Generally it's able to eliminate stores that are followed by a
poison, but if there's a load inbetween, it fails. The odd part is that it
*is* able to eliminate the load (by forwarding the stored value).

It seems to be an ordering issue - instcombine is required to remove the load,
but also removes the poison, which in turn is required for dead store
elimination.  Gngng.

I've attached a reproducer.

I'm not sure the llvm folks will be all that interested - there's no real C
correspondance to this. And, as it turns out, if I feed the memory to
something like free(), the analysis actually *does* figure out that it's not
needed anymore.

I think if / once we move most of this to a stack allocation, the problem
would also vanish.

Greetings,

Andres Freund

---

From: Anthonin Bonnefoy <[email-masked]>
Date: Thu, 12 Feb 2026 11:56:49 +0100

> actually inlined, otherwise there's no way that LLVM can see the store is

Thanks for the context, that makes things easier to understand.

I've run another test using:
- "select pg_last_xact_replay_timestamp();" for the query, compared to
int4mod, has a reachable PG_RETURN_NULL.
- run with "options='-cjit_inline_above_cost=0
-cjit_optimize_above_cost=100000 -cjit_above_cost=0
-cjit_dump_bitcode=true'" to force inlining while only going through
O0 pass.
- Then manually ran the optimisation pass with "opt-21
jit_initial_dump.ll --passes='default<O3>' -S"

The initial dump is using lifetime.end, but it can be used to check
what happens with poisoned values by manually replacing it.

Using lifetime_end, the store to isnull:
  28:
    store i8 1, ptr inttoptr (i64 200635374787156 to ptr), align 4
    br label %pg_last_xact_replay_timestamp.exit
is indeed removed.

Removing the lifetime_end calls, the store call is still present (I
wanted to make sure it wasn't removed by another optimization)
Replacing the lifetime_end calls with poison stores generates the same
IR as if there was no lifetime_end, and the store call is still
present. Tested with opt-21 and opt-22.

So it looks like that using poison value doesn't replicate
lifetime_end behaviour (at least, for the jit dump I've tested).

---

From: Devrim Gündüz <[email-masked]>
Date: Fri, 13 Mar 2026 16:36:04 +0300

Hi,

> an N-bit unsigned value")

Fedora pushed 22.1.0 to both Fedora 44 beta and rawhide repos, so I
tested these patches. Builds are fine and all regression tests pass.
Anything else I should check?

Regards,
-- 
Devrim Gündüz
Open Source Solution Architect, PostgreSQL Major Contributor
BlueSky: @devrim.gunduz.org , @gunduz.org

---

From: Thomas Munro <[email-masked]>
Date: Wed, 1 Apr 2026 16:25:02 +1300

On Wed, Apr 1, 2026 at 12:55 PM Tom Lane <[email-masked]> wrote:
> Devrim =?ISO-8859-1?Q?G=FCnd=FCz?= <[email-masked]> writes:
> Where are we on getting these patches pushed?  I think the reason that
> BF member midge has been failing of late is that it's running LLVM 22
> (if not indeed something even newer --- configure doesn't report
> the clang version, sadly).  Also, I've reproduced this symptom:

Working on this, more shortly...  I'm trying to figure out if Anthonin
and Andres's feedback means the poison approach does nothing useful
and we might as well just #ifdef out the lifetime.end stuff for LLVM
>= 22 to fix the breakage today.

Either way it looks like we need a patch to use alloca instead, which
I'll also look into...

> Intrinsic has incorrect argument type!
> ptr @llvm.lifetime.end.p0
> Intrinsic has incorrect argument type!
> ptr @llvm.lifetime.end.p0
> 2026-03-31 18:58:20.218 EDT [28486] FATAL:  fatal llvm error: Broken module found, compilation aborted!
>
> on a fresh Fedora 44/x86_64 installation with llvm 22.1.1.  So this is
> going to be a production compiler RSN.  (F44 is still labeled beta,
> but not for much longer.)

Yep, that's the issue alright.

---

From: Thomas Munro <[email-masked]>
Date: Thu, 2 Apr 2026 16:20:41 +1300

> >= 22 to fix the breakage today.

Done.  Hopefully midge and Devrim will now turn green :-)

> Either way it looks like we need a patch to use alloca instead, which

I see a few options, but I need to hack on them for a while to figure
out the tradeoffs, or what I'm missing...  after the freeze.

---

From: Tom Lane <[email-masked]>
Date: Wed, 01 Apr 2026 23:31:37 -0400

> Done.  Hopefully midge and Devrim will now turn green :-)

Just out of curiosity: I see you back-patched that all the way,
but midge had only been failing on v18 and HEAD.  Were you just
being defensive, or is there something deeper there?

			regards, tom lane

---

From: Thomas Munro <[email-masked]>
Date: Thu, 2 Apr 2026 17:16:14 +1300

> being defensive, or is there something deeper there?

It was failing locally for me on all branches.

I don't know why midge wasn't failing on 14-17.  Could jit be disabled
somewhere secret?  Aarch64 vs amd64, but this issue doesn't seem to be
architecture related, it's IR-level.

---

From: Tom Lane <[email-masked]>
Date: Thu, 02 Apr 2026 00:26:42 -0400

> It was failing locally for me on all branches.

Ah, thanks for that detail.

> architecture related, it's IR-level.

Definitely not arch-specific, because I reproduced it on x86_64.
midge's lack of failure is odd then, but I'm not sure it's worth
expending a lot of brain cells on.

			regards, tom lane

---

From: Devrim Gündüz <[email-masked]>
Date: Thu, 02 Apr 2026 15:26:56 +0300

Hi,

> Done.  Hopefully midge and Devrim will now turn green :-)

Thanks a lot! I built all supported releases on Fedora 44.

Regards,

---

From: Andres Freund <[email-masked]>
Date: Thu, 2 Apr 2026 10:31:48 -0400

Hi,

> Done.  Hopefully midge and Devrim will now turn green :-)

Thanks!

> out the tradeoffs, or what I'm missing...  after the freeze.

I've experimented a bunch with this, it seems we need the larger changes done
as part of the patchset for removing pointers from the expressions to actually
allow recent-ish LLVM to optimize this.  I did verify that what we did didn't
have an effect with any other recent LLVM either.

The real fix here might be to have a separate calling convention for the very
common case of a scalar stable function with 1-3 arguments.  We loose a fair
bit of efficiency even in interpreted execution due to ferrying arguments,
their nullness, and the nullness of the return value through memory.

Greetings,

Andres Freund

---

From: Thomas Munro <[email-masked]>
Date: Fri, 3 Apr 2026 16:04:13 +1300

> have an effect with any other recent LLVM either.

Yeah, I noticed this connection as well, coming at it from a keyhole
how-do-I-fix-THIS-problem angle.  It seemed to me that where
ExecInitFunc() builds the code to compute argument values to push into
&fcinfo->args[argno].value (a palloc'd FunctionCallInfoData object),
it might first alloca the space and store the collid etc (and after
return, it could lifetime.end it, or maybe the eventual ret in the
caller is enough but I don't see any reason not to lifetime.end it
ASAP), and then the destination would become a pointer into that, and
the most natural thing would be a stack pointer-relative one, and then
you'd have removed a major source of non-cacheability of compiled
expressions.  It took me a while to grok the function argument layout,
which is ... this might be a stretch... a bit like Fortran, neither a
linear stack nor a spaghetti stack, but just a bag of variables ready
to be used as functions arguments, with recursion not permitted.  And
also to grok the quirks of our V1 calls that compelled you to do it
like that.  But I'm still learning the secrets of this code and I may
be way off base in these musings, I haven't actually tried anything
and it sounds like I should keep out of your way...

> their nullness, and the nullness of the return value through memory.

Yeah.  I understand much better why you say that now.
FunctionCallInfoData holds data with two different lifetimes, some of
which might not be needed.

---

From: Thomas Munro <[email-masked]>
Date: Fri, 3 Apr 2026 16:06:35 +1300

On Fri, Apr 3, 2026 at 4:04 PM Thomas Munro <[email-masked]> wrote:
> Fortran

(Actually I take that back, IIRC Fortran had global variables for each
function's arguments, while we have arg space for each call site.  But
the point is they're preallocated.  Anyway...)