Skip to content

Commit 03b19f3

Browse files
committed
Sequester the REPL's linking machinery
All of this is in service of working around a pile of deficiencies in LLVM's Module Linker, and LLVMContext abstractions. And because we're just gonna scrap this code soon anyways, it's probably not worth the effort to push on these bugs to block the broader cleanup here. The LLVM Linker currently does not support linking modules allocated in different contexts. This appears to be motivated in part by LLVM's lack of a facility to clone a module from one context to another. This, in turn, appears to be motivated in part by LLVMContext's lack of a robust notion of identity - which makes it harder than it needs to be to detect the mismatch. However, it is not impossible to clone a module across contexts. We need to get creative and round-trip the module through some serialization layer. Out of convenience, that layer is currently textual IR, though bitcode would work equally well. Given that it is no longer under the caller's control which LLVMContext we generate code in, put all the above together to arrive at an egregious hack that clones the module into the LLVMContext the REPL expects.
1 parent de304b5 commit 03b19f3

File tree

3 files changed

+72
-46
lines changed

3 files changed

+72
-46
lines changed

lib/Immediate/Immediate.cpp

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@
3333
#include "llvm/Config/config.h"
3434
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
3535
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
36-
#include "llvm/IR/DiagnosticPrinter.h"
37-
#include "llvm/IR/DiagnosticInfo.h"
3836
#include "llvm/IR/LLVMContext.h"
39-
#include "llvm/Linker/Linker.h"
4037
#include "llvm/Transforms/IPO.h"
4138
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
4239
#include "llvm/Support/Path.h"
@@ -180,48 +177,6 @@ bool swift::immediate::tryLoadLibraries(ArrayRef<LinkLibrary> LinkLibraries,
180177
[](bool Value) { return Value; });
181178
}
182179

183-
static void linkerDiagnosticHandlerNoCtx(const llvm::DiagnosticInfo &DI) {
184-
if (DI.getSeverity() != llvm::DS_Error)
185-
return;
186-
187-
std::string MsgStorage;
188-
{
189-
llvm::raw_string_ostream Stream(MsgStorage);
190-
llvm::DiagnosticPrinterRawOStream DP(Stream);
191-
DI.print(DP);
192-
}
193-
llvm::errs() << "Error linking swift modules\n";
194-
llvm::errs() << MsgStorage << "\n";
195-
}
196-
197-
198-
199-
static void linkerDiagnosticHandler(const llvm::DiagnosticInfo &DI,
200-
void *Context) {
201-
// This assert self documents our precondition that Context is always
202-
// nullptr. It seems that parts of LLVM are using the flexibility of having a
203-
// context. We don't really care about this.
204-
assert(Context == nullptr && "We assume Context is always a nullptr");
205-
206-
return linkerDiagnosticHandlerNoCtx(DI);
207-
}
208-
209-
bool swift::immediate::linkLLVMModules(llvm::Module *Module,
210-
std::unique_ptr<llvm::Module> SubModule
211-
// TODO: reactivate the linker mode if it is
212-
// supported in llvm again. Otherwise remove the
213-
// commented code completely.
214-
/*, llvm::Linker::LinkerMode LinkerMode */)
215-
{
216-
llvm::LLVMContext &Ctx = SubModule->getContext();
217-
auto OldHandler = Ctx.getDiagnosticHandlerCallBack();
218-
void *OldDiagnosticContext = Ctx.getDiagnosticContext();
219-
Ctx.setDiagnosticHandlerCallBack(linkerDiagnosticHandler, nullptr);
220-
bool Failed = llvm::Linker::linkModules(*Module, std::move(SubModule));
221-
Ctx.setDiagnosticHandlerCallBack(OldHandler, OldDiagnosticContext);
222-
return !Failed;
223-
}
224-
225180
bool swift::immediate::autolinkImportedModules(ModuleDecl *M,
226181
const IRGenOptions &IRGenOpts) {
227182
// Perform autolinking.

lib/Immediate/ImmediateImpl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace llvm {
2828
namespace swift {
2929
class CompilerInstance;
3030
class DiagnosticEngine;
31+
class GeneratedModule;
3132
class IRGenOptions;
3233
class ModuleDecl;
3334
class SILOptions;
@@ -43,7 +44,7 @@ bool tryLoadLibraries(ArrayRef<LinkLibrary> LinkLibraries,
4344
SearchPathOptions SearchPathOpts,
4445
DiagnosticEngine &Diags);
4546
bool linkLLVMModules(llvm::Module *Module,
46-
std::unique_ptr<llvm::Module> SubModule);
47+
std::unique_ptr<llvm::Module> &&SubModule);
4748
bool autolinkImportedModules(ModuleDecl *M, const IRGenOptions &IRGenOpts);
4849

4950
} // end namespace immediate

lib/Immediate/REPL.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@
3131
#include "llvm/ExecutionEngine/MCJIT.h"
3232
#include "llvm/IR/Module.h"
3333
#include "llvm/IR/Constants.h"
34+
#include "llvm/IR/DiagnosticInfo.h"
35+
#include "llvm/IR/DiagnosticPrinter.h"
3436
#include "llvm/Transforms/Utils/Cloning.h"
3537
#include "llvm/ADT/StringSet.h"
38+
#include "llvm/IRReader/IRReader.h"
39+
#include "llvm/Linker/Linker.h"
3640
#include "llvm/Support/ConvertUTF.h"
3741
#include "llvm/Support/PrettyStackTrace.h"
3842
#include "llvm/Support/Process.h"
@@ -226,6 +230,34 @@ class PrettyStackTraceREPL : public llvm::PrettyStackTraceEntry {
226230
};
227231
} // end anonymous namespace
228232

233+
namespace {
234+
static void linkerDiagnosticHandlerNoCtx(const llvm::DiagnosticInfo &DI) {
235+
if (DI.getSeverity() != llvm::DS_Error)
236+
return;
237+
238+
std::string MsgStorage;
239+
{
240+
llvm::raw_string_ostream Stream(MsgStorage);
241+
llvm::DiagnosticPrinterRawOStream DP(Stream);
242+
DI.print(DP);
243+
}
244+
llvm::errs() << "Error linking swift modules\n";
245+
llvm::errs() << MsgStorage << "\n";
246+
}
247+
248+
249+
250+
static void linkerDiagnosticHandler(const llvm::DiagnosticInfo &DI,
251+
void *Context) {
252+
// This assert self documents our precondition that Context is always
253+
// nullptr. It seems that parts of LLVM are using the flexibility of having a
254+
// context. We don't really care about this.
255+
assert(Context == nullptr && "We assume Context is always a nullptr");
256+
257+
return linkerDiagnosticHandlerNoCtx(DI);
258+
}
259+
} // end anonymous namespace
260+
229261
/// EditLine wrapper that implements the user interface behavior for reading
230262
/// user input to the REPL. All of its methods must be usable from a separate
231263
/// thread and so shouldn't touch anything outside of the EditLine, History,
@@ -849,6 +881,44 @@ class REPLEnvironment {
849881
}
850882
}
851883

884+
bool linkLLVMModules(llvm::Module *Module,
885+
std::unique_ptr<llvm::Module> &&ModuleToLink) {
886+
// EGREGIOUS HACKS AHEAD
887+
//
888+
// 1) LLVMContext does not support robust notions of identity rdar://61895075
889+
// 2) llvm::Linker does not support modules allocated in separate contexts
890+
// rdar://61894890
891+
// 3) Cloning modules across contexts is a known deficiency in LLVM
892+
// rdar://61896692
893+
// 4) Round-tripping through the IR printer and parser is not guaranteed to
894+
// result in the same IR. It is heavily tested and implied that this is
895+
// the case, but LLVM makes no formal guarantees.
896+
//
897+
// So, work around 1) and do a naive pointer comparison to see if we
898+
// allocated the module we're about to link in a separate context.
899+
if (&Module->getContext() != &ModuleToLink->getContext()) {
900+
// If so, work around 2) by round-tripping the IR and parsing back it
901+
// into the original context. With module in-hand, we have successfully
902+
// worked around 3).
903+
llvm::SmallString<1024> scratch;
904+
llvm::raw_svector_ostream PrintBuffer(scratch);
905+
ModuleToLink->print(PrintBuffer, nullptr);
906+
auto Buffer = llvm::MemoryBuffer::getMemBufferCopy(PrintBuffer.str());
907+
llvm::SMDiagnostic Err;
908+
// Finally, hope that 4) doesn't come back to bite us in the long run.
909+
ModuleToLink = llvm::parseIR(Buffer->getMemBufferRef(), Err,
910+
Module->getContext());
911+
}
912+
913+
llvm::LLVMContext &Ctx = Module->getContext();
914+
auto OldHandler = Ctx.getDiagnosticHandlerCallBack();
915+
void *OldDiagnosticContext = Ctx.getDiagnosticContext();
916+
Ctx.setDiagnosticHandlerCallBack(linkerDiagnosticHandler, nullptr);
917+
bool Failed = llvm::Linker::linkModules(*Module, std::move(ModuleToLink));
918+
Ctx.setDiagnosticHandlerCallBack(OldHandler, OldDiagnosticContext);
919+
return !Failed;
920+
}
921+
852922
bool executeSwiftSource(llvm::StringRef Line, const ProcessCmdLine &CmdLine) {
853923
SWIFT_DEFER {
854924
// Always flush diagnostic consumers after executing a line.

0 commit comments

Comments
 (0)