Skip to content

Commit d6bd3d1

Browse files
Dev VMclaude
andcommitted
Fix DMD-style asm labels for LTO with template functions
When template functions containing DMD-style inline assembly are instantiated in multiple modules and linked with LTO, duplicate symbol errors can occur because the asm labels have identical names. This fix assigns a unique ID to each function containing inline asm, based on a deterministic hash of the template instantiation module name. The ID is appended to label names, ensuring uniqueness across compilation units. Fixes: #4294 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 091a6e9 commit d6bd3d1

12 files changed

Lines changed: 164 additions & 13 deletions

File tree

dmd/declaration.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,11 @@ class FuncDeclaration : public Declaration
565565
// Whether to emit instrumentation code if -fprofile-instr-generate is specified,
566566
// the value is set with pragma(LDC_profile_instr, true|false)
567567
bool emitInstrumentation;
568+
569+
// Unique ID for asm labels in this function, used to prevent duplicate
570+
// symbol errors when LTO merges multiple instantiations of template functions.
571+
// See: https://github.com/ldc-developers/ldc/issues/4294
572+
unsigned asmLabelId;
568573
#endif
569574

570575
VarDeclaration *vresult; // result variable for out contracts

dmd/func.d

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ version (IN_LLVM)
240240
// Whether to emit instrumentation code if -fprofile-instr-generate is specified,
241241
// the value is set with pragma(LDC_profile_instr, true|false)
242242
bool emitInstrumentation = true;
243+
244+
// Unique ID for asm labels in this function, used to prevent duplicate
245+
// symbol errors when LTO merges multiple instantiations of template functions.
246+
// See: https://github.com/ldc-developers/ldc/issues/4294
247+
uint asmLabelId = 0;
243248
}
244249

245250
VarDeclaration vresult; /// result variable for out contracts

gen/asm-x86.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2416,7 +2416,9 @@ struct AsmProcessor {
24162416
void addLabel(const char *id) {
24172417
// We need to delay emitting the actual function name, see
24182418
// replace_func_name in asmstmt.cpp for details.
2419-
printLabelName(insnTemplate, "<<func>>", id);
2419+
// Pass the function's asmLabelId to make labels unique across template
2420+
// instantiations when LTO merges them.
2421+
printLabelName(insnTemplate, "<<func>>", id, sc->func->asmLabelId);
24202422
}
24212423

24222424
/* Determines whether the operand is a register, memory reference

gen/asmstmt.cpp

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
#include "dmd/dsymbol.h"
1212
#include "dmd/errors.h"
1313
#include "dmd/ldcbindings.h"
14+
#include "dmd/module.h"
1415
#include "dmd/scope.h"
1516
#include "dmd/statement.h"
17+
#include "dmd/template.h"
1618
#include "gen/dvalue.h"
1719
#include "gen/functions.h"
1820
#include "gen/irstate.h"
@@ -97,6 +99,12 @@ static void replace_func_name(IRState *p, std::string &insnt) {
9799
}
98100
}
99101

102+
// Global counter for generating unique asm label IDs per function.
103+
// This prevents duplicate symbol errors when LTO merges multiple
104+
// instantiations of template functions with inline asm.
105+
// See: https://github.com/ldc-developers/ldc/issues/4294
106+
static unsigned nextAsmLabelId = 1;
107+
100108
Statement *asmSemantic(AsmStatement *s, Scope *sc) {
101109
if (!s->tokens) {
102110
return nullptr;
@@ -106,10 +114,50 @@ Statement *asmSemantic(AsmStatement *s, Scope *sc) {
106114
if (s->tokens->value == TOK::string_ ||
107115
s->tokens->value == TOK::leftParenthesis) {
108116
auto gas = createGccAsmStatement(s->loc, s->tokens);
109-
return gccAsmSemantic(gas, sc);
117+
return dmd::gccAsmSemantic(gas, sc);
110118
}
111119

112120
// this is DMD-style asm
121+
122+
// Assign a unique asm label ID to this function if it doesn't have one yet.
123+
// This ID is used to make labels unique across different compilation units
124+
// when LTO merges template instantiations.
125+
//
126+
// For template functions, we use the instantiation module's name to create
127+
// a deterministic hash that differs across compilation units. This ensures:
128+
// 1. Same compilation → same hash → reproducible builds
129+
// 2. Different compilation units → different hash → no LTO conflicts
130+
if (sc->func->asmLabelId == 0) {
131+
unsigned moduleHash = 0;
132+
133+
// For template instantiations, use the instantiation module's name
134+
// This is the module that caused this template to be instantiated,
135+
// which differs between compilation units even for the same template.
136+
if (auto ti = sc->func->isInstantiated()) {
137+
if (ti->minst && ti->minst->ident) {
138+
const char* modName = ti->minst->ident->toChars();
139+
for (const char* p = modName; *p; ++p) {
140+
moduleHash = moduleHash * 31 + static_cast<unsigned char>(*p);
141+
}
142+
}
143+
}
144+
145+
// If not a template or no instantiation module, use the current module
146+
if (moduleHash == 0 && sc->_module && sc->_module->ident) {
147+
const char* modName = sc->_module->ident->toChars();
148+
for (const char* p = modName; *p; ++p) {
149+
moduleHash = moduleHash * 31 + static_cast<unsigned char>(*p);
150+
}
151+
}
152+
153+
// Combine module hash with counter for uniqueness within a compilation
154+
// Use golden ratio constant for better bit mixing
155+
sc->func->asmLabelId = (moduleHash * 2654435761u) ^ (nextAsmLabelId++ * 31);
156+
// Ensure non-zero
157+
if (sc->func->asmLabelId == 0) {
158+
sc->func->asmLabelId = 1;
159+
}
160+
}
113161
sc->func->hasInlineAsm(true);
114162

115163
const auto caseSensitive = s->caseSensitive();
@@ -513,7 +561,7 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
513561
static size_t uniqueLabelsId = 0;
514562
std::string suffix = "_llvm_asm_end";
515563
suffix += std::to_string(uniqueLabelsId++);
516-
printLabelName(asmGotoEndLabel, fdmangle, suffix.c_str());
564+
printLabelName(asmGotoEndLabel, fdmangle, suffix.c_str(), fd->asmLabelId);
517565
}
518566

519567
// initialize the setter statement we're going to build
@@ -550,7 +598,7 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
550598
IF_LOG Logger::println(
551599
"statement '%s' references outer label '%s': creating forwarder",
552600
a->code.c_str(), ident->toChars());
553-
printLabelName(code, fdmangle, ident->toChars());
601+
printLabelName(code, fdmangle, ident->toChars(), fd->asmLabelId);
554602
code << ":\n\t";
555603
code << "movl $<<in" << n_goto << ">>, $<<out0>>\n";
556604
// FIXME: Store the value -> label mapping somewhere, so it can be

gen/llvmhelpers.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include <llvm/IR/Constant.h>
4949
#include <llvm/Analysis/ConstantFolding.h>
5050
#include <stack>
51+
#include <unordered_map>
5152

5253
using namespace dmd;
5354

@@ -1369,11 +1370,18 @@ bool isLLVMUnsigned(Type *t) {
13691370
////////////////////////////////////////////////////////////////////////////////
13701371

13711372
void printLabelName(std::ostream &target, const char *func_mangle,
1372-
const char *label_name) {
1373+
const char *label_name, unsigned asmLabelId) {
13731374
// note: quotes needed for Unicode
13741375
target << '"'
13751376
<< gTargetMachine->getMCAsmInfo()->getPrivateGlobalPrefix().str()
1376-
<< func_mangle << "_" << label_name << '"';
1377+
<< func_mangle << "_" << label_name;
1378+
// Append unique ID to prevent duplicate symbol errors when LTO merges
1379+
// multiple instantiations of template functions with inline asm.
1380+
// See: https://github.com/ldc-developers/ldc/issues/4294
1381+
if (asmLabelId > 0) {
1382+
target << "_" << asmLabelId;
1383+
}
1384+
target << '"';
13771385
}
13781386

13791387
////////////////////////////////////////////////////////////////////////////////

gen/llvmhelpers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ DValue *DtoCallFunction(Loc loc, Type *resulttype, DValue *fnval,
210210
Type *stripModifiers(Type *type, bool transitive = false);
211211

212212
void printLabelName(std::ostream &target, const char *func_mangle,
213-
const char *label_name);
213+
const char *label_name, unsigned asmLabelId = 0);
214214

215215
void AppendFunctionToLLVMGlobalCtorsDtors(llvm::Function *func,
216216
const uint32_t priority,

gen/naked.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class ToNakedIRVisitor : public Visitor {
129129
LOG_SCOPE;
130130

131131
printLabelName(irs->nakedAsm, mangleExact(irs->func()->decl),
132-
stmt->ident->toChars());
132+
stmt->ident->toChars(), irs->func()->decl->asmLabelId);
133133
irs->nakedAsm << ":";
134134

135135
if (stmt->statement) {

gen/statements.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1548,7 +1548,7 @@ class ToIRVisitor : public Visitor {
15481548
auto a = new IRAsmStmt;
15491549
std::stringstream label;
15501550
printLabelName(label, mangleExact(irs->func()->decl),
1551-
stmt->ident->toChars());
1551+
stmt->ident->toChars(), irs->func()->decl->asmLabelId);
15521552
label << ":";
15531553
a->code = label.str();
15541554
irs->asmBlock->s.push_back(a);

tests/codegen/asm_labels.d

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ void foo(int a)
88
{
99
asm
1010
{
11-
// CHECK: jmp .L_D10asm_labels3fooFiZv_label
11+
// CHECK: jmp .L_D10asm_labels3fooFiZv_label{{(_[0-9]+)?}}
1212
jmp label;
13-
// CHECK-NEXT: .L_D10asm_labels3fooFiZv_label:
13+
// CHECK-NEXT: .L_D10asm_labels3fooFiZv_label{{(_[0-9]+)?}}:
1414
label:
1515
ret;
1616
}
@@ -21,9 +21,9 @@ void foo(uint a)
2121
{
2222
asm
2323
{
24-
// CHECK: jmp .L_D10asm_labels3fooFkZv_label
24+
// CHECK: jmp .L_D10asm_labels3fooFkZv_label{{(_[0-9]+)?}}
2525
jmp label;
26-
// CHECK-NEXT: .L_D10asm_labels3fooFkZv_label:
26+
// CHECK-NEXT: .L_D10asm_labels3fooFkZv_label{{(_[0-9]+)?}}:
2727
label:
2828
ret;
2929
}

tests/linking/asm_labels_lto.d

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Tests that DMD-style asm labels in naked template functions get unique IDs
2+
// to prevent "symbol already defined" errors during LTO linking.
3+
//
4+
// Without the fix, LTO linking fails with:
5+
// symbol '.L_D...nakedAsmTemplate..._L1' is already defined
6+
//
7+
// See: https://github.com/ldc-developers/ldc/issues/4294
8+
9+
// REQUIRES: LTO
10+
// REQUIRES: atleast_llvm1400
11+
// REQUIRES: target_X86
12+
13+
// Compile two modules that each instantiate the same naked asm template.
14+
// RUN: %ldc -flto=full -c -I%S %S/inputs/asm_lto_user.d -of=%t_user%obj
15+
// RUN: %ldc -flto=full -c -I%S %s -of=%t_main%obj
16+
17+
// Link with LTO - this fails without the fix due to duplicate asm labels.
18+
// RUN: %ldc -flto=full %t_main%obj %t_user%obj -of=%t%exe
19+
20+
// Verify the executable runs correctly.
21+
// RUN: %t%exe
22+
23+
module asm_labels_lto;
24+
25+
import inputs.asm_lto_template;
26+
import inputs.asm_lto_user;
27+
28+
int main() {
29+
// Both modules instantiate nakedAsmTemplate!1
30+
// Without unique label IDs, LTO linking fails with "symbol already defined"
31+
uint a = nakedAsmTemplate!1(); // From this module's instantiation
32+
uint b = useTemplate(); // From asm_lto_user's instantiation
33+
34+
// Both should return the same value (>= 100)
35+
return (a == b && a >= 100) ? 0 : 1;
36+
}

0 commit comments

Comments
 (0)