Le jeudi 22 janvier 2026 à 8:54 PM, Matheus Alcantara 
<[email protected]> a écrit :

> Hi,
> 
> On 07/01/26 12:08, Pierre Ducroquet wrote:
> 
> > Hi
> > 
> > While reading the code generated by llvmjit, I realized the number of LLVM 
> > basic blocks used in tuple deforming was directly visible in the generated 
> > assembly code with the following code:
> > 0x723382b781c1: jmp 0x723382b781c3
> > 0x723382b781c3: jmp 0x723382b781eb
> > 0x723382b781c5: mov -0x20(%rsp),%rax
> > 0x723382b781..: ... .....
> > 0x723382b781e7: mov %cx,(%rax)
> > 0x723382b781ea: ret
> > 0x723382b781eb: jmp 0x723382b781ed
> > 0x723382b781ed: jmp 0x723382b781ef
> > 0x723382b781ef: jmp 0x723382b781f1
> > 0x723382b781f1: jmp 0x723382b781f3
> > 0x723382b781f3: mov -0x30(%rsp),%rax
> > 0x723382b781..: ... ......
> > 0x723382b78208: mov %rcx,(%rax)
> > 0x723382b7820b: jmp 0x723382b781c5
> > That's a lot of useless jumps, and LLVM has a specific pass to get rid of 
> > these. The attached patch modifies the llvmjit code to always call this 
> > pass, even below jit_optimize_above_cost.
> > 
> > On a basic benchmark (a simple select * from table where f = 42), this 
> > optimization saved 7ms of runtime while using only 0.1 ms of extra 
> > optimization time.
> 
> 
> The patch needs a rebase due to e5d99b4d9ef.
> 
> You've added the "simplifycfg" only when the "jit_optimize_above_cost"
> is not triggered which will use the default<O0> and mem2reg passes, the
> 
> default<O3> pass already include "simplifycfg"?
> 
> 
> With e5d99b4d9ef being committed, should we add "simplifycfg" when
> PGJIT_INLINE bit is set since it also use the default<O0> and mem2reg
> 
> passes?

Hi

Thank you, here is a rebased version of the patch.
To answer your questions:
- O3 already includes simplifycfg, so no need to modify O3
- any code generated by our llvmjit provider, esp. tuple deforming, is heavily 
dependent on simplifycfg, so when O0 is the basis we should always add this pass

From cb5cb74461ac9407c16903bfa9d2855f4e76918e Mon Sep 17 00:00:00 2001
From: Pierre Ducroquet <[email protected]>
Date: Wed, 7 Jan 2026 15:43:19 +0100
Subject: [PATCH] llvmjit: always use the simplifycfg pass

The simplifycfg pass will remove empty or unreachable LLVM basic blocks,
and merge blocks together when possible.
This is important because the tuple  deforming code will generate a lot of
basic blocks, and previously with O0 we did not run this pass, thus creating
this kind of (amd64) machine code:
   0x723382b781c1:      jmp    0x723382b781c3
   0x723382b781c3:      jmp    0x723382b781eb
   0x723382b781c5:      mov    -0x20(%rsp),%rax
   0x723382b781..:      ...    .....
   0x723382b781e7:      mov    %cx,(%rax)
   0x723382b781ea:      ret
   0x723382b781eb:      jmp    0x723382b781ed
   0x723382b781ed:      jmp    0x723382b781ef
   0x723382b781ef:      jmp    0x723382b781f1
   0x723382b781f1:      jmp    0x723382b781f3
   0x723382b781f3:      mov    -0x30(%rsp),%rax
   0x723382b781..:      ...    ......
   0x723382b78208:      mov    %rcx,(%rax)
   0x723382b7820b:      jmp    0x723382b781c5

This is not efficient at all, and triggering the simplifycfg pass ends up
tacking a few hundreds micro seconds while possibly saving much more time
during execution. On a basic benchmark, I saved 7ms on query runtime while
using 0.2ms on extra JIT compilation overhead
---
 src/backend/jit/llvm/llvmjit.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 2e8aa4749db..c22f83e97cf 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -633,6 +633,11 @@ llvm_optimize_module(LLVMJitContext *context, LLVMModuleRef module)
 	{
 		/* we rely on mem2reg heavily, so emit even in the O0 case */
 		LLVMAddPromoteMemoryToRegisterPass(llvm_fpm);
+		/*
+		 * the tuple deforming generates a lot of basic blocks,
+		 * simplify them even with O0
+		 */
+		LLVMAddCFGSimplificationPass(llvm_fpm);
 	}
 
 	LLVMPassManagerBuilderPopulateFunctionPassManager(llvm_pmb, llvm_fpm);
@@ -676,10 +681,10 @@ llvm_optimize_module(LLVMJitContext *context, LLVMModuleRef module)
 		passes = "default<O3>";
 	else if (context->base.flags & PGJIT_INLINE)
 		/* if doing inlining, but no expensive optimization, add inline pass */
-		passes = "default<O0>,mem2reg,inline";
+		passes = "default<O0>,mem2reg,simplifycfg,inline";
 	else
 		/* default<O0> includes always-inline pass */
-		passes = "default<O0>,mem2reg";
+		passes = "default<O0>,mem2reg,simplifycfg";
 
 	options = LLVMCreatePassBuilderOptions();
 
-- 
2.43.0

Reply via email to