Skip to content

Commit fbc5d2f

Browse files
JohanEngelenIlya Yanokkinke
authored
Add -fdebug-prefix-map and follow clang's behavior for debuginfo file/dir names (#5039)
This is required to have reproducible builds without remapping the source code directory to a fixed location (for example in Bazel, where the source code for every build action will end up in a sandbox directory with some hash in the path name). Try to keep the implementation as close as possible to what clang does. --------- Co-authored-by: Ilya Yanok <ilya.yanok@weka.io> Co-authored-by: Martin Kinkelin <mkinkelin@symmetryinvestments.com>
1 parent 9e1df01 commit fbc5d2f

6 files changed

Lines changed: 92 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- `aarch64-linux-gnu-clang`
1616
- `clang --target=aarch64-linux-gnu`
1717
- The prebuilt arm64/universal macOS packages additionally bundle the arm64 iOS-*simulator* libraries, for out-of-the-box cross-compilation support via e.g. `-mtriple=arm64-apple-ios12.0-simulator`. (#4974)
18+
- New `--fdebug-prefix-map` command-line option and changed debuginfo file/directory name splitting logic (both now similar to clang), to aid reproducible builds. (#5039)
1819

1920
#### Platform support
2021
- Supports LLVM 15 - 21.

driver/cl_options.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,28 @@ cl::opt<bool> emitDwarfDebugInfo(
223223
"gdwarf", cl::ZeroOrMore,
224224
cl::desc("Emit DWARF debuginfo (instead of CodeView) for MSVC targets"));
225225

226+
// Prefix map for filenames in DWARF debuginfo
227+
llvm::SmallVector<std::pair<std::string, std::string>, 0> debugPrefixMap;
228+
229+
struct DwarfPrefixParser : public cl::parser<std::string> {
230+
explicit DwarfPrefixParser(cl::Option &O) : cl::parser<std::string>(O) {}
231+
232+
bool parse(cl::Option &O, llvm::StringRef /*ArgName*/, llvm::StringRef Arg,
233+
std::string & /*Val*/) {
234+
auto [from, to] = Arg.split('=');
235+
if (from.empty() || to.empty()) {
236+
return O.error("invalid debug prefix map: " + Arg);
237+
}
238+
debugPrefixMap.emplace_back(from.str(), to.str());
239+
return false;
240+
}
241+
};
242+
243+
static cl::opt<std::string, false, DwarfPrefixParser> fdebugPrefixMap(
244+
"fdebug-prefix-map", cl::ZeroOrMore,
245+
cl::desc("Prefix map for filenames in DWARF debuginfo"),
246+
cl::value_desc("<old>=<new>"));
247+
226248
cl::opt<bool> noAsm("noasm", cl::desc("Disallow use of inline assembler"),
227249
cl::ZeroOrMore);
228250

driver/cl_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ extern cl::list<std::string> runargs;
4646
extern cl::opt<bool> invokedByLDMD;
4747
extern cl::opt<bool> compileOnly;
4848
extern cl::opt<bool> emitDwarfDebugInfo;
49+
extern llvm::SmallVector<std::pair<std::string, std::string>, 0> debugPrefixMap; // Prefix map for filenames in DWARF debuginfo
4950
extern cl::opt<bool> noAsm;
5051
extern cl::opt<bool> dontWriteObj;
5152
extern cl::opt<std::string> objectFile;

gen/dibuilder.cpp

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "ir/irfuncty.h"
3535
#include "ir/irmodule.h"
3636
#include "ir/irtypeaggr.h"
37+
#include "llvm/ADT/DenseMap.h"
3738
#include "llvm/ADT/SmallString.h"
3839
#include "llvm/Support/FileSystem.h"
3940
#include "llvm/Support/Path.h"
@@ -218,27 +219,71 @@ void DIBuilder::SetValue(Loc loc, llvm::Value *value,
218219
IR->scopebb());
219220
}
220221

222+
std::string DIBuilder::remapDIPath(llvm::StringRef path) {
223+
llvm::SmallString<256> P = path;
224+
for (auto &[from, to] : llvm::reverse(opts::debugPrefixMap))
225+
if (llvm::sys::path::replace_path_prefix(P, from, to))
226+
break;
227+
return P.str().str();
228+
}
229+
221230
DIFile DIBuilder::CreateFile(const char *filename) {
222231
if (!filename)
223232
filename = IR->dmodule->srcfile.toChars();
224233

225-
// clang appears to use the curent working dir as 'directory' for relative
226-
// source paths, and the root path for absolute ones:
227-
// clang -g -emit-llvm -S ..\blub.c =>
228-
// !DIFile(filename: "..\\blub.c", directory: "C:\\LDC\\ninja-ldc", ...)
229-
// !DIFile(filename: "Program
230-
// Files\\LLVM\\lib\\clang\\11.0.1\\include\\stddef.h", directory: "C:\\",
231-
// ...)
232-
233-
if (llvm::sys::path::is_absolute(filename)) {
234-
return DBuilder.createFile(llvm::sys::path::relative_path(filename),
235-
llvm::sys::path::root_path(filename));
234+
// Mimic clang's behavior as much as possible, including fdebug-prefix-map
235+
// remap behavior. See LLVM source clang/lib/CodeGen/CGDebugInfo.cpp
236+
237+
// First check cache
238+
auto iter = filenameToDIFileCache.find(filename);
239+
if (iter != filenameToDIFileCache.end()) {
240+
// Verify that the information still exists.
241+
if (llvm::Metadata *value = iter->second)
242+
return llvm::cast<llvm::DIFile>(value);
236243
}
237244

245+
// Cache miss, create a new DIFile
246+
247+
auto remappedFile = remapDIPath(filename);
248+
238249
llvm::SmallString<128> cwd;
239250
llvm::sys::fs::current_path(cwd);
251+
auto currentDir = remapDIPath(cwd);
252+
253+
llvm::StringRef debuginfoDir;
254+
llvm::StringRef debuginfoFile;
255+
256+
llvm::SmallString<128> DirBuf;
257+
llvm::SmallString<128> FileBuf;
258+
if (llvm::sys::path::is_absolute(remappedFile)) {
259+
// Strip the common prefix (if it is more than just "/" or "C:\") from
260+
// current directory and filename for a more space-efficient encoding.
261+
auto FileIt = llvm::sys::path::begin(remappedFile);
262+
auto FileE = llvm::sys::path::end(remappedFile);
263+
auto CurDirIt = llvm::sys::path::begin(currentDir);
264+
auto CurDirE = llvm::sys::path::end(currentDir);
265+
for (; CurDirIt != CurDirE && *CurDirIt == *FileIt; ++CurDirIt, ++FileIt)
266+
llvm::sys::path::append(DirBuf, *CurDirIt);
267+
if (llvm::sys::path::root_path(DirBuf) == DirBuf) {
268+
// Don't strip the common prefix if it is only the root ("/" or "C:\")
269+
// since that would make diagnostic locations confusing.
270+
debuginfoDir = {};
271+
debuginfoFile = remappedFile;
272+
} else {
273+
for (; FileIt != FileE; ++FileIt)
274+
llvm::sys::path::append(FileBuf, *FileIt);
275+
debuginfoDir = DirBuf;
276+
debuginfoFile = FileBuf;
277+
}
278+
} else {
279+
if (!llvm::sys::path::is_absolute(filename))
280+
debuginfoDir = currentDir;
281+
debuginfoFile = remappedFile;
282+
}
240283

241-
return DBuilder.createFile(filename, cwd);
284+
auto difile = DBuilder.createFile(debuginfoFile, debuginfoDir);
285+
filenameToDIFileCache[filename].reset(difile);
286+
return difile;
242287
}
243288

244289
DIFile DIBuilder::CreateFile(Loc loc) {

gen/dibuilder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class DIBuilder {
6767
const bool emitColumnInfo;
6868

6969
llvm::DenseMap<Declaration*, llvm::TypedTrackingMDRef<llvm::MDNode>> StaticDataMemberCache;
70+
llvm::DenseMap<const char *, llvm::TrackingMDRef> filenameToDIFileCache;
7071

7172
DICompileUnit GetCU() {
7273
return CUNode;
@@ -163,6 +164,7 @@ class DIBuilder {
163164
llvm::SmallVector<llvm::Metadata *, 16> &elems);
164165
void AddStaticMembers(AggregateDeclaration *sd, ldc::DIFile file,
165166
llvm::SmallVector<llvm::Metadata *, 16> &elems);
167+
std::string remapDIPath(llvm::StringRef path);
166168
DIFile CreateFile(const char *filename = nullptr);
167169
DIFile CreateFile(Loc loc);
168170
DIFile CreateFile(Dsymbol *decl);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %ldc -g -fdebug-prefix-map=%S=/blablabla -output-ll -of=%t.ll %s
2+
// RUN: FileCheck %s < %t.ll
3+
4+
// Check that the substitution took place. Could be as `filename: "/blablabla...` or as `directory: "/blablabla...` depending on the invoke path for lit testsuite.
5+
// CHECK: !DIFile({{.*}}: "/blablabla
6+
7+
void foo()
8+
{
9+
}

0 commit comments

Comments
 (0)