Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * llvmjit_inline.cpp
4 : : * Cross module inlining suitable for postgres' JIT
5 : : *
6 : : * The inliner iterates over external functions referenced from the passed
7 : : * module and attempts to inline those. It does so by utilizing pre-built
8 : : * indexes over both postgres core code and extension modules. When a match
9 : : * for an external function is found - not guaranteed! - the index will then
10 : : * be used to judge their instruction count / inline worthiness. After doing
11 : : * so for all external functions, all the referenced functions (and
12 : : * prerequisites) will be imported.
13 : : *
14 : : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
15 : : *
16 : : * IDENTIFICATION
17 : : * src/backend/lib/llvmjit/llvmjit_inline.cpp
18 : : *
19 : : *-------------------------------------------------------------------------
20 : : */
21 : :
22 : : extern "C"
23 : : {
24 : : #include "postgres.h"
25 : : }
26 : :
27 : : #include "jit/llvmjit.h"
28 : :
29 : : extern "C"
30 : : {
31 : : #include <fcntl.h>
32 : : #include <sys/mman.h>
33 : : #include <sys/stat.h>
34 : : #include <sys/types.h>
35 : : #include <unistd.h>
36 : :
37 : : #include "common/string.h"
38 : : #include "miscadmin.h"
39 : : #include "storage/fd.h"
40 : : }
41 : :
42 : : #include <llvm-c/Core.h>
43 : : #include <llvm-c/BitReader.h>
44 : :
45 : : /* Avoid macro clash with LLVM's C++ headers */
46 : : #undef Min
47 : :
48 : : #include <llvm/ADT/SetVector.h>
49 : : #include <llvm/ADT/StringSet.h>
50 : : #include <llvm/ADT/StringMap.h>
51 : : #include <llvm/Analysis/ModuleSummaryAnalysis.h>
52 : : #include <llvm/Bitcode/BitcodeReader.h>
53 : : #include <llvm/IR/Attributes.h>
54 : : #include <llvm/IR/DebugInfo.h>
55 : : #include <llvm/IR/IntrinsicInst.h>
56 : : #include <llvm/IR/IRBuilder.h>
57 : : #include <llvm/IR/ModuleSummaryIndex.h>
58 : : #include <llvm/Linker/IRMover.h>
59 : : #include <llvm/Support/ManagedStatic.h>
60 : : #include <llvm/Support/MemoryBuffer.h>
61 : :
62 : :
63 : : /*
64 : : * Type used to represent modules InlineWorkListItem's subject is searched for
65 : : * in.
66 : : */
67 : : typedef llvm::SmallVector<llvm::ModuleSummaryIndex *, 2> InlineSearchPath;
68 : :
69 : : /*
70 : : * Item in queue of to-be-checked symbols and corresponding queue.
71 : : */
72 : : typedef struct InlineWorkListItem
73 : : {
74 : : llvm::StringRef symbolName;
75 : : llvm::SmallVector<llvm::ModuleSummaryIndex *, 2> searchpath;
76 : : } InlineWorkListItem;
77 : : typedef llvm::SmallVector<InlineWorkListItem, 128> InlineWorkList;
78 : :
79 : : /*
80 : : * Information about symbols processed during inlining. Used to prevent
81 : : * repeated searches and provide additional information.
82 : : */
83 : : typedef struct FunctionInlineState
84 : : {
85 : : int costLimit;
86 : : bool processed;
87 : : bool inlined;
88 : : bool allowReconsidering;
89 : : } FunctionInlineState;
90 : : typedef llvm::StringMap<FunctionInlineState> FunctionInlineStates;
91 : :
92 : : /*
93 : : * Map of modules that should be inlined, with a list of the to-be inlined
94 : : * symbols.
95 : : */
96 : : typedef llvm::StringMap<llvm::StringSet<> > ImportMapTy;
97 : :
98 : :
99 : : const float inline_cost_decay_factor = 0.5;
100 : : const int inline_initial_cost = 150;
101 : :
102 : : /*
103 : : * These are managed statics so LLVM knows to deallocate them during an
104 : : * LLVMShutdown(), rather than after (which'd cause crashes).
105 : : */
106 : : typedef llvm::StringMap<std::unique_ptr<llvm::Module> > ModuleCache;
107 : : llvm::ManagedStatic<ModuleCache> module_cache;
108 : : typedef llvm::StringMap<std::unique_ptr<llvm::ModuleSummaryIndex> > SummaryCache;
109 : : llvm::ManagedStatic<SummaryCache> summary_cache;
110 : :
111 : :
112 : : static std::unique_ptr<ImportMapTy> llvm_build_inline_plan(LLVMContextRef lc, llvm::Module *mod);
113 : : static void llvm_execute_inline_plan(llvm::Module *mod,
114 : : ImportMapTy *globalsToInline);
115 : :
116 : : static llvm::Module* load_module_cached(LLVMContextRef c, llvm::StringRef modPath);
117 : : static std::unique_ptr<llvm::Module> load_module(LLVMContextRef c, llvm::StringRef Identifier);
118 : : static std::unique_ptr<llvm::ModuleSummaryIndex> llvm_load_summary(llvm::StringRef path);
119 : :
120 : :
121 : : static llvm::Function* create_redirection_function(std::unique_ptr<llvm::Module> &importMod,
122 : : llvm::Function *F,
123 : : llvm::StringRef Name);
124 : :
125 : : static bool function_inlinable(llvm::Function &F,
126 : : int threshold,
127 : : FunctionInlineStates &functionState,
128 : : InlineWorkList &worklist,
129 : : InlineSearchPath &searchpath,
130 : : llvm::SmallPtrSet<const llvm::Function *, 8> &visitedFunctions,
131 : : int &running_instcount,
132 : : llvm::StringSet<> &importVars);
133 : : static void function_references(llvm::Function &F,
134 : : int &running_instcount,
135 : : llvm::SmallPtrSet<llvm::GlobalVariable *, 8> &referencedVars,
136 : : llvm::SmallPtrSet<llvm::Function *, 8> &referencedFunctions);
137 : :
138 : : static void add_module_to_inline_search_path(InlineSearchPath& path, llvm::StringRef modpath);
139 : : static llvm::SmallVector<llvm::GlobalValueSummary *, 1>
140 : : summaries_for_guid(const InlineSearchPath& path, llvm::GlobalValue::GUID guid);
141 : :
142 : : /* verbose debugging for inliner development */
143 : : /* #define INLINE_DEBUG */
144 : : #ifdef INLINE_DEBUG
145 : : #define ilog elog
146 : : #else
147 : : #define ilog(...) (void) 0
148 : : #endif
149 : :
150 : : /*
151 : : * Reset inlining related state. This needs to be called before the currently
152 : : * used LLVMContextRef is disposed (and a new one create), otherwise we would
153 : : * have dangling references to deleted modules.
154 : : */
155 : : void
156 : 0 : llvm_inline_reset_caches(void)
157 : : {
158 : 0 : module_cache->clear();
159 : 0 : summary_cache->clear();
160 : 0 : }
161 : :
162 : : /*
163 : : * Perform inlining of external function references in M based on a simple
164 : : * cost based analysis.
165 : : */
166 : : void
167 : 0 : llvm_inline(LLVMModuleRef M)
168 : : {
169 [ # # ]: 0 : LLVMContextRef lc = LLVMGetModuleContext(M);
170 : 0 : llvm::Module *mod = llvm::unwrap(M);
171 : :
172 [ # # ]: 0 : std::unique_ptr<ImportMapTy> globalsToInline = llvm_build_inline_plan(lc, mod);
173 [ # # ]: 0 : if (!globalsToInline)
174 : 0 : return;
175 [ # # ]: 0 : llvm_execute_inline_plan(mod, globalsToInline.get());
176 [ # # ]: 0 : }
177 : :
178 : : /*
179 : : * Build information necessary for inlining external function references in
180 : : * mod.
181 : : */
182 : : static std::unique_ptr<ImportMapTy>
183 : 0 : llvm_build_inline_plan(LLVMContextRef lc, llvm::Module *mod)
184 : : {
185 [ # # # # ]: 0 : std::unique_ptr<ImportMapTy> globalsToInline(new ImportMapTy());
186 : 0 : FunctionInlineStates functionStates;
187 [ # # ]: 0 : InlineWorkList worklist;
188 : :
189 [ # # ]: 0 : InlineSearchPath defaultSearchPath;
190 : :
191 : : /* attempt to add module to search path */
192 [ # # # # ]: 0 : add_module_to_inline_search_path(defaultSearchPath, "$libdir/postgres");
193 : : /* if postgres isn't available, no point continuing */
194 [ # # ]: 0 : if (defaultSearchPath.empty())
195 : 0 : return nullptr;
196 : :
197 : : /*
198 : : * Start inlining with current references to external functions by putting
199 : : * them on the inlining worklist. If, during inlining of those, new extern
200 : : * functions need to be inlined, they'll also be put there, with a lower
201 : : * priority.
202 : : */
203 [ # # # # : 0 : for (const llvm::Function &funcDecl : mod->functions())
# # # # ]
204 : : {
205 [ # # ]: 0 : InlineWorkListItem item = {};
206 : 0 : FunctionInlineState inlineState = {};
207 : :
208 : : /* already has a definition */
209 [ # # # # ]: 0 : if (!funcDecl.isDeclaration())
210 : 0 : continue;
211 : :
212 : : /* llvm provides implementation */
213 [ # # ]: 0 : if (funcDecl.isIntrinsic())
214 : 0 : continue;
215 : :
216 [ # # ]: 0 : item.symbolName = funcDecl.getName();
217 [ # # ]: 0 : item.searchpath = defaultSearchPath;
218 [ # # ]: 0 : worklist.push_back(item);
219 : 0 : inlineState.costLimit = inline_initial_cost;
220 : 0 : inlineState.processed = false;
221 : 0 : inlineState.inlined = false;
222 : 0 : inlineState.allowReconsidering = false;
223 [ # # # # ]: 0 : functionStates[funcDecl.getName()] = inlineState;
224 [ # # ]: 0 : }
225 : :
226 : : /*
227 : : * Iterate over pending worklist items, look them up in index, check
228 : : * whether they should be inlined.
229 : : */
230 [ # # ]: 0 : while (!worklist.empty())
231 : : {
232 [ # # ]: 0 : InlineWorkListItem item = worklist.pop_back_val();
233 : 0 : llvm::StringRef symbolName = item.symbolName;
234 : : char *cmodname;
235 : : char *cfuncname;
236 [ # # ]: 0 : FunctionInlineState &inlineState = functionStates[symbolName];
237 : : llvm::GlobalValue::GUID funcGUID;
238 : :
239 [ # # ]: 0 : llvm_split_symbol_name(symbolName.data(), &cmodname, &cfuncname);
240 : :
241 : : #if LLVM_VERSION_MAJOR >= 21
242 : : funcGUID = llvm::GlobalValue::getGUIDAssumingExternalLinkage(cfuncname);
243 : : #else
244 [ # # # # ]: 0 : funcGUID = llvm::GlobalValue::getGUID(cfuncname);
245 : : #endif
246 : :
247 : : /* already processed */
248 [ # # ]: 0 : if (inlineState.processed)
249 : 0 : continue;
250 : :
251 : :
252 [ # # ]: 0 : if (cmodname)
253 [ # # # # ]: 0 : add_module_to_inline_search_path(item.searchpath, cmodname);
254 : :
255 : : /*
256 : : * Iterate over all known definitions of function, via the index. Then
257 : : * look up module(s), check if function actually is defined (there
258 : : * could be hash conflicts).
259 : : */
260 [ # # # # ]: 0 : for (const auto &gvs : summaries_for_guid(item.searchpath, funcGUID))
261 : : {
262 : : const llvm::FunctionSummary *fs;
263 : 0 : llvm::StringRef modPath = gvs->modulePath();
264 : : llvm::Module *defMod;
265 : : llvm::Function *funcDef;
266 : :
267 [ # # ]: 0 : fs = llvm::cast<llvm::FunctionSummary>(gvs);
268 : :
269 [ # # ]: 0 : if (gvs->notEligibleToImport())
270 : : {
271 : : ilog(DEBUG1, "ineligibile to import %s due to summary",
272 : : symbolName.data());
273 : 0 : continue;
274 : : }
275 : :
276 [ # # ]: 0 : if ((int) fs->instCount() > inlineState.costLimit)
277 : : {
278 : : ilog(DEBUG1, "ineligibile to import %s due to early threshold: %u vs %u",
279 : : symbolName.data(), fs->instCount(), inlineState.costLimit);
280 : 0 : inlineState.allowReconsidering = true;
281 : 0 : continue;
282 : : }
283 : :
284 [ # # ]: 0 : defMod = load_module_cached(lc, modPath);
285 [ # # # # : 0 : if (defMod->materializeMetadata())
# # ]
286 [ # # # # : 0 : elog(FATAL, "failed to materialize metadata");
# # # # ]
287 : :
288 [ # # # # ]: 0 : funcDef = defMod->getFunction(cfuncname);
289 : :
290 : : /*
291 : : * This can happen e.g. in case of a hash collision of the
292 : : * function's name.
293 : : */
294 [ # # ]: 0 : if (!funcDef)
295 : 0 : continue;
296 : :
297 [ # # # # : 0 : if (funcDef->materialize())
# # ]
298 [ # # # # : 0 : elog(FATAL, "failed to materialize metadata");
# # # # ]
299 : :
300 : : Assert(!funcDef->isDeclaration());
301 : : Assert(funcDef->hasExternalLinkage());
302 : :
303 [ # # ]: 0 : llvm::StringSet<> importVars;
304 : 0 : llvm::SmallPtrSet<const llvm::Function *, 8> visitedFunctions;
305 : 0 : int running_instcount = 0;
306 : :
307 : : /*
308 : : * Check whether function, and objects it depends on, are
309 : : * inlinable.
310 : : */
311 [ # # # # ]: 0 : if (function_inlinable(*funcDef,
312 : : inlineState.costLimit,
313 : : functionStates,
314 : : worklist,
315 : : item.searchpath,
316 : : visitedFunctions,
317 : : running_instcount,
318 : : importVars))
319 : : {
320 : : /*
321 : : * Check whether function and all its dependencies are too
322 : : * big. Dependencies already counted for other functions that
323 : : * will get inlined are not counted again. While this make
324 : : * things somewhat order dependent, I can't quite see a point
325 : : * in a different behaviour.
326 : : */
327 [ # # ]: 0 : if (running_instcount > inlineState.costLimit)
328 : : {
329 : : ilog(DEBUG1, "skipping inlining of %s due to late threshold %d vs %d",
330 : : symbolName.data(), running_instcount, inlineState.costLimit);
331 : 0 : inlineState.allowReconsidering = true;
332 : 0 : continue;
333 : : }
334 : :
335 : : ilog(DEBUG1, "inline top function %s total_instcount: %d, partial: %d",
336 : : symbolName.data(), running_instcount, fs->instCount());
337 : :
338 : : /* import referenced function itself */
339 [ # # ]: 0 : importVars.insert(symbolName);
340 : :
341 : : {
342 [ # # ]: 0 : llvm::StringSet<> &modGlobalsToInline = (*globalsToInline)[modPath];
343 [ # # # # : 0 : for (auto& importVar : importVars)
# # # # #
# ]
344 [ # # # # ]: 0 : modGlobalsToInline.insert(importVar.first());
345 : : Assert(modGlobalsToInline.size() > 0);
346 : : }
347 : :
348 : : /* mark function as inlined */
349 : 0 : inlineState.inlined = true;
350 : :
351 : : /*
352 : : * Found definition to inline, don't look for further
353 : : * potential definitions.
354 : : */
355 : 0 : break;
356 : : }
357 : : else
358 : : {
359 : : ilog(DEBUG1, "had to skip inlining %s",
360 : : symbolName.data());
361 : :
362 : : /* It's possible there's another definition that's inlinable. */
363 : : }
364 [ # # # # : 0 : }
# # ]
365 : :
366 : : /*
367 : : * Signal that we're done with symbol, whether successful (inlined =
368 : : * true above) or not.
369 : : */
370 : 0 : inlineState.processed = true;
371 [ # # ]: 0 : }
372 : :
373 : 0 : return globalsToInline;
374 : 0 : }
375 : :
376 : : /*
377 : : * Perform the actual inlining of external functions (and their dependencies)
378 : : * into mod.
379 : : */
380 : : static void
381 : 0 : llvm_execute_inline_plan(llvm::Module *mod, ImportMapTy *globalsToInline)
382 : : {
383 [ # # ]: 0 : llvm::IRMover Mover(*mod);
384 : :
385 [ # # # # : 0 : for (const auto& toInline : *globalsToInline)
# # # # #
# ]
386 : : {
387 [ # # ]: 0 : const llvm::StringRef& modPath = toInline.first();
388 : 0 : const llvm::StringSet<>& modGlobalsToInline = toInline.second;
389 [ # # ]: 0 : llvm::SetVector<llvm::GlobalValue *> GlobalsToImport;
390 : :
391 : : Assert(module_cache->count(modPath));
392 [ # # # # ]: 0 : std::unique_ptr<llvm::Module> importMod(std::move((*module_cache)[modPath]));
393 [ # # # # ]: 0 : module_cache->erase(modPath);
394 : :
395 [ # # ]: 0 : if (modGlobalsToInline.empty())
396 : 0 : continue;
397 : :
398 [ # # # # : 0 : for (auto &glob: modGlobalsToInline)
# # # # #
# ]
399 : : {
400 [ # # ]: 0 : llvm::StringRef SymbolName = glob.first();
401 : : char *modname;
402 : : char *funcname;
403 : :
404 [ # # ]: 0 : llvm_split_symbol_name(SymbolName.data(), &modname, &funcname);
405 : :
406 [ # # # # ]: 0 : llvm::GlobalValue *valueToImport = importMod->getNamedValue(funcname);
407 : :
408 [ # # ]: 0 : if (!valueToImport)
409 [ # # # # : 0 : elog(FATAL, "didn't refind value %s to import", SymbolName.data());
# # # # ]
410 : :
411 : : /*
412 : : * For functions (global vars are only inlined if already static),
413 : : * mark imported variables as being clones from other
414 : : * functions. That a) avoids symbol conflicts b) allows the
415 : : * optimizer to perform inlining.
416 : : */
417 [ # # # # ]: 0 : if (llvm::isa<llvm::Function>(valueToImport))
418 : : {
419 [ # # ]: 0 : llvm::Function *F = llvm::dyn_cast<llvm::Function>(valueToImport);
420 : : typedef llvm::GlobalValue::LinkageTypes LinkageTypes;
421 : :
422 : : /*
423 : : * Per-function info isn't necessarily stripped yet, as the
424 : : * module is lazy-loaded when stripped above.
425 : : */
426 [ # # ]: 0 : llvm::stripDebugInfo(*F);
427 : :
428 : : /*
429 : : * If the to-be-imported function is one referenced including
430 : : * its module name, create a tiny inline function that just
431 : : * forwards the call. One might think a GlobalAlias would do
432 : : * the trick, but a) IRMover doesn't override a declaration
433 : : * with an alias pointing to a definition (instead renaming
434 : : * it), b) Aliases can't be AvailableExternally.
435 : : */
436 [ # # ]: 0 : if (modname)
437 : : {
438 : : llvm::Function *AF;
439 : :
440 [ # # ]: 0 : AF = create_redirection_function(importMod, F, SymbolName);
441 : :
442 [ # # ]: 0 : GlobalsToImport.insert(AF);
443 [ # # ]: 0 : llvm::stripDebugInfo(*AF);
444 : : }
445 : :
446 [ # # # # ]: 0 : if (valueToImport->hasExternalLinkage())
447 : : {
448 [ # # ]: 0 : valueToImport->setLinkage(LinkageTypes::AvailableExternallyLinkage);
449 : : }
450 : : }
451 : :
452 [ # # ]: 0 : GlobalsToImport.insert(valueToImport);
453 : : ilog(DEBUG1, "performing import of %s %s",
454 : : modPath.data(), SymbolName.data());
455 : :
456 : : }
457 : :
458 [ # # # # : 0 : if (Mover.move(std::move(importMod), GlobalsToImport.getArrayRef(),
# # # # #
# ]
459 : 0 : [](llvm::GlobalValue &, llvm::IRMover::ValueAdder) {},
460 : : /*IsPerformingImport=*/false))
461 [ # # # # : 0 : elog(FATAL, "function import failed with linker error");
# # # # ]
462 [ # # # # ]: 0 : }
463 : 0 : }
464 : :
465 : : /*
466 : : * Return a module identified by modPath, caching it in memory.
467 : : *
468 : : * Note that such a module may *not* be modified without copying, otherwise
469 : : * the cache state would get corrupted.
470 : : */
471 : : static llvm::Module*
472 : 0 : load_module_cached(LLVMContextRef lc, llvm::StringRef modPath)
473 : : {
474 [ # # # # ]: 0 : auto it = module_cache->find(modPath);
475 [ # # # # : 0 : if (it == module_cache->end())
# # ]
476 : : {
477 [ # # # # ]: 0 : it = module_cache->insert(
478 [ # # # # ]: 0 : std::make_pair(modPath, load_module(lc, modPath))).first;
479 : : }
480 : :
481 [ # # ]: 0 : return it->second.get();
482 : : }
483 : :
484 : : static std::unique_ptr<llvm::Module>
485 : 0 : load_module(LLVMContextRef lc, llvm::StringRef Identifier)
486 : : {
487 : : LLVMMemoryBufferRef buf;
488 : : LLVMModuleRef mod;
489 : : char path[MAXPGPATH];
490 : : char *msg;
491 : :
492 : 0 : snprintf(path, MAXPGPATH,"%s/bitcode/%s", pkglib_path, Identifier.data());
493 : :
494 [ # # # # ]: 0 : if (LLVMCreateMemoryBufferWithContentsOfFile(path, &buf, &msg))
495 [ # # # # : 0 : elog(FATAL, "failed to open bitcode file \"%s\": %s",
# # # # ]
496 : : path, msg);
497 [ # # # # ]: 0 : if (LLVMGetBitcodeModuleInContext2(lc, buf, &mod))
498 [ # # # # : 0 : elog(FATAL, "failed to parse bitcode in file \"%s\"", path);
# # # # ]
499 : :
500 : : /*
501 : : * Currently there's no use in more detailed debug info for JITed
502 : : * code. Until that changes, not much point in wasting memory and cycles
503 : : * on processing debuginfo.
504 : : */
505 [ # # ]: 0 : llvm::StripDebugInfo(*llvm::unwrap(mod));
506 : :
507 : 0 : return std::unique_ptr<llvm::Module>(llvm::unwrap(mod));
508 : : }
509 : :
510 : : /*
511 : : * Compute list of referenced variables, functions and the instruction count
512 : : * for a function.
513 : : */
514 : : static void
515 : 0 : function_references(llvm::Function &F,
516 : : int &running_instcount,
517 : : llvm::SmallPtrSet<llvm::GlobalVariable *, 8> &referencedVars,
518 : : llvm::SmallPtrSet<llvm::Function *, 8> &referencedFunctions)
519 : : {
520 : 0 : llvm::SmallPtrSet<const llvm::User *, 32> Visited;
521 : :
522 [ # # # # : 0 : for (llvm::BasicBlock &BB : F)
# # # # #
# ]
523 : : {
524 [ # # # # : 0 : for (llvm::Instruction &I : BB)
# # # # #
# ]
525 : : {
526 [ # # # # ]: 0 : if (llvm::isa<llvm::DbgInfoIntrinsic>(I))
527 : 0 : continue;
528 : :
529 [ # # ]: 0 : llvm::SmallVector<llvm::User *, 8> Worklist;
530 [ # # ]: 0 : Worklist.push_back(&I);
531 : :
532 : 0 : running_instcount++;
533 : :
534 [ # # ]: 0 : while (!Worklist.empty()) {
535 [ # # ]: 0 : llvm::User *U = Worklist.pop_back_val();
536 : :
537 : : /* visited before */
538 [ # # # # ]: 0 : if (!Visited.insert(U).second)
539 : 0 : continue;
540 : :
541 [ # # # # ]: 0 : for (auto &OI : U->operands()) {
542 [ # # ]: 0 : llvm::User *Operand = llvm::dyn_cast<llvm::User>(OI);
543 [ # # ]: 0 : if (!Operand)
544 : 0 : continue;
545 [ # # # # ]: 0 : if (llvm::isa<llvm::BlockAddress>(Operand))
546 : 0 : continue;
547 [ # # # # ]: 0 : if (auto *GV = llvm::dyn_cast<llvm::GlobalVariable>(Operand)) {
548 [ # # ]: 0 : referencedVars.insert(GV);
549 [ # # # # ]: 0 : if (GV->hasInitializer())
550 [ # # # # ]: 0 : Worklist.push_back(GV->getInitializer());
551 : 0 : continue;
552 : : }
553 [ # # # # ]: 0 : if (auto *CF = llvm::dyn_cast<llvm::Function>(Operand)) {
554 [ # # ]: 0 : referencedFunctions.insert(CF);
555 : 0 : continue;
556 : : }
557 [ # # ]: 0 : Worklist.push_back(Operand);
558 : : }
559 : : }
560 : 0 : }
561 : : }
562 : 0 : }
563 : :
564 : : /*
565 : : * Check whether function F is inlinable and, if so, what globals need to be
566 : : * imported.
567 : : *
568 : : * References to external functions from, potentially recursively, inlined
569 : : * functions are added to the passed in worklist.
570 : : */
571 : : static bool
572 : 0 : function_inlinable(llvm::Function &F,
573 : : int threshold,
574 : : FunctionInlineStates &functionStates,
575 : : InlineWorkList &worklist,
576 : : InlineSearchPath &searchpath,
577 : : llvm::SmallPtrSet<const llvm::Function *, 8> &visitedFunctions,
578 : : int &running_instcount,
579 : : llvm::StringSet<> &importVars)
580 : : {
581 : 0 : int subThreshold = threshold * inline_cost_decay_factor;
582 : 0 : llvm::SmallPtrSet<llvm::GlobalVariable *, 8> referencedVars;
583 : 0 : llvm::SmallPtrSet<llvm::Function *, 8> referencedFunctions;
584 : :
585 : : /* can't rely on what may be inlined */
586 [ # # # # ]: 0 : if (F.isInterposable())
587 : 0 : return false;
588 : :
589 : : /*
590 : : * Can't rely on function being present. Alternatively we could create a
591 : : * static version of these functions?
592 : : */
593 [ # # # # ]: 0 : if (F.hasAvailableExternallyLinkage())
594 : 0 : return false;
595 : :
596 : : ilog(DEBUG1, "checking inlinability of %s", F.getName().data());
597 : :
598 [ # # # # : 0 : if (F.materialize())
# # ]
599 [ # # # # : 0 : elog(FATAL, "failed to materialize metadata");
# # # # ]
600 : :
601 [ # # # # ]: 0 : if (F.getAttributes().hasFnAttr(llvm::Attribute::NoInline))
602 : : {
603 : : ilog(DEBUG1, "ineligibile to import %s due to noinline",
604 : : F.getName().data());
605 : 0 : return false;
606 : : }
607 : :
608 [ # # ]: 0 : function_references(F, running_instcount, referencedVars, referencedFunctions);
609 : :
610 [ # # # # : 0 : for (llvm::GlobalVariable* rv: referencedVars)
# # # # ]
611 : : {
612 [ # # # # : 0 : if (rv->materialize())
# # ]
613 [ # # # # : 0 : elog(FATAL, "failed to materialize metadata");
# # # # ]
614 : :
615 : : /*
616 : : * Don't inline functions that access thread local variables. That
617 : : * doesn't work on current LLVM releases (but might in future).
618 : : */
619 [ # # # # ]: 0 : if (rv->isThreadLocal())
620 : : {
621 : : ilog(DEBUG1, "cannot inline %s due to thread-local variable %s",
622 : : F.getName().data(), rv->getName().data());
623 : 0 : return false;
624 : : }
625 : :
626 : : /*
627 : : * Never want to inline externally visible vars, cheap enough to
628 : : * reference.
629 : : */
630 [ # # # # : 0 : if (rv->hasExternalLinkage() || rv->hasAvailableExternallyLinkage())
# # # # #
# ]
631 : 0 : continue;
632 : :
633 : : /*
634 : : * If variable is file-local, we need to inline it, to be able to
635 : : * inline the function itself. Can't do that if the variable can be
636 : : * modified, because they'd obviously get out of sync.
637 : : *
638 : : * XXX: Currently not a problem, but there'd be problems with
639 : : * nontrivial initializers if they were allowed for postgres.
640 : : */
641 [ # # ]: 0 : if (!rv->isConstant())
642 : : {
643 : : ilog(DEBUG1, "cannot inline %s due to uncloneable variable %s",
644 : : F.getName().data(), rv->getName().data());
645 : 0 : return false;
646 : : }
647 : :
648 : : ilog(DEBUG1, "memorizing global var %s linkage %d for inlining",
649 : : rv->getName().data(), (int)rv->getLinkage());
650 : :
651 [ # # # # ]: 0 : importVars.insert(rv->getName());
652 : : /* small cost attributed to each cloned global */
653 : 0 : running_instcount += 5;
654 : : }
655 : :
656 [ # # ]: 0 : visitedFunctions.insert(&F);
657 : :
658 : : /*
659 : : * Check referenced functions. Check whether used static ones are
660 : : * inlinable, and remember external ones for inlining.
661 : : */
662 [ # # # # : 0 : for (llvm::Function* referencedFunction: referencedFunctions)
# # # # ]
663 : : {
664 [ # # ]: 0 : llvm::StringSet<> recImportVars;
665 : :
666 [ # # # # : 0 : if (referencedFunction->materialize())
# # ]
667 [ # # # # : 0 : elog(FATAL, "failed to materialize metadata");
# # # # ]
668 : :
669 [ # # ]: 0 : if (referencedFunction->isIntrinsic())
670 : 0 : continue;
671 : :
672 : : /* if already visited skip, otherwise remember */
673 [ # # # # ]: 0 : if (!visitedFunctions.insert(referencedFunction).second)
674 : 0 : continue;
675 : :
676 : : /*
677 : : * We don't inline external functions directly here, instead we put
678 : : * them on the worklist if appropriate and check them from
679 : : * llvm_build_inline_plan().
680 : : */
681 [ # # # # ]: 0 : if (referencedFunction->hasExternalLinkage())
682 : : {
683 [ # # ]: 0 : llvm::StringRef funcName = referencedFunction->getName();
684 : :
685 : : /*
686 : : * Don't bother checking for inlining if remaining cost budget is
687 : : * very small.
688 : : */
689 [ # # ]: 0 : if (subThreshold < 5)
690 : 0 : continue;
691 : :
692 [ # # ]: 0 : auto it = functionStates.find(funcName);
693 [ # # # # ]: 0 : if (it == functionStates.end())
694 : : {
695 : : FunctionInlineState inlineState;
696 : :
697 : 0 : inlineState.costLimit = subThreshold;
698 : 0 : inlineState.processed = false;
699 : 0 : inlineState.inlined = false;
700 : 0 : inlineState.allowReconsidering = false;
701 : :
702 [ # # ]: 0 : functionStates[funcName] = inlineState;
703 [ # # ]: 0 : worklist.push_back({funcName, searchpath});
704 : :
705 : : ilog(DEBUG1,
706 : : "considering extern function %s at %d for inlining",
707 : : funcName.data(), subThreshold);
708 : : }
709 [ # # ]: 0 : else if (!it->second.inlined &&
710 [ # # # # : 0 : (!it->second.processed || it->second.allowReconsidering) &&
# # # # #
# ]
711 [ # # # # : 0 : it->second.costLimit < subThreshold)
# # ]
712 : : {
713 : : /*
714 : : * Update inlining threshold if higher. Need to re-queue
715 : : * to be processed if already processed with lower
716 : : * threshold.
717 : : */
718 [ # # # # ]: 0 : if (it->second.processed)
719 : : {
720 : : ilog(DEBUG1,
721 : : "reconsidering extern function %s at %d for inlining, increasing from %d",
722 : : funcName.data(), subThreshold, it->second.costLimit);
723 : :
724 [ # # ]: 0 : it->second.processed = false;
725 [ # # ]: 0 : it->second.allowReconsidering = false;
726 [ # # ]: 0 : worklist.push_back({funcName, searchpath});
727 : : }
728 [ # # ]: 0 : it->second.costLimit = subThreshold;
729 : : }
730 : 0 : continue;
731 : 0 : }
732 : :
733 : : /* can't rely on what may be inlined */
734 [ # # # # ]: 0 : if (referencedFunction->isInterposable())
735 : 0 : return false;
736 : :
737 [ # # # # ]: 0 : if (!function_inlinable(*referencedFunction,
738 : : subThreshold,
739 : : functionStates,
740 : : worklist,
741 : : searchpath,
742 : : visitedFunctions,
743 : : running_instcount,
744 : : recImportVars))
745 : : {
746 : : ilog(DEBUG1,
747 : : "cannot inline %s due to required function %s not being inlinable",
748 : : F.getName().data(), referencedFunction->getName().data());
749 : 0 : return false;
750 : : }
751 : :
752 : : /* import referenced function itself */
753 [ # # # # ]: 0 : importVars.insert(referencedFunction->getName());
754 : :
755 : : /* import referenced function and its dependents */
756 [ # # # # : 0 : for (auto& recImportVar : recImportVars)
# # # # #
# ]
757 [ # # # # ]: 0 : importVars.insert(recImportVar.first());
758 [ # # # ]: 0 : }
759 : :
760 : 0 : return true;
761 [ # # # # ]: 0 : }
762 : :
763 : : /*
764 : : * Attempt to load module summary located at path. Return empty pointer when
765 : : * loading fails.
766 : : */
767 : : static std::unique_ptr<llvm::ModuleSummaryIndex>
768 : 0 : llvm_load_summary(llvm::StringRef path)
769 : : {
770 : : llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer> > MBOrErr =
771 [ # # # # ]: 0 : llvm::MemoryBuffer::getFile(path);
772 : :
773 [ # # # # ]: 0 : if (std::error_code EC = MBOrErr.getError())
774 : : {
775 : : ilog(DEBUG1, "failed to open %s: %s", path.data(),
776 : : EC.message().c_str());
777 : : }
778 : : else
779 : : {
780 [ # # # # ]: 0 : llvm::MemoryBufferRef ref(*MBOrErr.get().get());
781 : :
782 : : llvm::Expected<std::unique_ptr<llvm::ModuleSummaryIndex> > IndexOrErr =
783 [ # # ]: 0 : llvm::getModuleSummaryIndex(ref);
784 [ # # ]: 0 : if (IndexOrErr)
785 [ # # ]: 0 : return std::move(IndexOrErr.get());
786 [ # # # # : 0 : elog(FATAL, "failed to load summary \"%s\": %s",
# # # # #
# # # ]
787 : : path.data(),
788 : : toString(IndexOrErr.takeError()).c_str());
789 : 0 : }
790 : 0 : return nullptr;
791 : 0 : }
792 : :
793 : : /*
794 : : * Attempt to add modpath to the search path.
795 : : */
796 : : static void
797 : 0 : add_module_to_inline_search_path(InlineSearchPath& searchpath, llvm::StringRef modpath)
798 : : {
799 : : /* only extension in libdir are candidates for inlining for now */
800 : : #if LLVM_VERSION_MAJOR < 16
801 : : #define starts_with startswith
802 : : #endif
803 [ # # # # ]: 0 : if (!modpath.starts_with("$libdir/"))
804 : 0 : return;
805 : :
806 : : /* if there's no match, attempt to load */
807 [ # # # # ]: 0 : auto it = summary_cache->find(modpath);
808 [ # # # # : 0 : if (it == summary_cache->end())
# # ]
809 : : {
810 [ # # ]: 0 : std::string path(modpath);
811 [ # # # # : 0 : path = path.replace(0, strlen("$libdir"), std::string(pkglib_path) + "/bitcode");
# # # # ]
812 [ # # ]: 0 : path += ".index.bc";
813 [ # # # # : 0 : (*summary_cache)[modpath] = llvm_load_summary(path);
# # ]
814 [ # # # # ]: 0 : it = summary_cache->find(modpath);
815 : 0 : }
816 : :
817 : : Assert(it != summary_cache->end());
818 : :
819 : : /* if the entry isn't NULL, it's validly loaded */
820 [ # # # # ]: 0 : if (it->second)
821 [ # # # # ]: 0 : searchpath.push_back(it->second.get());
822 : : }
823 : :
824 : : /*
825 : : * Search for all references for functions hashing to guid in the search path,
826 : : * and return them in search path order.
827 : : */
828 : : static llvm::SmallVector<llvm::GlobalValueSummary *, 1>
829 : 0 : summaries_for_guid(const InlineSearchPath& path, llvm::GlobalValue::GUID guid)
830 : : {
831 : 0 : llvm::SmallVector<llvm::GlobalValueSummary *, 1> matches;
832 : :
833 [ # # ]: 0 : for (auto index : path)
834 : : {
835 [ # # ]: 0 : llvm::ValueInfo funcVI = index->getValueInfo(guid);
836 : :
837 : : /* if index doesn't know function, we don't have a body, continue */
838 [ # # # # ]: 0 : if (funcVI)
839 [ # # # # ]: 0 : for (auto &gv : funcVI.getSummaryList())
840 [ # # ]: 0 : matches.push_back(gv.get());
841 : : }
842 : :
843 : 0 : return matches;
844 : 0 : }
845 : :
846 : : /*
847 : : * Create inline wrapper with the name Name, redirecting the call to F.
848 : : */
849 : : static llvm::Function*
850 : 0 : create_redirection_function(std::unique_ptr<llvm::Module> &importMod,
851 : : llvm::Function *F,
852 : : llvm::StringRef Name)
853 : : {
854 : : typedef llvm::GlobalValue::LinkageTypes LinkageTypes;
855 : :
856 [ # # ]: 0 : llvm::LLVMContext &Context = F->getContext();
857 [ # # ]: 0 : llvm::IRBuilder<> Builder(Context);
858 : : llvm::Function *AF;
859 : : llvm::BasicBlock *BB;
860 : : llvm::CallInst *fwdcall;
861 : :
862 [ # # # # : 0 : AF = llvm::Function::Create(F->getFunctionType(),
# # ]
863 : : LinkageTypes::AvailableExternallyLinkage,
864 : : Name, importMod.get());
865 [ # # # # ]: 0 : BB = llvm::BasicBlock::Create(Context, "entry", AF);
866 : :
867 [ # # ]: 0 : Builder.SetInsertPoint(BB);
868 [ # # # # : 0 : fwdcall = Builder.CreateCall(F, &*AF->arg_begin());
# # # # ]
869 [ # # ]: 0 : fwdcall->addFnAttr(llvm::Attribute::AlwaysInline);
870 [ # # ]: 0 : Builder.CreateRet(fwdcall);
871 : :
872 : 0 : return AF;
873 : 0 : }
|