Skip to content

Commit ff14eaf

Browse files
Reuse incremental JIT compilation for --image-codegen (#50649)
We don't need to merge all of the workqueue modules when performing compilation with `--image-codegen` set, we just need the global variable initializers to be defined before they're used in one of the modules. Therefore we can do this by compiling all of the global variable initializers upfront, so that later references will link them properly.
1 parent 8c3452f commit ff14eaf

File tree

2 files changed

+54
-28
lines changed

2 files changed

+54
-28
lines changed

src/jitlayers.cpp

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,12 @@ void jl_link_global(GlobalVariable *GV, void *addr) JL_NOTSAFEPOINT
141141
++LinkedGlobals;
142142
Constant *P = literal_static_pointer_val(addr, GV->getValueType());
143143
GV->setInitializer(P);
144+
GV->setDSOLocal(true);
144145
if (jl_options.image_codegen) {
145146
// If we are forcing imaging mode codegen for debugging,
146147
// emit external non-const symbol to avoid LLVM optimizing the code
147148
// similar to non-imaging mode.
148-
GV->setLinkage(GlobalValue::ExternalLinkage);
149+
assert(GV->hasExternalLinkage());
149150
}
150151
else {
151152
GV->setConstant(true);
@@ -162,6 +163,23 @@ void jl_jit_globals(std::map<void *, GlobalVariable*> &globals) JL_NOTSAFEPOINT
162163
}
163164
}
164165

166+
// used for image_codegen, where we keep all the gvs external
167+
// so we can't jit them directly into each module
168+
static orc::ThreadSafeModule jl_get_globals_module(orc::ThreadSafeContext &ctx, bool imaging_mode, const DataLayout &DL, const Triple &T, std::map<void *, GlobalVariable*> &globals) JL_NOTSAFEPOINT
169+
{
170+
auto lock = ctx.getLock();
171+
auto GTSM = jl_create_ts_module("globals", ctx, imaging_mode, DL, T);
172+
auto GM = GTSM.getModuleUnlocked();
173+
for (auto &global : globals) {
174+
auto GV = global.second;
175+
auto GV2 = new GlobalVariable(*GM, GV->getValueType(), GV->isConstant(), GlobalValue::ExternalLinkage, literal_static_pointer_val(global.first, GV->getValueType()), GV->getName(), nullptr, GV->getThreadLocalMode(), GV->getAddressSpace(), false);
176+
GV2->copyAttributesFrom(GV);
177+
GV2->setDSOLocal(true);
178+
GV2->setAlignment(GV->getAlign());
179+
}
180+
return GTSM;
181+
}
182+
165183
// this generates llvm code for the lambda info
166184
// and adds the result to the jitlayers
167185
// (and the shadow module),
@@ -211,46 +229,53 @@ static jl_callptr_t _jl_compile_codeinst(
211229

212230
if (params._shared_module)
213231
jl_ExecutionEngine->addModule(orc::ThreadSafeModule(std::move(params._shared_module), params.tsctx));
214-
if (!params.imaging) {
215-
StringMap<orc::ThreadSafeModule*> NewExports;
232+
233+
// In imaging mode, we can't inline global variable initializers in order to preserve
234+
// the fiction that we don't know what loads from the global will return. Thus, we
235+
// need to emit a separate module for the globals before any functions are compiled,
236+
// to ensure that the globals are defined when they are compiled.
237+
if (params.imaging) {
238+
jl_ExecutionEngine->addModule(jl_get_globals_module(params.tsctx, params.imaging, params.DL, params.TargetTriple, params.global_targets));
239+
} else {
216240
StringMap<void*> NewGlobals;
217241
for (auto &global : params.global_targets) {
218242
NewGlobals[global.second->getName()] = global.first;
219243
}
220244
for (auto &def : emitted) {
221-
orc::ThreadSafeModule &TSM = std::get<0>(def.second);
222-
//The underlying context object is still locked because params is not destroyed yet
223-
auto M = TSM.getModuleUnlocked();
224-
for (auto &F : M->global_objects()) {
225-
if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) {
226-
NewExports[F.getName()] = &TSM;
227-
}
228-
}
229-
// Let's link all globals here also (for now)
245+
auto M = std::get<0>(def.second).getModuleUnlocked();
230246
for (auto &GV : M->globals()) {
231247
auto InitValue = NewGlobals.find(GV.getName());
232248
if (InitValue != NewGlobals.end()) {
233249
jl_link_global(&GV, InitValue->second);
234250
}
235251
}
236252
}
237-
DenseMap<orc::ThreadSafeModule*, int> Queued;
238-
std::vector<orc::ThreadSafeModule*> Stack;
239-
for (auto &def : emitted) {
240-
// Add the results to the execution engine now
241-
orc::ThreadSafeModule &M = std::get<0>(def.second);
242-
jl_add_to_ee(M, NewExports, Queued, Stack);
243-
assert(Queued.empty() && Stack.empty() && !M);
244-
}
245-
} else {
246-
jl_jit_globals(params.global_targets);
247-
auto main = std::move(emitted[codeinst].first);
248-
for (auto &def : emitted) {
249-
if (def.first != codeinst) {
250-
jl_merge_module(main, std::move(def.second.first));
253+
}
254+
255+
// Collect the exported functions from the emitted modules,
256+
// which form dependencies on which functions need to be
257+
// compiled first. Cycles of functions are compiled together.
258+
// (essentially we compile a DAG of SCCs in reverse topological order,
259+
// if we treat declarations of external functions as edges from declaration
260+
// to definition)
261+
StringMap<orc::ThreadSafeModule*> NewExports;
262+
for (auto &def : emitted) {
263+
orc::ThreadSafeModule &TSM = std::get<0>(def.second);
264+
//The underlying context object is still locked because params is not destroyed yet
265+
auto M = TSM.getModuleUnlocked();
266+
for (auto &F : M->global_objects()) {
267+
if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) {
268+
NewExports[F.getName()] = &TSM;
251269
}
252270
}
253-
jl_ExecutionEngine->addModule(std::move(main));
271+
}
272+
DenseMap<orc::ThreadSafeModule*, int> Queued;
273+
std::vector<orc::ThreadSafeModule*> Stack;
274+
for (auto &def : emitted) {
275+
// Add the results to the execution engine now
276+
orc::ThreadSafeModule &M = std::get<0>(def.second);
277+
jl_add_to_ee(M, NewExports, Queued, Stack);
278+
assert(Queued.empty() && Stack.empty() && !M);
254279
}
255280
++CompiledCodeinsts;
256281
MaxWorkqueueSize.updateMax(emitted.size());

test/llvmpasses/image-codegen.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
# CHECK-NOT: internal global
1414
# CHECK-NOT: private global
1515
# CHECK: jl_global
16-
# CHECK-SAME: = global
16+
# COM: we emit both declarations and definitions, so we may see either style in the IR
17+
# CHECK-SAME: = {{(external )?}}global
1718
# CHECK: julia_f_
1819
# CHECK-NOT: internal global
1920
# CHECK-NOT: private global

0 commit comments

Comments
 (0)