Skip to content

Commit 2d7ee8c

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 2d7ee8c

2 files changed

Lines changed: 85 additions & 15 deletions

File tree

gen/naked.cpp

Lines changed: 27 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,17 @@ 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+
func->setLinkage(lwc.first);
192+
if (lwc.second) {
193+
func->setComdat(module.getOrInsertComdat(irMangle));
194+
} else {
195+
func->setComdat(nullptr);
196+
}
180197
}
181198

182199
// Set naked attribute - this tells LLVM not to generate prologue/epilogue
@@ -187,11 +204,6 @@ void DtoDefineNakedFunction(FuncDeclaration *fd) {
187204
func->addFnAttr(llvm::Attribute::OptimizeNone);
188205
func->addFnAttr(llvm::Attribute::NoInline);
189206

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Tests that naked template functions are correctly handled when building DLLs
2+
// on Windows. Verifies that:
3+
// 1. Naked template functions can be exported from DLLs
4+
// 2. Other modules can link against and use them
5+
//
6+
// This tests the fix for naked function code generation that ensures proper
7+
// symbol mangling and linkage on Windows targets.
8+
//
9+
// See: https://github.com/ldc-developers/ldc/issues/4294
10+
11+
// REQUIRES: target_X86
12+
13+
// RUN: split-file %s %t
14+
15+
// Compile for Windows x86_64 target using lld-link
16+
// RUN: %ldc -mtriple=x86_64-windows-msvc -betterC -c %t/naked_template.d -of=%t/naked_template.obj
17+
// RUN: %ldc -mtriple=x86_64-windows-msvc -betterC -c %t/naked_user.d -I%t -of=%t/naked_user.obj
18+
19+
// Create DLL from template module - exports both callFromTemplate and the naked template
20+
// RUN: lld-link /dll /noentry %t/naked_template.obj /out:%t/lib.dll /implib:%t/lib.lib
21+
22+
// Link user module against the DLL
23+
// RUN: lld-link /dll /noentry /export:callFromUser %t/naked_user.obj %t/lib.lib /out:%t/user.dll
24+
25+
//--- naked_template.d
26+
module naked_template;
27+
28+
// Exported naked template function - the export attribute ensures it gets
29+
// dllexport and can be linked from other DLLs
30+
export uint nakedTemplateFunc(char op)() pure @safe @nogc {
31+
asm pure nothrow @nogc @trusted {
32+
naked;
33+
xor EAX, EAX;
34+
}
35+
static if (op == '+')
36+
asm pure nothrow @nogc @trusted { inc EAX; }
37+
else
38+
asm pure nothrow @nogc @trusted { dec EAX; }
39+
asm pure nothrow @nogc @trusted {
40+
ret;
41+
}
42+
}
43+
44+
// Pre-instantiate with export
45+
alias nakedPlus = nakedTemplateFunc!('+');
46+
47+
extern(C) export uint callFromTemplate() {
48+
return nakedPlus();
49+
}
50+
51+
//--- naked_user.d
52+
module naked_user;
53+
import naked_template;
54+
55+
// Use the exported template from the DLL
56+
extern(C) export uint callFromUser() {
57+
return nakedPlus();
58+
}

0 commit comments

Comments
 (0)