Skip to content

Commit 0ba11e5

Browse files
committed
Add manual dllimport 'relocation' pass for static data initializers
References to dllimported globals in static data initializers lead to undefined-symbol linker errors. We need to resolve the indirection manually at runtime. I went with an extra LLVM pass for Windows targets when using `-fvisibility=public`, and scanning the initializers of all global variables (well, only thread-global ones). The problematic pointers in the initializers are nullified and initialized at runtime early via a CRT constructor (working with -betterC as well).
1 parent e168c3a commit 0ba11e5

File tree

5 files changed

+257
-1
lines changed

5 files changed

+257
-1
lines changed

gen/optimizer.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,11 @@ bool ldc_optimize_module(llvm::Module *M) {
412412

413413
addOptimizationPasses(mpm, fpm, optLevel(), sizeLevel());
414414

415+
if (global.params.targetTriple->isOSWindows() &&
416+
opts::symbolVisibility == opts::SymbolVisibility::public_) {
417+
mpm.add(createDLLImportRelocationPass());
418+
}
419+
415420
// Run per-function passes.
416421
fpm.doInitialization();
417422
for (auto &F : *M) {

gen/passes/DLLImportRelocation.cpp

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//===-- DLLImportRelocation.cpp -------------------------------------------===//
2+
//
3+
// LDC – the LLVM D compiler
4+
//
5+
// This file is distributed under the BSD-style LDC license. See the LICENSE
6+
// file for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This transform scans the initializers of global variables for references to
11+
// dllimported globals. Such references need to be 'relocated' manually on
12+
// Windows to prevent undefined-symbol linker errors. This is done by
13+
// 1) nullifying the pointers in the static initializer, and
14+
// 2) initializing these fields at runtime via a CRT constructor.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#define DEBUG_TYPE "dllimport-relocation"
19+
#if LDC_LLVM_VER < 700
20+
#define LLVM_DEBUG DEBUG
21+
#endif
22+
23+
#include "gen/passes/Passes.h"
24+
#include "llvm/ADT/Statistic.h"
25+
#include "llvm/IR/Constants.h"
26+
#include "llvm/IR/IRBuilder.h"
27+
#include "llvm/IR/Module.h"
28+
#include "llvm/Pass.h"
29+
#include "llvm/Support/Debug.h"
30+
#include "llvm/Transforms/Utils/ModuleUtils.h"
31+
32+
using namespace llvm;
33+
34+
STATISTIC(NumPatchedGlobals,
35+
"Number of global variables with patched initializer");
36+
STATISTIC(NumRelocations,
37+
"Total number of patched references to dllimported globals");
38+
39+
namespace {
40+
struct LLVM_LIBRARY_VISIBILITY DLLImportRelocation : public ModulePass {
41+
static char ID; // Pass identification, replacement for typeid
42+
DLLImportRelocation() : ModulePass(ID) {}
43+
44+
// Returns true if the module has been changed.
45+
bool runOnModule(Module &m) override;
46+
};
47+
48+
struct Impl {
49+
Module &m;
50+
51+
// the global variable whose initializer is being fixed up
52+
GlobalVariable *currentGlobal = nullptr;
53+
// the GEP indices from the global to the currently inspected field
54+
SmallVector<uint64_t, 4> currentGepPath;
55+
56+
Impl(Module &m) : m(m) {}
57+
58+
~Impl() {
59+
if (ctor) {
60+
// append a `ret void` instruction
61+
ReturnInst::Create(m.getContext(), &ctor->getEntryBlock());
62+
}
63+
}
64+
65+
// Recursively walks over each field of an initializer and checks for
66+
// references to dllimported globals.
67+
// Returns true if a fixup was necessary.
68+
bool fixup(Constant *initializer) {
69+
if (!initializer)
70+
return false;
71+
72+
// set i to the initializer, skipping over an optional cast
73+
auto i = initializer;
74+
if (auto ce = dyn_cast<ConstantExpr>(i)) {
75+
if (ce->isCast())
76+
i = ce->getOperand(0);
77+
}
78+
79+
// check if i is a reference to a dllimport global
80+
if (auto globalRef = dyn_cast<GlobalVariable>(i)) {
81+
if (globalRef->hasDLLImportStorageClass()) {
82+
onDLLImportReference(globalRef);
83+
return true;
84+
}
85+
return false;
86+
}
87+
88+
const Type *t = initializer->getType();
89+
auto st = dyn_cast<StructType>(t);
90+
auto at = dyn_cast<ArrayType>(t);
91+
if (st || at) {
92+
// descend recursively into each field/element
93+
const uint64_t N = st ? st->getNumElements() : at->getNumElements();
94+
bool hasChanged = false;
95+
for (uint64_t i = 0; i < N; ++i) {
96+
currentGepPath.push_back(i);
97+
if (fixup(initializer->getAggregateElement(i)))
98+
hasChanged = true;
99+
currentGepPath.pop_back();
100+
}
101+
return hasChanged;
102+
}
103+
104+
return false;
105+
}
106+
107+
private:
108+
void onDLLImportReference(GlobalVariable *importedVar) {
109+
++NumRelocations;
110+
111+
// initialize at runtime:
112+
currentGlobal->setConstant(false);
113+
appendToCRTConstructor(importedVar);
114+
115+
const auto pathLength = currentGepPath.size();
116+
if (pathLength == 0) {
117+
currentGlobal->setInitializer(
118+
Constant::getNullValue(currentGlobal->getValueType()));
119+
return;
120+
}
121+
122+
// We cannot mutate a llvm::Constant, so need to replace all parent
123+
// aggregate initializers.
124+
SmallVector<Constant *, 4> initializers;
125+
initializers.reserve(pathLength + 1);
126+
initializers.push_back(currentGlobal->getInitializer());
127+
for (uint64_t i = 0; i < pathLength - 1; ++i) {
128+
initializers.push_back(
129+
initializers.back()->getAggregateElement(currentGepPath[i]));
130+
}
131+
132+
// Nullify the field referencing the dllimported global.
133+
const auto fieldIndex = currentGepPath.back();
134+
auto fieldType =
135+
initializers.back()->getAggregateElement(fieldIndex)->getType();
136+
initializers.push_back(Constant::getNullValue(fieldType));
137+
138+
// Replace all parent aggregate initializers, bottom-up.
139+
for (ptrdiff_t i = pathLength - 1; i >= 0; --i) {
140+
initializers[i] =
141+
replaceField(initializers[i], currentGepPath[i], initializers[i + 1]);
142+
}
143+
144+
currentGlobal->setInitializer(initializers[0]);
145+
}
146+
147+
static Constant *replaceField(Constant *aggregate, uint64_t fieldIndex,
148+
Constant *newFieldValue) {
149+
const auto t = aggregate->getType();
150+
const auto st = dyn_cast<StructType>(t);
151+
const auto at = dyn_cast<ArrayType>(t);
152+
153+
if (!st && !at) {
154+
llvm_unreachable("Only expecting IR structs or arrays here");
155+
return aggregate;
156+
}
157+
158+
const auto N = st ? st->getNumElements() : at->getNumElements();
159+
std::vector<Constant *> elements;
160+
elements.reserve(N);
161+
for (uint64_t i = 0; i < N; ++i)
162+
elements.push_back(aggregate->getAggregateElement(i));
163+
elements[fieldIndex] = newFieldValue;
164+
return st ? ConstantStruct::get(st, elements)
165+
: ConstantArray::get(at, elements);
166+
}
167+
168+
Function *ctor = nullptr;
169+
170+
void createCRTConstructor() {
171+
ctor = Function::Create(
172+
FunctionType::get(Type::getVoidTy(m.getContext()), false),
173+
GlobalValue::PrivateLinkage, "ldc.dllimport_relocation", &m);
174+
llvm::BasicBlock::Create(m.getContext(), "", ctor);
175+
176+
llvm::appendToGlobalCtors(m, ctor, 65535);
177+
}
178+
179+
void appendToCRTConstructor(GlobalVariable *importedVar) {
180+
if (!ctor)
181+
createCRTConstructor();
182+
183+
IRBuilder<> b(&ctor->getEntryBlock());
184+
185+
Value *address = currentGlobal;
186+
for (auto i : currentGepPath) {
187+
if (i <= 0xFFFFFFFFu) {
188+
address = b.CreateConstInBoundsGEP2_32(
189+
address->getType()->getPointerElementType(), address, 0,
190+
static_cast<unsigned>(i));
191+
} else {
192+
address = b.CreateConstInBoundsGEP2_64(address, 0, i);
193+
}
194+
}
195+
196+
Value *value = importedVar;
197+
auto t = address->getType()->getPointerElementType();
198+
if (value->getType() != t)
199+
value = b.CreatePointerCast(value, t);
200+
201+
b.CreateStore(value, address);
202+
}
203+
};
204+
}
205+
206+
char DLLImportRelocation::ID = 0;
207+
static RegisterPass<DLLImportRelocation>
208+
X("dllimport-relocation",
209+
"Patch references to dllimported globals in static initializers");
210+
211+
ModulePass *createDLLImportRelocationPass() {
212+
return new DLLImportRelocation();
213+
}
214+
215+
bool DLLImportRelocation::runOnModule(Module &m) {
216+
Impl impl(m);
217+
bool hasChanged = false;
218+
219+
for (GlobalVariable &global : m.getGlobalList()) {
220+
// TODO: thread-local globals would need to be initialized in a separate TLS
221+
// ctor
222+
if (!global.hasInitializer() || global.isThreadLocal())
223+
continue;
224+
225+
impl.currentGlobal = &global;
226+
impl.currentGepPath.clear();
227+
if (impl.fixup(global.getInitializer())) {
228+
++NumPatchedGlobals;
229+
hasChanged = true;
230+
}
231+
}
232+
233+
return hasChanged;
234+
}

gen/passes/Passes.h

+2
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ llvm::FunctionPass *createSimplifyDRuntimeCalls();
2424
llvm::FunctionPass *createGarbageCollect2Stack();
2525

2626
llvm::ModulePass *createStripExternalsPass();
27+
28+
llvm::ModulePass *createDLLImportRelocationPass();

tests/codegen/fvisibility_dll.d

+15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@
1313

1414
import fvisibility_dll_lib;
1515

16+
// test manual 'relocation' of references to dllimported globals in static data initializers:
17+
__gshared int* dllimportRef = &dllGlobal;
18+
__gshared void* castDllimportRef = &dllGlobal;
19+
immutable void*[2] arrayOfRefs = [ null, cast(immutable) &dllGlobal ];
20+
21+
struct MyStruct
22+
{
23+
int* dllimportRef = &dllGlobal; // init symbol references dllimported global
24+
}
25+
1626
extern(C) void main()
1727
{
1828
assert(dllGlobal == 123);
@@ -24,4 +34,9 @@ extern(C) void main()
2434

2535
scope c = new MyClass;
2636
assert(c.myInt == 456);
37+
38+
assert(dllimportRef == &dllGlobal);
39+
assert(castDllimportRef == &dllGlobal);
40+
assert(arrayOfRefs[1] == &dllGlobal);
41+
assert(MyStruct.init.dllimportRef == &dllGlobal);
2742
}

tests/codegen/inputs/fvisibility_dll_lib.d

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ double dllSum(double a, double b)
99

1010
void dllWeakFoo() @weak {}
1111

12-
// extern(C++) for no vtable; that requires an explicit `export` (contrary to extern(D))
12+
// extern(C++) for -betterC; that requires an explicit `export` (contrary to extern(D))
1313
export extern(C++) class MyClass
1414
{
1515
int myInt = 456;

0 commit comments

Comments
 (0)