Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dmd/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,11 @@ class FuncDeclaration : public Declaration
// Whether to emit instrumentation code if -fprofile-instr-generate is specified,
// the value is set with pragma(LDC_profile_instr, true|false)
bool emitInstrumentation;

// Unique ID for asm labels in this function, used to prevent duplicate
// symbol errors when LTO merges multiple instantiations of template functions.
// See: https://github.com/ldc-developers/ldc/issues/4294
unsigned asmLabelId;
#endif

VarDeclaration *vresult; // result variable for out contracts
Expand Down
5 changes: 5 additions & 0 deletions dmd/func.d
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ version (IN_LLVM)
// Whether to emit instrumentation code if -fprofile-instr-generate is specified,
// the value is set with pragma(LDC_profile_instr, true|false)
bool emitInstrumentation = true;

// Unique ID for asm labels in this function, used to prevent duplicate
// symbol errors when LTO merges multiple instantiations of template functions.
// See: https://github.com/ldc-developers/ldc/issues/4294
uint asmLabelId = 0;
}

VarDeclaration vresult; /// result variable for out contracts
Expand Down
4 changes: 3 additions & 1 deletion gen/asm-x86.h
Original file line number Diff line number Diff line change
Expand Up @@ -2416,7 +2416,9 @@ struct AsmProcessor {
void addLabel(const char *id) {
// We need to delay emitting the actual function name, see
// replace_func_name in asmstmt.cpp for details.
printLabelName(insnTemplate, "<<func>>", id);
// Pass the function's asmLabelId to make labels unique across template
// instantiations when LTO merges them.
printLabelName(insnTemplate, "<<func>>", id, sc->func->asmLabelId);
}

/* Determines whether the operand is a register, memory reference
Expand Down
54 changes: 51 additions & 3 deletions gen/asmstmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
#include "dmd/dsymbol.h"
#include "dmd/errors.h"
#include "dmd/ldcbindings.h"
#include "dmd/module.h"
#include "dmd/scope.h"
#include "dmd/statement.h"
#include "dmd/template.h"
#include "gen/dvalue.h"
#include "gen/functions.h"
#include "gen/irstate.h"
Expand Down Expand Up @@ -97,6 +99,12 @@ static void replace_func_name(IRState *p, std::string &insnt) {
}
}

// Global counter for generating unique asm label IDs per function.
// This prevents duplicate symbol errors when LTO merges multiple
// instantiations of template functions with inline asm.
// See: https://github.com/ldc-developers/ldc/issues/4294
static unsigned nextAsmLabelId = 1;

Statement *asmSemantic(AsmStatement *s, Scope *sc) {
if (!s->tokens) {
return nullptr;
Expand All @@ -106,10 +114,50 @@ Statement *asmSemantic(AsmStatement *s, Scope *sc) {
if (s->tokens->value == TOK::string_ ||
s->tokens->value == TOK::leftParenthesis) {
auto gas = createGccAsmStatement(s->loc, s->tokens);
return gccAsmSemantic(gas, sc);
return dmd::gccAsmSemantic(gas, sc);
}

// this is DMD-style asm

// Assign a unique asm label ID to this function if it doesn't have one yet.
// This ID is used to make labels unique across different compilation units
// when LTO merges template instantiations.
//
// For template functions, we use the instantiation module's name to create
// a deterministic hash that differs across compilation units. This ensures:
// 1. Same compilation → same hash → reproducible builds
// 2. Different compilation units → different hash → no LTO conflicts
if (sc->func->asmLabelId == 0) {
unsigned moduleHash = 0;

// For template instantiations, use the instantiation module's name
// This is the module that caused this template to be instantiated,
// which differs between compilation units even for the same template.
if (auto ti = sc->func->isInstantiated()) {
if (ti->minst && ti->minst->ident) {
const char* modName = ti->minst->ident->toChars();
for (const char* p = modName; *p; ++p) {
moduleHash = moduleHash * 31 + static_cast<unsigned char>(*p);
}
}
}

// If not a template or no instantiation module, use the current module
if (moduleHash == 0 && sc->_module && sc->_module->ident) {
const char* modName = sc->_module->ident->toChars();
for (const char* p = modName; *p; ++p) {
moduleHash = moduleHash * 31 + static_cast<unsigned char>(*p);
}
}

// Combine module hash with counter for uniqueness within a compilation
// Use golden ratio constant for better bit mixing
sc->func->asmLabelId = (moduleHash * 2654435761u) ^ (nextAsmLabelId++ * 31);
// Ensure non-zero
if (sc->func->asmLabelId == 0) {
sc->func->asmLabelId = 1;
}
}
sc->func->hasInlineAsm(true);

const auto caseSensitive = s->caseSensitive();
Expand Down Expand Up @@ -513,7 +561,7 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
static size_t uniqueLabelsId = 0;
std::string suffix = "_llvm_asm_end";
suffix += std::to_string(uniqueLabelsId++);
printLabelName(asmGotoEndLabel, fdmangle, suffix.c_str());
printLabelName(asmGotoEndLabel, fdmangle, suffix.c_str(), fd->asmLabelId);
}

// initialize the setter statement we're going to build
Expand Down Expand Up @@ -550,7 +598,7 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
IF_LOG Logger::println(
"statement '%s' references outer label '%s': creating forwarder",
a->code.c_str(), ident->toChars());
printLabelName(code, fdmangle, ident->toChars());
printLabelName(code, fdmangle, ident->toChars(), fd->asmLabelId);
code << ":\n\t";
code << "movl $<<in" << n_goto << ">>, $<<out0>>\n";
// FIXME: Store the value -> label mapping somewhere, so it can be
Expand Down
12 changes: 10 additions & 2 deletions gen/llvmhelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <llvm/IR/Constant.h>
#include <llvm/Analysis/ConstantFolding.h>
#include <stack>
#include <unordered_map>

using namespace dmd;

Expand Down Expand Up @@ -1369,11 +1370,18 @@ bool isLLVMUnsigned(Type *t) {
////////////////////////////////////////////////////////////////////////////////

void printLabelName(std::ostream &target, const char *func_mangle,
const char *label_name) {
const char *label_name, unsigned asmLabelId) {
// note: quotes needed for Unicode
target << '"'
<< gTargetMachine->getMCAsmInfo()->getPrivateGlobalPrefix().str()
<< func_mangle << "_" << label_name << '"';
<< func_mangle << "_" << label_name;
// Append unique ID to prevent duplicate symbol errors when LTO merges
// multiple instantiations of template functions with inline asm.
// See: https://github.com/ldc-developers/ldc/issues/4294
if (asmLabelId > 0) {
target << "_" << asmLabelId;
}
target << '"';
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion gen/llvmhelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ DValue *DtoCallFunction(Loc loc, Type *resulttype, DValue *fnval,
Type *stripModifiers(Type *type, bool transitive = false);

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

void AppendFunctionToLLVMGlobalCtorsDtors(llvm::Function *func,
const uint32_t priority,
Expand Down
2 changes: 1 addition & 1 deletion gen/naked.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class ToNakedIRVisitor : public Visitor {
LOG_SCOPE;

printLabelName(irs->nakedAsm, mangleExact(irs->func()->decl),
stmt->ident->toChars());
stmt->ident->toChars(), irs->func()->decl->asmLabelId);
irs->nakedAsm << ":";

if (stmt->statement) {
Expand Down
2 changes: 1 addition & 1 deletion gen/statements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1548,7 +1548,7 @@ class ToIRVisitor : public Visitor {
auto a = new IRAsmStmt;
std::stringstream label;
printLabelName(label, mangleExact(irs->func()->decl),
stmt->ident->toChars());
stmt->ident->toChars(), irs->func()->decl->asmLabelId);
label << ":";
a->code = label.str();
irs->asmBlock->s.push_back(a);
Expand Down
8 changes: 4 additions & 4 deletions tests/codegen/asm_labels.d
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ void foo(int a)
{
asm
{
// CHECK: jmp .L_D10asm_labels3fooFiZv_label
// CHECK: jmp .L_D10asm_labels3fooFiZv_label{{(_[0-9]+)?}}
jmp label;
// CHECK-NEXT: .L_D10asm_labels3fooFiZv_label:
// CHECK-NEXT: .L_D10asm_labels3fooFiZv_label{{(_[0-9]+)?}}:
label:
ret;
}
Expand All @@ -21,9 +21,9 @@ void foo(uint a)
{
asm
{
// CHECK: jmp .L_D10asm_labels3fooFkZv_label
// CHECK: jmp .L_D10asm_labels3fooFkZv_label{{(_[0-9]+)?}}
jmp label;
// CHECK-NEXT: .L_D10asm_labels3fooFkZv_label:
// CHECK-NEXT: .L_D10asm_labels3fooFkZv_label{{(_[0-9]+)?}}:
label:
ret;
}
Expand Down
90 changes: 90 additions & 0 deletions tests/linking/asm_labels_lto.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Tests that DMD-style asm labels in naked template functions get unique IDs
// to prevent "symbol already defined" errors during LTO linking.
//
// Without the fix, LTO linking fails with:
// symbol '.L_D...nakedAsmTemplate..._L1' is already defined
//
// See: https://github.com/ldc-developers/ldc/issues/4294

// REQUIRES: LTO
// REQUIRES: target_X86
// REQUIRES: Linux

// RUN: split-file %s %t

// Compile two modules that each instantiate the same naked asm template.
// RUN: %ldc -flto=full -c -I%t %t/asm_lto_user.d -of=%t/user%obj
// RUN: %ldc -flto=full -c -I%t %t/asm_lto_main.d -of=%t/main%obj

// Link with LTO - this fails without the fix due to duplicate asm labels.
// RUN: %ldc -flto=full %t/main%obj %t/user%obj -of=%t/test%exe

// Verify the executable runs correctly.
// RUN: %t/test%exe

//--- asm_lto_template.d
// Template with naked function containing inline asm labels.
// This mimics std.internal.math.biguintx86 which triggers issue #4294.
// The naked function's asm becomes "module asm" which gets concatenated
// during LTO, causing duplicate symbol errors without the fix.
module asm_lto_template;

// Template function - when instantiated in multiple modules and linked
// with LTO, the labels must have unique IDs to avoid "symbol already defined"
uint nakedAsmTemplate(int N)() {
version (D_InlineAsm_X86) {
asm { naked; }
asm {
xor EAX, EAX;
L1:
add EAX, N;
cmp EAX, 100;
jl L1;
ret;
}
} else version (D_InlineAsm_X86_64) {
asm { naked; }
asm {
xor EAX, EAX;
L1:
add EAX, N;
cmp EAX, 100;
jl L1;
ret;
}
} else {
// Fallback for non-x86
uint result = 0;
while (result < 100) result += N;
return result;
}
}

//--- asm_lto_user.d
// Second module that instantiates the same naked asm template.
// This creates a separate instantiation that, when linked with LTO,
// causes "symbol already defined" errors if labels aren't unique.
module asm_lto_user;

import asm_lto_template;

uint useTemplate() {
// Instantiate nakedAsmTemplate!1 - same as in main module
return nakedAsmTemplate!1();
}

//--- asm_lto_main.d
module asm_lto_main;

import asm_lto_template;
import asm_lto_user;

int main() {
// Both modules instantiate nakedAsmTemplate!1
// Without unique label IDs, LTO linking fails with "symbol already defined"
uint a = nakedAsmTemplate!1(); // From this module's instantiation
uint b = useTemplate(); // From asm_lto_user's instantiation

// Both should return the same value (>= 100)
return (a == b && a >= 100) ? 0 : 1;
}