Skip to content

Commit 9dc63f8

Browse files
Dev VMclaude
andcommitted
Fix naked function symbol mangling and linkage for Windows
- Use getIRMangledName() instead of mangleExact() to get proper Windows calling convention decoration (e.g., \01 prefix for vectorcall) - Use DtoLinkage() for proper linkage determination for all function types - Fix linkage for functions already declared by DtoDeclareFunction() - Remove redundant COMDAT setup code This fixes undefined symbol errors when using naked template functions across Windows DLL boundaries. The issue was that DtoDeclareFunction() creates functions with ExternalLinkage, and DtoDefineNakedFunction() wasn't correcting the linkage or using the proper IR mangle name. Add test that cross-compiles for Windows and verifies naked template functions work correctly with DLL linking. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 36620f0 commit 9dc63f8

File tree

3 files changed

+78
-15
lines changed

3 files changed

+78
-15
lines changed

gen/naked.cpp

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "gen/llvm.h"
2222
#include "gen/llvmhelpers.h"
2323
#include "gen/logger.h"
24+
#include "gen/mangling.h"
2425
#include "gen/tollvm.h"
2526
#include "ir/irfunction.h"
2627
#include "llvm/IR/InlineAsm.h"
@@ -148,27 +149,32 @@ void DtoDefineNakedFunction(FuncDeclaration *fd) {
148149
IF_LOG Logger::println("DtoDefineNakedFunction(%s)", mangleExact(fd));
149150
LOG_SCOPE;
150151

151-
const char *mangle = mangleExact(fd);
152152
const auto &triple = *global.params.targetTriple;
153153

154+
// Get the proper IR mangle name (includes Windows calling convention decoration)
155+
TypeFunction *tf = fd->type->isTypeFunction();
156+
const std::string irMangle = getIRMangledName(fd, tf ? tf->linkage : LINK::d);
157+
154158
// Get or create the LLVM function first, before visiting the body.
155159
// The visitor may call Declaration_codegen which needs an IR insert point.
156160
llvm::Module &module = gIR->module;
157-
llvm::Function *func = module.getFunction(mangle);
161+
llvm::Function *func = module.getFunction(irMangle);
158162

159163
if (!func) {
160164
// Create function type using the existing infrastructure
161165
llvm::FunctionType *funcType = DtoFunctionType(fd);
162166

163-
// Create function with appropriate linkage
164-
llvm::GlobalValue::LinkageTypes linkage;
165-
if (fd->isInstantiated()) {
166-
linkage = llvm::GlobalValue::LinkOnceODRLinkage;
167-
} else {
168-
linkage = llvm::GlobalValue::ExternalLinkage;
169-
}
167+
// Get proper linkage using the standard infrastructure.
168+
// This correctly handles lambdas (internal linkage), templates (weak_odr),
169+
// and other cases.
170+
const auto lwc = DtoLinkage(fd);
170171

171-
func = llvm::Function::Create(funcType, linkage, mangle, &module);
172+
func = llvm::Function::Create(funcType, lwc.first, irMangle, &module);
173+
174+
// Set up COMDAT if needed (for template deduplication on ELF/Windows)
175+
if (lwc.second) {
176+
func->setComdat(module.getOrInsertComdat(irMangle));
177+
}
172178
} else if (!func->empty()) {
173179
// Function already has a body - this can happen if the function was
174180
// already defined (e.g., template instantiation in another module).
@@ -177,6 +183,23 @@ void DtoDefineNakedFunction(FuncDeclaration *fd) {
177183
} else if (func->hasFnAttribute(llvm::Attribute::Naked)) {
178184
// Function already has naked attribute - it was already processed
179185
return;
186+
} else {
187+
// Function was declared but not yet defined (e.g., by DtoDeclareFunction).
188+
// Fix the linkage - DtoDeclareFunction always uses ExternalLinkage,
189+
// but naked functions need proper linkage based on their declaration type.
190+
const auto lwc = DtoLinkage(fd);
191+
// Clear DLL storage class before setting linkage - LLVM requires that
192+
// functions with local linkage (internal/private) have DefaultStorageClass.
193+
if (lwc.first == llvm::GlobalValue::InternalLinkage ||
194+
lwc.first == llvm::GlobalValue::PrivateLinkage) {
195+
func->setDLLStorageClass(llvm::GlobalValue::DefaultStorageClass);
196+
}
197+
func->setLinkage(lwc.first);
198+
if (lwc.second) {
199+
func->setComdat(module.getOrInsertComdat(irMangle));
200+
} else {
201+
func->setComdat(nullptr);
202+
}
180203
}
181204

182205
// Set naked attribute - this tells LLVM not to generate prologue/epilogue
@@ -187,11 +210,6 @@ void DtoDefineNakedFunction(FuncDeclaration *fd) {
187210
func->addFnAttr(llvm::Attribute::OptimizeNone);
188211
func->addFnAttr(llvm::Attribute::NoInline);
189212

190-
// For template instantiations, set up COMDAT for deduplication
191-
if (fd->isInstantiated()) {
192-
func->setComdat(module.getOrInsertComdat(mangle));
193-
}
194-
195213
// Set other common attributes
196214
func->addFnAttr(llvm::Attribute::NoUnwind);
197215

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Tests that naked template functions get correct symbol mangling on Windows.
2+
//
3+
// The bug: When a naked template function is called, DtoDeclareFunction creates
4+
// a declaration with the proper IR mangle name (including \01 prefix for
5+
// vectorcall). Without the fix, DtoDefineNakedFunction would create a definition
6+
// with a DIFFERENT mangle name (missing the \01 prefix), leaving the declared
7+
// function undefined.
8+
//
9+
// This test verifies that calling and defining a naked template in the same
10+
// module produces a linkable object file.
11+
//
12+
// See: https://github.com/ldc-developers/ldc/issues/4294
13+
14+
// REQUIRES: target_X86
15+
// REQUIRES: atleast_llvm1600
16+
// REQUIRES: ld.lld
17+
18+
// Compile for Windows with LTO and link into a DLL.
19+
// LTO is critical for this test - without LTO, the linker resolves both
20+
// symbol variants to the same name, masking the bug.
21+
// RUN: %ldc -mtriple=x86_64-windows-msvc -betterC -flto=full -c %s -of=%t.obj
22+
// RUN: ld.lld -flavor link /dll /noentry /export:caller %t.obj /out:%t.dll
23+
24+
module naked_lambda_linkage;
25+
26+
// Non-exported naked template function.
27+
// The call in caller() triggers DtoDeclareFunction first, then the function
28+
// is defined by DtoDefineNakedFunction. Without the fix, these use different
29+
// mangle names and the declared symbol remains undefined.
30+
uint nakedTemplateFunc(int N)() pure @safe @nogc {
31+
asm pure nothrow @nogc @trusted {
32+
naked;
33+
mov EAX, N;
34+
ret;
35+
}
36+
}
37+
38+
// This function calls the naked template, triggering the declaration before definition
39+
extern(C) export uint caller() {
40+
return nakedTemplateFunc!42();
41+
}

tests/lit.site.cfg.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ if (platform.system() == 'Windows') and os.path.isfile( cdb ):
214214
if (platform.system() != 'Windows') and lit.util.which('gdb', config.environment['PATH']):
215215
config.available_features.add('gdb')
216216

217+
# Check whether ld.lld is present (for cross-platform COFF linking tests)
218+
if lit.util.which('ld.lld', config.environment['PATH']):
219+
config.available_features.add('ld.lld')
220+
217221
if 'LD_LIBRARY_PATH' in os.environ:
218222
libs = []
219223
for lib_path in [s for s in os.environ['LD_LIBRARY_PATH'].split(':') if s]:

0 commit comments

Comments
 (0)