Skip to content

Commit 966866f

Browse files
danielgustafssonpull[bot]
authored andcommitted
llvmjit: Use explicit LLVMContextRef for inlining
When performing inlining LLVM unfortunately "leaks" types (the types survive and are usable, but a new round of inlining will recreate new structurally equivalent types). This accumulation will over time amount to a memory leak which for some queries can be large enough to trigger the OOM process killer. To avoid accumulation of types, all IR related data is stored in an LLVMContextRef which is dropped and recreated in order to release all types. Dropping and recreating incurs overhead, so it will be done only after 100 queries. This is a heuristic which might be revisited, but until we can get the size of the context from LLVM we are flying a bit blind. This issue has been reported several times, there may be more references to it in the archives on top of the threads linked below. Backpatching of this fix will be handled once it has matured in master for a bit. Reported-By: Justin Pryzby <pryzby@telsasoft.com> Reported-By: Kurt Roeckx <kurt@roeckx.be> Reported-By: Jaime Casanova <jcasanov@systemguards.com.ec> Reported-By: Lauri Laanmets <pcspets@gmail.com> Author: Andres Freund and Daniel Gustafsson Discussion: https://postgr.es/m/7acc8678-df5f-4923-9cf6-e843131ae89d@www.fastmail.com Discussion: https://postgr.es/m/20201218235607.GC30237@telsasoft.com Discussion: https://postgr.es/m/CAPH-tTxLf44s3CvUUtQpkDr1D8Hxqc2NGDzGXS1ODsfiJ6WSqA@mail.gmail.com
1 parent 5e239c2 commit 966866f

File tree

6 files changed

+234
-94
lines changed

6 files changed

+234
-94
lines changed

src/backend/jit/llvm/llvmjit.c

Lines changed: 122 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
#include "utils/memutils.h"
4343
#include "utils/resowner_private.h"
4444

45+
#define LLVMJIT_LLVM_CONTEXT_REUSE_MAX 100
46+
4547
/* Handle of a module emitted via ORC JIT */
4648
typedef struct LLVMJitHandle
4749
{
@@ -81,8 +83,15 @@ static LLVMModuleRef llvm_types_module = NULL;
8183

8284
static bool llvm_session_initialized = false;
8385
static size_t llvm_generation = 0;
86+
87+
/* number of LLVMJitContexts that currently are in use */
88+
static size_t llvm_jit_context_in_use_count = 0;
89+
90+
/* how many times has the current LLVMContextRef been used */
91+
static size_t llvm_llvm_context_reuse_count = 0;
8492
static const char *llvm_triple = NULL;
8593
static const char *llvm_layout = NULL;
94+
static LLVMContextRef llvm_context;
8695

8796

8897
static LLVMTargetRef llvm_targetref;
@@ -103,6 +112,8 @@ static void llvm_compile_module(LLVMJitContext *context);
103112
static void llvm_optimize_module(LLVMJitContext *context, LLVMModuleRef module);
104113

105114
static void llvm_create_types(void);
115+
static void llvm_set_target(void);
116+
static void llvm_recreate_llvm_context(void);
106117
static uint64_t llvm_resolve_symbol(const char *name, void *ctx);
107118

108119
#if LLVM_VERSION_MAJOR > 11
@@ -124,6 +135,63 @@ _PG_jit_provider_init(JitProviderCallbacks *cb)
124135
cb->compile_expr = llvm_compile_expr;
125136
}
126137

138+
139+
/*
140+
* Every now and then create a new LLVMContextRef. Unfortunately, during every
141+
* round of inlining, types may "leak" (they can still be found/used via the
142+
* context, but new types will be created the next time in inlining is
143+
* performed). To prevent that from slowly accumulating problematic amounts of
144+
* memory, recreate the LLVMContextRef we use. We don't want to do so too
145+
* often, as that implies some overhead (particularly re-loading the module
146+
* summaries / modules is fairly expensive). A future TODO would be to make
147+
* this more finegrained and only drop/recreate the LLVMContextRef when we know
148+
* there has been inlining. If we can get the size of the context from LLVM
149+
* then that might be a better way to determine when to drop/recreate rather
150+
* then the usagecount heuristic currently employed.
151+
*/
152+
static void
153+
llvm_recreate_llvm_context(void)
154+
{
155+
if (!llvm_context)
156+
elog(ERROR, "Trying to recreate a non-existing context");
157+
158+
/*
159+
* We can only safely recreate the LLVM context if no other code is being
160+
* JITed, otherwise we'd release the types in use for that.
161+
*/
162+
if (llvm_jit_context_in_use_count > 0)
163+
{
164+
llvm_llvm_context_reuse_count++;
165+
return;
166+
}
167+
168+
if (llvm_llvm_context_reuse_count <= LLVMJIT_LLVM_CONTEXT_REUSE_MAX)
169+
{
170+
llvm_llvm_context_reuse_count++;
171+
return;
172+
}
173+
174+
/*
175+
* Need to reset the modules that the inlining code caches before
176+
* disposing of the context. LLVM modules exist within a specific LLVM
177+
* context, therefore disposing of the context before resetting the cache
178+
* would lead to dangling pointers to modules.
179+
*/
180+
llvm_inline_reset_caches();
181+
182+
LLVMContextDispose(llvm_context);
183+
llvm_context = LLVMContextCreate();
184+
llvm_llvm_context_reuse_count = 0;
185+
186+
/*
187+
* Re-build cached type information, so code generation code can rely on
188+
* that information to be present (also prevents the variables to be
189+
* dangling references).
190+
*/
191+
llvm_create_types();
192+
}
193+
194+
127195
/*
128196
* Create a context for JITing work.
129197
*
@@ -140,6 +208,8 @@ llvm_create_context(int jitFlags)
140208

141209
llvm_session_initialize();
142210

211+
llvm_recreate_llvm_context();
212+
143213
ResourceOwnerEnlargeJIT(CurrentResourceOwner);
144214

145215
context = MemoryContextAllocZero(TopMemoryContext,
@@ -150,6 +220,8 @@ llvm_create_context(int jitFlags)
150220
context->base.resowner = CurrentResourceOwner;
151221
ResourceOwnerRememberJIT(CurrentResourceOwner, PointerGetDatum(context));
152222

223+
llvm_jit_context_in_use_count++;
224+
153225
return context;
154226
}
155227

@@ -159,9 +231,15 @@ llvm_create_context(int jitFlags)
159231
static void
160232
llvm_release_context(JitContext *context)
161233
{
162-
LLVMJitContext *llvm_context = (LLVMJitContext *) context;
234+
LLVMJitContext *llvm_jit_context = (LLVMJitContext *) context;
163235
ListCell *lc;
164236

237+
/*
238+
* Consider as cleaned up even if we skip doing so below, that way we can
239+
* verify the tracking is correct (see llvm_shutdown()).
240+
*/
241+
llvm_jit_context_in_use_count--;
242+
165243
/*
166244
* When this backend is exiting, don't clean up LLVM. As an error might
167245
* have occurred from within LLVM, we do not want to risk reentering. All
@@ -172,13 +250,13 @@ llvm_release_context(JitContext *context)
172250

173251
llvm_enter_fatal_on_oom();
174252

175-
if (llvm_context->module)
253+
if (llvm_jit_context->module)
176254
{
177-
LLVMDisposeModule(llvm_context->module);
178-
llvm_context->module = NULL;
255+
LLVMDisposeModule(llvm_jit_context->module);
256+
llvm_jit_context->module = NULL;
179257
}
180258

181-
foreach(lc, llvm_context->handles)
259+
foreach(lc, llvm_jit_context->handles)
182260
{
183261
LLVMJitHandle *jit_handle = (LLVMJitHandle *) lfirst(lc);
184262

@@ -208,8 +286,8 @@ llvm_release_context(JitContext *context)
208286

209287
pfree(jit_handle);
210288
}
211-
list_free(llvm_context->handles);
212-
llvm_context->handles = NIL;
289+
list_free(llvm_jit_context->handles);
290+
llvm_jit_context->handles = NIL;
213291

214292
llvm_leave_fatal_on_oom();
215293
}
@@ -229,7 +307,7 @@ llvm_mutable_module(LLVMJitContext *context)
229307
{
230308
context->compiled = false;
231309
context->module_generation = llvm_generation++;
232-
context->module = LLVMModuleCreateWithName("pg");
310+
context->module = LLVMModuleCreateWithNameInContext("pg", llvm_context);
233311
LLVMSetTarget(context->module, llvm_triple);
234312
LLVMSetDataLayout(context->module, llvm_layout);
235313
}
@@ -787,6 +865,14 @@ llvm_session_initialize(void)
787865
LLVMInitializeNativeAsmPrinter();
788866
LLVMInitializeNativeAsmParser();
789867

868+
if (llvm_context == NULL)
869+
{
870+
llvm_context = LLVMContextCreate();
871+
872+
llvm_jit_context_in_use_count = 0;
873+
llvm_llvm_context_reuse_count = 0;
874+
}
875+
790876
/*
791877
* When targeting an LLVM version with opaque pointers enabled by default,
792878
* turn them off for the context we build our code in. We don't need to
@@ -803,6 +889,11 @@ llvm_session_initialize(void)
803889
*/
804890
llvm_create_types();
805891

892+
/*
893+
* Extract target information from loaded module.
894+
*/
895+
llvm_set_target();
896+
806897
if (LLVMGetTargetFromTriple(llvm_triple, &llvm_targetref, &error) != 0)
807898
{
808899
elog(FATAL, "failed to query triple %s", error);
@@ -898,6 +989,10 @@ llvm_shutdown(int code, Datum arg)
898989
return;
899990
}
900991

992+
if (llvm_jit_context_in_use_count != 0)
993+
elog(PANIC, "LLVMJitContext in use count not 0 at exit (is %zu)",
994+
llvm_jit_context_in_use_count);
995+
901996
#if LLVM_VERSION_MAJOR > 11
902997
{
903998
if (llvm_opt3_orc)
@@ -968,6 +1063,23 @@ load_return_type(LLVMModuleRef mod, const char *name)
9681063
return typ;
9691064
}
9701065

1066+
/*
1067+
* Load triple & layout from clang emitted file so we're guaranteed to be
1068+
* compatible.
1069+
*/
1070+
static void
1071+
llvm_set_target(void)
1072+
{
1073+
if (!llvm_types_module)
1074+
elog(ERROR, "failed to extract target information, llvmjit_types.c not loaded");
1075+
1076+
if (llvm_triple == NULL)
1077+
llvm_triple = pstrdup(LLVMGetTarget(llvm_types_module));
1078+
1079+
if (llvm_layout == NULL)
1080+
llvm_layout = pstrdup(LLVMGetDataLayoutStr(llvm_types_module));
1081+
}
1082+
9711083
/*
9721084
* Load required information, types, function signatures from llvmjit_types.c
9731085
* and make them available in global variables.
@@ -991,19 +1103,12 @@ llvm_create_types(void)
9911103
}
9921104

9931105
/* eagerly load contents, going to need it all */
994-
if (LLVMParseBitcode2(buf, &llvm_types_module))
1106+
if (LLVMParseBitcodeInContext2(llvm_context, buf, &llvm_types_module))
9951107
{
996-
elog(ERROR, "LLVMParseBitcode2 of %s failed", path);
1108+
elog(ERROR, "LLVMParseBitcodeInContext2 of %s failed", path);
9971109
}
9981110
LLVMDisposeMemoryBuffer(buf);
9991111

1000-
/*
1001-
* Load triple & layout from clang emitted file so we're guaranteed to be
1002-
* compatible.
1003-
*/
1004-
llvm_triple = pstrdup(LLVMGetTarget(llvm_types_module));
1005-
llvm_layout = pstrdup(LLVMGetDataLayoutStr(llvm_types_module));
1006-
10071112
TypeSizeT = llvm_pg_var_type("TypeSizeT");
10081113
TypeParamBool = load_return_type(llvm_types_module, "FunctionReturningBool");
10091114
TypeStorageBool = llvm_pg_var_type("TypeStorageBool");

0 commit comments

Comments
 (0)