From: Andres Freund <andres@anarazel.de>
Subject: [PATCH v3 4/4] wip: llvmjit: Plug memory leak when performing inlining.
Date: Mon, 31 Oct 2022 22:35:56 -0700
When performing inlining LLVM unfortunately "leaks" types (the types
survive and are usable, but a new round of inlining will recreate new
types that are structurally equivalent). I could not come up with a
backpatchable fix for that, so address the problem by dropping &
recreating the LLVMContextRef that contains all IR related data -
which releases all the types.
That incurs some overhead, so only do so after 10000 JITed queries.
Needs a good bit more work:
- confusing naming between different types of contexts (PG side
LLVMJitContext, and LLVM side LLVMContextRef)
- add detailed explanation of leak issue
- consider only incrementing the counter triggering the recreation of
the memory context during inlining.
- several FIXMEs in code
Reported-By: Justin Pryzby <pryzby@telsasoft.com>
Reported-By: Kurt Roeckx <kurt@roeckx.be>
Reported-By: Jaime Casanova <jcasanov@systemguards.com.ec>
Author: Andres Freund
Discussion: https://postgr.es/m/7acc8678-df5f-4923-9cf6-e843131ae89d@www.fastmail.com
Discussion: https://postgr.es/m/20201218235607.GC30237@telsasoft.com
---
src/include/jit/llvmjit.h | 8 +++
src/backend/jit/llvm/llvmjit.c | 76 ++++++++++++++++++++++++-
src/backend/jit/llvm/llvmjit_inline.cpp | 12 ++++
3 files changed, 94 insertions(+), 2 deletions(-)
inline patch 1: src/include/jit/llvmjit.h, src/backend/jit/llvm/llvmjit.c, src/backend/jit/llvm/llvmjit_inline.cpp (+94/-2 lines) download inline-1.patchdiff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.hindex f4c01c28672..d90d7b2e347 100644--- a/src/include/jit/llvmjit.h+++ b/src/include/jit/llvmjit.h@@ -42,6 +42,13 @@ typedef struct LLVMJitContext
/* number of modules created */
size_t module_generation;
+ /*+ * The LLVM Context used by this JIT context. An LLVM context is reused+ * across many compilations, but occasionally reset to prevent it using+ * too much memory due to more and more types accumulating.+ */+ LLVMContextRef llvm_context;+
/* current, "open for write", module */
LLVMModuleRef module;
@@ -99,6 +106,7 @@ extern LLVMValueRef llvm_function_reference(LLVMJitContext *context,
LLVMModuleRef mod,
FunctionCallInfo fcinfo);
+extern void llvm_inline_reset_caches(void);
extern void llvm_inline(LLVMModuleRef mod);
/*
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.cindex 3625edceb3e..a7e19a69a8b 100644--- a/src/backend/jit/llvm/llvmjit.c+++ b/src/backend/jit/llvm/llvmjit.c@@ -42,6 +42,10 @@
#include "utils/memutils.h"
#include "utils/resowner_private.h"
++#define LLVMJIT_LLVM_CONTEXT_REUSE_MAX 100++
/* Handle of a module emitted via ORC JIT */
typedef struct LLVMJitHandle
{
@@ -81,6 +85,10 @@ static LLVMModuleRef llvm_types_module = NULL;
static bool llvm_session_initialized = false;
static size_t llvm_generation = 0;
+/* number of LLVMJitContexts that currently are in use */+static size_t llvm_jit_context_in_use_count = 0;+/* how many times has the current LLVMContextRef been used */+static size_t llvm_llvm_context_reuse_count = 0;
static const char *llvm_triple = NULL;
static const char *llvm_layout = NULL;
static LLVMContextRef llvm_context;
@@ -141,6 +149,55 @@ llvm_create_context(int jitFlags)
llvm_session_initialize();
+ /*+ * Every now and then create a new LLVMContextRef. Unfortunately, during+ * every round of inlining, types may "leak" (they can still be found/used+ * via the context, but new types will be created the next time in+ * inlining is performed). To prevent that from slowly accumulating+ * problematic amounts of memory, recreate the LLVMContextRef we use. We+ * don't want to do so too often, as that implies some overhead+ * (particularly re-loading the module summaries / modules is fairly+ * expensive).+ *+ * We can only safely recreate the LLVM context if no other code is being+ * JITed, otherwise we'd release the types in use for that.+ *+ * FIXME: Extract into helper function.+ */+ if (llvm_jit_context_in_use_count == 0 &&+ llvm_llvm_context_reuse_count > LLVMJIT_LLVM_CONTEXT_REUSE_MAX)+ {+ llvm_llvm_context_reuse_count = 0;++ Assert(llvm_context != NULL);++ /*+ * Need to reset the modules that the inlining code caches before+ * disposing of the context. LLVM modules exist within a specific LLVM+ * context, therefore disposing of the context before resetting the+ * cache would lead to dangling pointers to modules.+ */+ llvm_inline_reset_caches();++ if (llvm_context != NULL)+ LLVMContextDispose(llvm_context);+ llvm_context = LLVMContextCreate();++ /*+ * Re-build cached type information, so code generation code can rely+ * on that information to be present (also prevents the variables to+ * be dangling references).+ *+ * FIXME: should split the handling of llvm_triple / llvm_layout out+ * of llvm_create_types() - that doesn't need to be redone.+ */+ llvm_create_types();+ }+ else+ {+ llvm_llvm_context_reuse_count++;+ }+
ResourceOwnerEnlargeJIT(CurrentResourceOwner);
context = MemoryContextAllocZero(TopMemoryContext,
@@ -151,6 +208,8 @@ llvm_create_context(int jitFlags)
context->base.resowner = CurrentResourceOwner;
ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
+ llvm_jit_context_in_use_count++;+
return context;
}
@@ -163,6 +222,12 @@ llvm_release_context(JitContext *context)
LLVMJitContext *llvm_context = (LLVMJitContext *) context;
ListCell *lc;
+ /*+ * Consider as cleaned up even if we skip doing so below, that way we can+ * verify the tracking is correct (see llvm_shutdown()).+ */+ llvm_jit_context_in_use_count--;+
/*
* When this backend is exiting, don't clean up LLVM. As an error might
* have occurred from within LLVM, we do not want to risk reentering. All
@@ -901,6 +966,10 @@ llvm_shutdown(int code, Datum arg)
return;
}
+ if (llvm_jit_context_in_use_count != 0)+ elog(PANIC, "LLVMJitContext in use count not 0 at exit (is %zu)",+ llvm_jit_context_in_use_count);+
#if LLVM_VERSION_MAJOR > 11
{
if (llvm_opt3_orc)
@@ -1004,8 +1073,11 @@ llvm_create_types(void)
* Load triple & layout from clang emitted file so we're guaranteed to be
* compatible.
*/
- llvm_triple = pstrdup(LLVMGetTarget(llvm_types_module));- llvm_layout = pstrdup(LLVMGetDataLayoutStr(llvm_types_module));+ if (llvm_triple == NULL)+ {+ llvm_triple = pstrdup(LLVMGetTarget(llvm_types_module));+ llvm_layout = pstrdup(LLVMGetDataLayoutStr(llvm_types_module));+ }
TypeSizeT = llvm_pg_var_type("TypeSizeT");
TypeParamBool = load_return_type(llvm_types_module, "FunctionReturningBool");
diff --git a/src/backend/jit/llvm/llvmjit_inline.cpp b/src/backend/jit/llvm/llvmjit_inline.cppindex 73fe253f2ec..6b46e141fcb 100644--- a/src/backend/jit/llvm/llvmjit_inline.cpp+++ b/src/backend/jit/llvm/llvmjit_inline.cpp@@ -152,6 +152,18 @@ summaries_for_guid(const InlineSearchPath& path, llvm::GlobalValue::GUID guid);
#define ilog(...) (void) 0
#endif
+/*+ * Reset inlining related state. This needs to be called before the currently+ * used LLVMContextRef is disposed (and a new one create), otherwise we would+ * have dangling references to deleted modules.+ */+void+llvm_inline_reset_caches(void)+{+ module_cache->clear();+ summary_cache->clear();+}+
/*
* Perform inlining of external function references in M based on a simple
* cost based analysis.
--
2.38.0
--ujwq2ejpw4d4bq5o--
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-bugs@postgresql.org
Cc: andres@anarazel.de
Subject: Re: [PATCH v3 4/4] wip: llvmjit: Plug memory leak when performing inlining.
In-Reply-To: <no-message-id-7949@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox