Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9ca884a
Emit naked functions as LLVM IR with inline asm instead of module asm
Dec 23, 2025
76f45e7
Fix IRBuilderHelper assertion when DtoDefineNakedFunction called at m…
Dec 23, 2025
4b07717
Fix naked_asm_corner_cases.d test to work on macOS
Dec 23, 2025
36620f0
Fix naked_asm_output.d test for flexible function ordering
Dec 23, 2025
9dc63f8
Fix naked function symbol mangling and linkage for Windows
Dec 23, 2025
130ae8c
Fix naked lambda dllexport assertion on Windows
Dec 23, 2025
f475f9e
Fix naked_asm_corner_cases.d test for Windows and 32-bit
Dec 23, 2025
c7907b7
WIP: Remove DtoDefineNakedFunction()
kinke Jan 12, 2026
7f16e37
WIP: Hack away special cases for *naked* DMD-style inline asm
kinke Feb 6, 2026
3b59004
[restore support for jumping to non-asm labels]
kinke Feb 7, 2026
62c09e4
[little cleanup]
kinke Feb 7, 2026
420cc00
Merge remote-tracking branch 'origin/master' into naked-llvm-ir-refactor
kinke Feb 7, 2026
006e01b
Naked DMD-style inline asm: Disallow branches to non-asm labels
kinke Feb 7, 2026
19a56a5
Alternative approach: Allow jumping to D labels in naked DMD-style in…
kinke Feb 7, 2026
7c97088
Remove 2 new naked DMD-style inline-asm tests again
kinke Apr 4, 2026
aec8193
[revise new tests/codegen/naked_asm_output.d]
kinke Apr 4, 2026
325cad1
[revise new tests/codegen/naked_asm_corner_cases.d]
kinke Apr 4, 2026
3a12831
[revise new tests/linking/asm_labels_lto.d]
kinke Apr 4, 2026
4d91a03
Merge remote-tracking branch 'origin/master' into naked-llvm-ir-refactor
kinke Apr 4, 2026
c98dc83
[add changelog entry]
kinke Apr 4, 2026
3206d53
[tiny nits after last review pass]
kinke Apr 4, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#### Big news
- Support for [LLVM 22](https://releases.llvm.org/22.1.0/docs/ReleaseNotes.html). The prebuilt packages use v22.1.2. (#5097, #5102)
- Minimum LLVM version raised to 18. (#5094)
- DMD-style inline assembly: `asm { naked; }` is now much less of a special case wrt. codegen - such naked functions are now emitted as if `asm { naked; }` was replaced with the `@naked` function UDA. IR-wise, they are not emitted as module-level asm blobs anymore, but regular IR functions. This should lift a few former `asm { naked; }`-specific restrictions and fixed at least one LTO issue. (#5041)
- Predefined version `LDC_LLVM_*` now only contains the LLVM major version, i.e., former `version (LDC_LLVM_1801)` with LLVM v18.1 is now `version (LDC_LLVM_18)`. Use `ldc.intrinsics.LLVM_version` for backwards compatibility if really needed. (#5109)

#### Platform support
Expand Down
3 changes: 3 additions & 0 deletions dmd/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,9 @@ class FuncDeclaration : public Declaration
VarDeclaration *v_argptr; // '_argptr' variable
VarDeclarations *parameters; // Array of VarDeclaration's for parameters
DsymbolTable *labtab; // statement label symbol table
#if IN_LLVM
Identifiers *asmLabels; // identifiers of labels defined in DMD-style inline assembly
#endif
Dsymbol *overnext; // next in overload list
FuncDeclaration *overnext0; // next in overload list (only used during IFTI)
Loc endloc; // location of closing curly bracket
Expand Down
4 changes: 4 additions & 0 deletions dmd/func.d
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ version (IN_LLVM)
VarDeclaration v_argptr; /// '_argptr' variable
VarDeclarations* parameters; /// Array of VarDeclaration's for parameters
DsymbolTable labtab; /// statement label symbol table
version (IN_LLVM)
{
Identifiers* asmLabels; /// identifiers of labels defined in DMD-style inline assembly
}
Dsymbol overnext; /// next in overload list
FuncDeclaration overnext0; /// next in overload list (only used during IFTI)
Loc endloc; /// location of closing curly bracket
Expand Down
7 changes: 7 additions & 0 deletions dmd/statementsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -3695,6 +3695,13 @@ version (IN_LLVM)
if (auto ls = s.isLabelStatement())
{
sc.func.searchLabel(ls.ident, ls.loc);

version (IN_LLVM)
{
if (!sc.func.asmLabels)
sc.func.asmLabels = new Identifiers();
sc.func.asmLabels.push(ls.ident);
}
}
}
}
Expand Down
76 changes: 11 additions & 65 deletions gen/asm-x86.h
Original file line number Diff line number Diff line change
Expand Up @@ -2340,57 +2340,15 @@ struct AsmProcessor {
AsmCode *asmcode, AsmArgMode mode = Mode_Input) {
using namespace dmd;

if (sc->func->isNaked()) {
switch (type) {
case Arg_Integer:
if (e->type->isUnsigned()) {
insnTemplate << "$" << e->toUInteger();
} else {
if (type == Arg_Integer) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this now for non-naked asm too, to avoid an integer input for the asm expression - simply use the literal in the template string directly, for readability and simplicity.

if (e->type->isUnsigned()) {
insnTemplate << "$$" << e->toUInteger();
} else {
#ifndef ASM_X86_64
insnTemplate << "$" << static_cast<sinteger_t>(e->toInteger());
insnTemplate << "$$" << static_cast<sinteger_t>(e->toInteger());
#else
insnTemplate << "$" << e->toInteger();
insnTemplate << "$$" << e->toInteger();
#endif
}
break;

case Arg_Pointer:
error(stmt->loc, "unsupported pointer reference to `%s` in naked asm",
e->toChars());
break;

case Arg_Memory:
// Peel off one layer of explicitly taking the address, if present.
if (auto ae = e->isAddrExp()) {
e = ae->e1;
}

if (auto v = e->isVarExp()) {
if (VarDeclaration *vd = v->var->isVarDeclaration()) {
if (!vd->isDataseg()) {
error(stmt->loc, "only global variables can be referenced by "
"identifier in naked asm");
break;
}

// print out the mangle
if (prependExtraUnderscore(vd->resolvedLinkage())) {
insnTemplate << "_";
}
OutBuffer buf;
mangleToBuffer(vd, buf);
insnTemplate << buf.peekChars();
getIrGlobal(vd, true)->nakedUse = true;
break;
}
}
error(stmt->loc, "unsupported memory reference to `%s` in naked asm",
e->toChars());
break;

default:
llvm_unreachable("Unsupported argument in asm.");
break;
}
} else {
insnTemplate << fmt << "<<" << (mode == Mode_Input ? "in" : "out")
Expand All @@ -2401,13 +2359,6 @@ struct AsmProcessor {
void addOperand2(const char *fmtpre, const char *fmtpost, AsmArgType type,
Expression *e, AsmCode *asmcode,
AsmArgMode mode = Mode_Input) {
if (sc->func->isNaked()) {
// taken from above
error(stmt->loc, "only global variables can be referenced by identifier in "
"naked asm");
return;
}

insnTemplate << fmtpre << "<<" << (mode == Mode_Input ? "in" : "out")
<< ">>" << fmtpost;
asmcode->args.push_back(AsmArg(type, e, mode));
Expand Down Expand Up @@ -3078,11 +3029,9 @@ struct AsmProcessor {
// If we subtract 8 from our const-displacement, then we can use the `H` modifier to ensure
// that we always end up with a valid syntax for a memory operand with an offset.
// So, we do just that when we have const-displacement in play.
// (Only for non-naked asm, as this isn't an issue for naked asm.)
//
// See also: https://lists.llvm.org/pipermail/llvm-dev/2017-August/116244.html
const auto forceLeadingDisplacement = hasConstDisplacement && !sc->func->isNaked();
if (forceLeadingDisplacement) {
if (hasConstDisplacement) {
// Subtract 8 from our const-displacement, and prepare to add the 8 from the `H` modifier.
insnTemplate << "-8+";
}
Expand All @@ -3092,14 +3041,11 @@ struct AsmProcessor {
use_star = false;
}

if (!sc->func->isNaked()) // no addrexp in naked asm please :)
{
Type *tt = pointerTo(e->type);
e = createAddrExp(Loc(), e);
e->type = tt;
}
Type *tt = pointerTo(e->type);
e = createAddrExp(Loc(), e);
e->type = tt;

if (forceLeadingDisplacement) {
if (hasConstDisplacement) {
// We have a const-displacement in play, so we add the `H` modifier, as described earlier.
insnTemplate << "${" << "<<" << (mode == Mode_Input ? "in" : "out")
<< asmcode->args.size() << ">>" << ":H}";
Expand Down
29 changes: 8 additions & 21 deletions gen/asmstmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,9 +532,14 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
}
Identifier *const ident = targetLabel->ident;

// if internal, no special handling is necessary, skip
if (llvm::any_of(asmblock->internalLabels,
[ident](Identifier *i) { return i->equals(ident); })) {
// if the label is defined in inline asm, we can jump to it directly
if (fd->isNaked() // in naked DMD-style inline-asm functions, all D labels
// become assembly labels too (and the extra forwarding
// code would mess with the stack due to an i32 alloca)
|| (fd->asmLabels &&
llvm::any_of(*fd->asmLabels, [ident](Identifier *i) {
return i->equals(ident);
}))) {
continue;
}

Expand Down Expand Up @@ -784,21 +789,3 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
p->ir->SetInsertPoint(bb);
}
}

//////////////////////////////////////////////////////////////////////////////

void AsmStatement_toNakedIR(InlineAsmStatement *stmt, IRState *irs) {
IF_LOG Logger::println("InlineAsmStatement::toNakedIR(): %s",
stmt->loc.toChars());
LOG_SCOPE;

// is there code?
if (!stmt->asmcode) {
return;
}
AsmCode *code = static_cast<AsmCode *>(stmt->asmcode);

// build asm stmt
replace_func_name(irs, code->insnTemplate);
irs->nakedAsm << "\t" << code->insnTemplate << std::endl;
}
38 changes: 14 additions & 24 deletions gen/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,10 +773,8 @@ static LinkageWithCOMDAT lowerFuncLinkage(FuncDeclaration *fdecl) {
return LinkageWithCOMDAT(LLGlobalValue::ExternalLinkage, false);
}

// A body-less declaration always needs to be marked as external in LLVM
// (also e.g. naked template functions which would otherwise be weak_odr,
// but where the definition is in module-level inline asm).
if (!fdecl->fbody || fdecl->isNaked()) {
// A body-less declaration always needs to be marked as external in LLVM.
if (!fdecl->fbody) {
return LinkageWithCOMDAT(LLGlobalValue::ExternalLinkage, false);
}

Expand Down Expand Up @@ -1119,12 +1117,6 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
gIR->funcGenStates.pop_back();
};

// if this function is naked, we take over right away! no standard processing!
if (fd->isNaked()) {
DtoDefineNakedFunction(fd);
return;
}

SCOPE_EXIT {
if (irFunc->isDynamicCompiled()) {
defineDynamicCompiledFunction(gIR, irFunc);
Expand Down Expand Up @@ -1208,31 +1200,26 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
gIR->ir->setFastMathFlags(irFunc->FMF);
gIR->DBuilder.EmitFuncStart(fd);

// @naked: emit body and return, no prologue/epilogue
if (func->hasFnAttribute(llvm::Attribute::Naked)) {
Statement_toIR(fd->fbody, gIR);
const bool wasDummy = eraseDummyAfterReturnBB(gIR->scopebb());
if (!wasDummy && !gIR->scopereturned()) {
// this is what clang does to prevent LLVM complaining about
// non-terminated function
gIR->ir->CreateUnreachable();
}
return;
if (fd->isNaked()) { // DMD-style `asm { naked; }`
func->addFnAttr(llvm::Attribute::Naked);
}

// @naked: emit body and return, no prologue/epilogue
const bool isNaked = func->hasFnAttribute(llvm::Attribute::Naked);

// create temporary allocas block
funcGen.allocasBlock =
llvm::BasicBlock::Create(gIR->context(), "allocas", func);

emitInstrumentationFnEnter(fd);

if (global.params.trace && fd->emitInstrumentation && !fd->isCMain() &&
!fd->isNaked()) {
!isNaked) {
emitDMDStyleFunctionTrace(*gIR, fd, funcGen);
}

// give the 'this' parameter (an lvalue) storage and debug info
if (irFty.arg_this) {
if (!isNaked && irFty.arg_this) {
LLValue *thisvar = irFunc->thisArg;
assert(thisvar);

Expand All @@ -1254,7 +1241,7 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
}

// define all explicit parameters
if (fd->parameters)
if (!isNaked && fd->parameters)
defineParameters(irFty, *fd->parameters);

// Initialize PGO state for this function
Expand Down Expand Up @@ -1320,7 +1307,10 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {

// pass the previous block into this block
gIR->DBuilder.EmitStopPoint(fd->endloc);
if (func->getReturnType() == LLType::getVoidTy(gIR->context())) {

if (isNaked) {
gIR->ir->CreateUnreachable();
} else if (func->getReturnType() == LLType::getVoidTy(gIR->context())) {
gIR->ir->CreateRetVoid();
} else if (isAnyMainFunction(fd)) {
gIR->ir->CreateRet(LLConstant::getNullValue(func->getReturnType()));
Expand Down
1 change: 0 additions & 1 deletion gen/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ void DtoResolveFunction(FuncDeclaration *fdecl);
void DtoDeclareFunction(FuncDeclaration *fdecl);
void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally = false);

void DtoDefineNakedFunction(FuncDeclaration *fd);
void emitABIReturnAsmStmt(IRAsmBlock *asmblock, Loc loc,
FuncDeclaration *fdecl);

Expand Down
4 changes: 0 additions & 4 deletions gen/irstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ struct IRAsmBlock {
std::set<std::string> clobs;
size_t outputcount;

// stores the labels within the asm block
std::vector<Identifier *> internalLabels;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collecting them now in FuncDeclaration.asmLabels instead, for the whole function, means that non-naked asm functions also don't need the extra complicated forwarding code when jumping to an asm label in another asm block.


CompoundAsmStatement *asmBlock;
LLType *retty;
unsigned retn;
Expand Down Expand Up @@ -220,7 +217,6 @@ struct IRState {

// for inline asm
IRAsmBlock *asmBlock = nullptr;
std::ostringstream nakedAsm;

// Globals to pin in the llvm.used array to make sure they are not
// eliminated.
Expand Down
Loading
Loading