Link a pre-compiled bitcodes with my jit code, enabling IPO (original) (raw)
August 14, 2025, 9:42am 1
Hi everyone,
I’m implementing a JIT engine using LLVM’s ORCv2 framework. Currently, my engine generates LLVM IR from user-supplied scripts, as shown in this simplified example:
declare ptr @CreateWrappedValueFromInt(i32) local_unnamed_addr
define noundef i32 @f_() local_unnamed_addr #0 {
ret i32 23333
}
define ptr @f_WRAPPED(ptr nocapture readnone %0, ptr nocapture readnone %1) local_unnamed_addr {
%3 = tail call ptr @CreateWrappedValueFromInt(i32 23333)
ret ptr %3
}
Here, f_ is the user’s translated function, while f_WRAPPED is a generated wrapper that adapts the result for the interpreter (e.g., passing f_’s output to CreateWrappedValueFromInt). The interpreter invokes f_WRAPPED directly.
I need Inter-Procedural Optimization (IPO) like inlining for helper functions (e.g., CreateWrappedValueFromInt). These functions are implemented in C++ and rely heavily on host-program definitions, making IRBuilder-based generation impractical for maintenance.
Current Approach
I precompile these helper functions via clang -emit-llvm, then merge them into a single bitcode file using llvm-link. At runtime:
- Load this bitcode into an
llvm::Module. - Link it with my module created from AST.
- Apply optimizations (Just use -O3)
- Add the final module to ExecutionSession, lookup symbol (e.g. f_WRAPPED)
Problem: Module Linking
I’m struggling to link modules correctly without triggering MaterializationResponsibility assertions/errors. What I’ve tried:
llvm::Linker::linkModules:- If used in
IRTransformLayer, the final module contains extra definitions conflicting withMaterializationResponsibility. - If linked before added to ExecutionSession, some symbols vanish after optimizations, again breaking
MaterializationResponsibility. - Question: Can/should
MaterializationResponsibilitybe modified to resolve this?(Or add an extra layer to create theMaterializationResponsibilitymanually) - After I pass a InternalizeCallback to
linkModules(), the final module works. Is it a correct way? My bitcodes contains some global object used in helper functions, it means that should I only import function definitions from the bitcode module? - I discovered
llvm::FunctionImporterin llvm source. But it was used in llvm LTO only. Could I use it standalone?
- If used in
llvm::LTO:- This appears tailored for static linking scenarios. Is it viable (or overkill) for JIT?
Additional Concern
My bitcode may contain global initializers. Does this imply the bitcode module must also be jit-ed and managed by ORC? (Contains the global variables, other module will import function definitions only)
I’d greatly appreciate insights into best practices for linking external bitcode with ORCv2 JIT modules while preserving IPO and avoiding materialization issues.
Thanks in advance!
j20w August 14, 2025, 3:58pm 2
I found some posts similar to this:
Almost did the same thing as I did!
However, this post did not mention the issue of global objects. If the bitcode module has been link to multiple modules and all things have been internalized, the global object may exists multiple instances?
This reminded me of these functions! I will try to use them. I found that LTO also uses several of the functions mentioned here to create pipelines.