diff --git a/CHANGELOG.md b/CHANGELOG.md index 701c537c3f9..d9f19defa1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/dmd/declaration.h b/dmd/declaration.h index 75333483745..5c4562aab90 100644 --- a/dmd/declaration.h +++ b/dmd/declaration.h @@ -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 diff --git a/dmd/func.d b/dmd/func.d index 6709e32ebb0..e39e233eaef 100644 --- a/dmd/func.d +++ b/dmd/func.d @@ -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 diff --git a/dmd/statementsem.d b/dmd/statementsem.d index 987979bb883..a5be77972f8 100644 --- a/dmd/statementsem.d +++ b/dmd/statementsem.d @@ -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); +} } } } diff --git a/gen/asm-x86.h b/gen/asm-x86.h index eecd1a8b654..2104b4155c1 100644 --- a/gen/asm-x86.h +++ b/gen/asm-x86.h @@ -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) { + if (e->type->isUnsigned()) { + insnTemplate << "$$" << e->toUInteger(); + } else { #ifndef ASM_X86_64 - insnTemplate << "$" << static_cast(e->toInteger()); + insnTemplate << "$$" << static_cast(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") @@ -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)); @@ -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+"; } @@ -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}"; diff --git a/gen/asmstmt.cpp b/gen/asmstmt.cpp index 12d364d012d..b5c868a844c 100644 --- a/gen/asmstmt.cpp +++ b/gen/asmstmt.cpp @@ -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; } @@ -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(stmt->asmcode); - - // build asm stmt - replace_func_name(irs, code->insnTemplate); - irs->nakedAsm << "\t" << code->insnTemplate << std::endl; -} diff --git a/gen/functions.cpp b/gen/functions.cpp index f48a1d45e97..b1e106f6bc2 100644 --- a/gen/functions.cpp +++ b/gen/functions.cpp @@ -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); } @@ -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); @@ -1208,18 +1200,13 @@ 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); @@ -1227,12 +1214,12 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) { 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); @@ -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 @@ -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())); diff --git a/gen/functions.h b/gen/functions.h index 5dcfa7032dc..609da95620d 100644 --- a/gen/functions.h +++ b/gen/functions.h @@ -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); diff --git a/gen/irstate.h b/gen/irstate.h index 3441b86071e..0e8983a9f71 100644 --- a/gen/irstate.h +++ b/gen/irstate.h @@ -94,9 +94,6 @@ struct IRAsmBlock { std::set clobs; size_t outputcount; - // stores the labels within the asm block - std::vector internalLabels; - CompoundAsmStatement *asmBlock; LLType *retty; unsigned retn; @@ -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. diff --git a/gen/naked.cpp b/gen/naked.cpp index 5e5b3ea653d..e63ed83d557 100644 --- a/gen/naked.cpp +++ b/gen/naked.cpp @@ -27,228 +27,6 @@ using namespace dmd; -//////////////////////////////////////////////////////////////////////////////// -// FIXME: Integrate these functions -void AsmStatement_toNakedIR(InlineAsmStatement *stmt, IRState *irs); - -//////////////////////////////////////////////////////////////////////////////// - -class ToNakedIRVisitor : public Visitor { - IRState *irs; - -public: - explicit ToNakedIRVisitor(IRState *irs) : irs(irs) {} - - ////////////////////////////////////////////////////////////////////////// - - // Import all functions from class Visitor - using Visitor::visit; - - ////////////////////////////////////////////////////////////////////////// - - void visit(Statement *stmt) override { - error(stmt->loc, "Statement not allowed in naked function"); - } - - ////////////////////////////////////////////////////////////////////////// - - void visit(InlineAsmStatement *stmt) override { - AsmStatement_toNakedIR(stmt, irs); - } - - ////////////////////////////////////////////////////////////////////////// - - void visit(CompoundStatement *stmt) override { - IF_LOG Logger::println("CompoundStatement::toNakedIR(): %s", - stmt->loc.toChars()); - LOG_SCOPE; - - if (stmt->statements) { - for (auto s : *stmt->statements) { - if (s) { - s->accept(this); - } - } - } - } - - ////////////////////////////////////////////////////////////////////////// - - void visit(ExpStatement *stmt) override { - IF_LOG Logger::println("ExpStatement::toNakedIR(): %s", - stmt->loc.toChars()); - LOG_SCOPE; - - // This happens only if there is a ; at the end: - // asm { naked; ... }; - // Is this a legal AST? - if (!stmt->exp) { - return; - } - - // only expstmt supported in declarations - if (!stmt->exp || stmt->exp->op != EXP::declaration) { - visit(static_cast(stmt)); - return; - } - - DeclarationExp *d = static_cast(stmt->exp); - VarDeclaration *vd = d->declaration->isVarDeclaration(); - FuncDeclaration *fd = d->declaration->isFuncDeclaration(); - EnumDeclaration *ed = d->declaration->isEnumDeclaration(); - - // and only static variable/function declaration - // no locals or nested stuffies! - if (!vd && !fd && !ed) { - visit(static_cast(stmt)); - return; - } - if (vd && !(vd->storage_class & (STCstatic | STCmanifest))) { - error(vd->loc, "non-static variable `%s` not allowed in naked function", - vd->toChars()); - return; - } - if (fd && !fd->isStatic()) { - error(fd->loc, - "non-static nested function `%s` not allowed in naked function", - fd->toChars()); - return; - } - // enum decls should always be safe - - // make sure the symbols gets processed - // TODO: codegen() here is likely incorrect - Declaration_codegen(d->declaration, irs); - } - - ////////////////////////////////////////////////////////////////////////// - - void visit(LabelStatement *stmt) override { - IF_LOG Logger::println("LabelStatement::toNakedIR(): %s", - stmt->loc.toChars()); - LOG_SCOPE; - - printLabelName(irs->nakedAsm, mangleExact(irs->func()->decl), - stmt->ident->toChars()); - irs->nakedAsm << ":"; - - if (stmt->statement) { - stmt->statement->accept(this); - } - } -}; - -//////////////////////////////////////////////////////////////////////////////// - -void DtoDefineNakedFunction(FuncDeclaration *fd) { - IF_LOG Logger::println("DtoDefineNakedFunction(%s)", mangleExact(fd)); - LOG_SCOPE; - - // we need to do special processing on the body, since we only want - // to allow actual inline asm blocks to reach the final asm output - - std::ostringstream &asmstr = gIR->nakedAsm; - - // build function header - - // FIXME: could we perhaps use llvm asmwriter to give us these details ? - - const char *mangle = mangleExact(fd); - std::string fullmangle; // buffer only - - const auto &triple = *global.params.targetTriple; - bool const isWin = triple.isOSWindows(); - bool const isDarwin = triple.isOSDarwin(); - - // osx is different - // also mangling has an extra underscore prefixed - if (isDarwin) { - fullmangle += '_'; - fullmangle += mangle; - mangle = fullmangle.c_str(); - - asmstr << "\t.section\t__TEXT,__text,regular,pure_instructions" - << std::endl; - asmstr << "\t.globl\t" << mangle << std::endl; - if (fd->isInstantiated()) { - asmstr << "\t.weak_definition\t" << mangle << std::endl; - } - asmstr << "\t.p2align\t4, 0x90" << std::endl; - asmstr << mangle << ":" << std::endl; - } - // Windows is different - else if (isWin) { - // mangled names starting with '?' (MSVC++ symbols) apparently need quoting - if (mangle[0] == '?') { - fullmangle += '"'; - fullmangle += mangle; - fullmangle += '"'; - mangle = fullmangle.c_str(); - } else if (triple.isArch32Bit()) { - // prepend extra underscore for Windows x86 - fullmangle += '_'; - fullmangle += mangle; - mangle = fullmangle.c_str(); - } - - asmstr << "\t.def\t" << mangle << ";" << std::endl; - // hard code these two numbers for now since gas ignores .scl and llvm - // is defaulting to .type 32 for everything I have seen - asmstr << "\t.scl\t2;" << std::endl; - asmstr << "\t.type\t32;" << std::endl; - asmstr << "\t.endef" << std::endl; - - if (fd->isInstantiated()) { - asmstr << "\t.section\t.text,\"xr\",discard," << mangle << std::endl; - } else { - asmstr << "\t.text" << std::endl; - } - asmstr << "\t.globl\t" << mangle << std::endl; - asmstr << "\t.p2align\t4, 0x90" << std::endl; - asmstr << mangle << ":" << std::endl; - } else { - if (fd->isInstantiated()) { - asmstr << "\t.section\t.text." << mangle << ",\"axG\",@progbits," - << mangle << ",comdat" << std::endl; - asmstr << "\t.weak\t" << mangle << std::endl; - } else { - asmstr << "\t.text" << std::endl; - asmstr << "\t.globl\t" << mangle << std::endl; - } - asmstr << "\t.p2align\t4, 0x90" << std::endl; - asmstr << "\t.type\t" << mangle << ",@function" << std::endl; - asmstr << mangle << ":" << std::endl; - } - - // emit body - ToNakedIRVisitor v(gIR); - fd->fbody->accept(&v); - - // We could have generated new errors in toNakedIR(), but we are in codegen - // already so we have to abort here. - if (global.errors) { - fatal(); - } - - // emit size after body - // llvm does this on linux, but not on osx or Win - if (!(isWin || isDarwin)) { - asmstr << "\t.size\t" << mangle << ", .-" << mangle << std::endl - << std::endl; - } - - gIR->module.appendModuleInlineAsm(asmstr.str()); - asmstr.str(""); - - if (global.params.dllexport || - (global.params.targetTriple->isOSWindows() && fd->isExport())) { - // Embed a linker switch telling the MS linker to export the naked function. - // This mimics the effect of the dllexport attribute for regular functions. - const auto linkerSwitch = std::string("/EXPORT:") + mangle; - gIR->addLinkerOption(llvm::StringRef(linkerSwitch)); - } -} - //////////////////////////////////////////////////////////////////////////////// void emitABIReturnAsmStmt(IRAsmBlock *asmblock, Loc loc, @@ -436,7 +214,7 @@ DValue *DtoInlineAsmExpr(Loc loc, FuncDeclaration *fd, LLSmallVector operands; LLSmallVector indirectTypes; operands.reserve(n); - + Type *returnType = fd->type->nextOf(); const size_t cisize = constraintInfo.size(); const size_t minRequired = n + (returnType->ty == TY::Tvoid ? 0 : 1); diff --git a/gen/statements.cpp b/gen/statements.cpp index 6ee89057ff4..004fba0656a 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -1550,17 +1550,21 @@ class ToIRVisitor : public Visitor { auto &PGO = irs->funcGen().pgo; PGO.setCurrentStmt(stmt); + auto fd = irs->func()->decl; + + const auto getFullAsmLabelString = [stmt, fd]() { + std::stringstream s; + printLabelName(s, mangleExact(fd), stmt->ident->toChars()); + s << ":"; + return s.str(); + }; + // if it's an inline asm label, we don't create a basicblock, just emit it // in the asm if (irs->asmBlock) { auto a = new IRAsmStmt; - std::stringstream label; - printLabelName(label, mangleExact(irs->func()->decl), - stmt->ident->toChars()); - label << ":"; - a->code = label.str(); + a->code = getFullAsmLabelString(); irs->asmBlock->s.push_back(a); - irs->asmBlock->internalLabels.push_back(stmt->ident); // disable inlining irs->func()->setNeverInline(); @@ -1574,6 +1578,13 @@ class ToIRVisitor : public Visitor { } irs->ir->SetInsertPoint(labelBB); + + // in a naked DMD-style inline-asm function, create an assembly label too + // (so that inline-asm can jump to it directly) + if (fd->isNaked()) { + DtoInlineAsmExpr(stmt->loc, getFullAsmLabelString(), "", {}, {}, + irs->ir->getVoidTy()); + } } PGO.emitCounterIncrement(stmt); diff --git a/tests/codegen/naked_asm_corner_cases.d b/tests/codegen/naked_asm_corner_cases.d new file mode 100644 index 00000000000..3bbd2a10c21 --- /dev/null +++ b/tests/codegen/naked_asm_corner_cases.d @@ -0,0 +1,107 @@ +// Tests corner cases for naked functions with DMD-style inline asm. +// +// This tests: +// 1. Stack manipulation (push/pop) +// 2. Forward and backward jumps +// 3. Nested labels +// 4. Naked function calling convention + +// REQUIRES: host_X86 && target_X86 + +// RUN: %ldc -run %s + +module naked_asm_corner_cases; + +// Test 1: Stack manipulation with push/pop +extern(C) int stackManipulation() { + asm { naked; } + // Save callee-saved register + version (D_InlineAsm_X86_64) + asm { push RBX; } + else + asm { push EBX; } + asm { + mov EAX, 42; + mov EBX, EAX; // Use the saved register + mov EAX, EBX; + } + // Restore + version (D_InlineAsm_X86_64) + asm { pop RBX; } + else + asm { pop EBX; } + asm { ret; } +} + +// Test 2: Forward jump (jump to label defined later) +extern(C) int forwardJump() { + asm { + naked; + mov EAX, 1; + jmp skip; // Forward jump + mov EAX, 0; // Should be skipped + skip: + ret; + } +} + +// Test 3: Backward jump (loop) +extern(C) int backwardJump() { + asm { + naked; + xor EAX, EAX; + again: + inc EAX; + cmp EAX, 5; + jl again; // Backward jump + ret; + } +} + +// Test 4: Multiple control flow paths +extern(C) int multiPath(int x) { + asm { naked; } + version (D_InlineAsm_X86_64) { + // x is in ECX for the Win64 ABI, otherwise EDI for SysV + version (Windows) + asm { test ECX, ECX; } + else + asm { test EDI, EDI; } + } else { + // x is on stack at [ESP+4] for 32-bit cdecl + asm { + mov EAX, [ESP+4]; + test EAX, EAX; + } + } + asm { + jz path1; + jmp path2; + path1: + mov EAX, 10; + jmp done; + path2: + mov EAX, 20; + done: + ret; + } +} + +// Test 5: Naked function with static immutable variable declaration +extern(C) int nakedWithStaticDecl() { + static immutable int staticVal = 42; + asm { + naked; + mov EAX, staticVal; + ret; + } +} + +void main() { + assert(stackManipulation() == 42, "stackManipulation failed"); + assert(forwardJump() == 1, "forwardJump failed"); + assert(backwardJump() == 5, "backwardJump failed"); + assert(multiPath(0) == 10, "multiPath(0) failed"); + assert(multiPath(1) == 20, "multiPath(1) failed"); + assert(nakedWithStaticDecl() == 42, "nakedWithStaticDecl failed"); +} diff --git a/tests/codegen/naked_asm_output.d b/tests/codegen/naked_asm_output.d new file mode 100644 index 00000000000..c7e6d8d1adc --- /dev/null +++ b/tests/codegen/naked_asm_output.d @@ -0,0 +1,124 @@ +// Tests that naked functions with DMD-style inline asm generate correct +// machine code without prologue/epilogue overhead. +// +// This verifies: +// 1. No function prologue (no push rbp, mov rbp rsp, etc.) +// 2. No function epilogue (no pop rbp before our explicit ret) +// 3. Labels are generated correctly +// 4. Template instantiations use proper linkage (comdat) + +// REQUIRES: target_X86 + +// Generate both .s (asm) and .ll (IR) outputs at once. +// RUN: %ldc -mtriple=x86_64-linux-gnu -O0 -output-s -output-ll -of=%t.s %s + +// Test 1: Basic naked function - verify no prologue/epilogue +// RUN: FileCheck %s --check-prefix=ASM < %t.s + +// Test 2: Verify LLVM IR has correct attributes +// RUN: FileCheck %s --check-prefix=IR < %t.ll + +module naked_asm_output; + +// ASM-LABEL: simpleNaked: +// ASM-NEXT: .cfi_startproc +// ASM-NEXT: #APP +// ASM-NEXT: xorl %eax, %eax +// ASM-NEXT: retq + +// IR-LABEL: define i32 @simpleNaked() +// IR-SAME: #[[ATTRS:[0-9]+]] +extern(C) int simpleNaked() { + asm { + naked; + xor EAX, EAX; + ret; + } +} + +// ASM-LABEL: nakedWithLabels: +// ASM-NEXT: .cfi_startproc +// ASM-NEXT: #APP +// ASM-NEXT: xorl %eax, %eax +// ASM-NEXT: .LnakedWithLabels_loop: +// ASM-NEXT: incl %eax +// ASM-NEXT: cmpl $10, %eax +// ASM-NEXT: jl .LnakedWithLabels_loop +// ASM-NEXT: retq + +extern(C) int nakedWithLabels() { + asm { + naked; + xor EAX, EAX; + loop: + inc EAX; + cmp EAX, 10; + jl loop; + ret; + } +} + +// ASM-LABEL: nakedWithMultipleLabels: +// ASM-NEXT: .cfi_startproc +// ASM-NEXT: #APP +// ASM-NEXT: jmp .LnakedWithMultipleLabels_innerAsmLabel +// ASM-NEXT: .LnakedWithMultipleLabels_innerAsmLabel: +// ASM-NEXT: jmp .LnakedWithMultipleLabels_otherAsmLabel +// ASM-NEXT: #NO_APP +// ASM-NEXT: #APP +// ASM-NEXT: .LnakedWithMultipleLabels_otherAsmLabel: +// ASM-NEXT: jmp .LnakedWithMultipleLabels_dLabel +// ASM-NEXT: #NO_APP +// ASM-NEXT: #APP +// ASM-NEXT: .LnakedWithMultipleLabels_dLabel: +// ASM-NEXT: #NO_APP +// ASM-NEXT: #APP +// ASM-NEXT: jmp .LnakedWithMultipleLabels_innerAsmLabel +// ASM-NEXT: jmp .LnakedWithMultipleLabels_otherAsmLabel +// ASM-NEXT: retq + +extern(C) int nakedWithMultipleLabels() { + asm { + naked; + jmp innerAsmLabel; + innerAsmLabel: + jmp otherAsmLabel; + } + asm { + otherAsmLabel: + jmp dLabel; + } +dLabel: + asm { + jmp innerAsmLabel; + jmp otherAsmLabel; + ret; + } +} + +// Template function - should have comdat for deduplication +// ASM: .section .text._D16naked_asm_output__T13nakedTemplateVii42ZQvFZi,"axG",@progbits,_D16naked_asm_output__T13nakedTemplateVii42ZQvFZi,comdat +// ASM-LABEL: _D16naked_asm_output__T13nakedTemplateVii42ZQvFZi: +// ASM-NEXT: .cfi_startproc +// ASM-NEXT: #APP +// ASM-NEXT: movl $42, %eax +// ASM-NEXT: retq + +// Template function check - use IR-DAG to allow flexible ordering since +// the template may be emitted after its caller (instantiate1) +// IR-DAG: define weak_odr i32 @_D16naked_asm_output__T13nakedTemplateVii42ZQvFZi() #[[ATTRS]] comdat +int nakedTemplate(int N)() { + asm { + naked; + mov EAX, N; + ret; + } +} + +// Instantiate template +int instantiate1() { + return nakedTemplate!42(); +} + +// Verify naked and noinline attributes are present in attributes group +// IR: attributes #[[ATTRS]] = {{{.*}}naked{{.*}}noinline diff --git a/tests/linking/asm_labels_lto.d b/tests/linking/asm_labels_lto.d new file mode 100644 index 00000000000..968b2ca322b --- /dev/null +++ b/tests/linking/asm_labels_lto.d @@ -0,0 +1,71 @@ +// Tests that naked template functions work correctly with LTO linking. +// Previously, naked functions were emitted as module asm which caused +// duplicate symbol errors when the same template was instantiated in +// multiple modules and linked with LTO. +// +// The fix emits naked functions as LLVM IR functions with inline asm, +// allowing LLVM to properly deduplicate template instantiations. +// +// See: https://github.com/ldc-developers/ldc/issues/4294 + +// REQUIRES: LTO && host_X86 && target_X86 + +// RUN: split-file %s %t + +// Compile two modules (separately) 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.di +// Template with naked function containing inline asm labels. +// This mimics std.internal.math.biguintx86 which triggers issue #4294. +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)() { + asm { + naked; + xor EAX, EAX; + L1: + add EAX, N; + cmp EAX, 100; + jl L1; + ret; + } +} + +//--- 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; +}