diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index 4ba5d80b581ef..39fc25047f3b1 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -875,6 +875,19 @@ void RangeExtensionThunkARM64::writeTo(uint8_t *buf) const { applyArm64Imm(buf + 4, target->getRVA() & 0xfff, 0); } +void SameAddressThunkARM64EC::setDynamicRelocs(COFFLinkerContext &ctx) const { + // Add ARM64X relocations replacing adrp/add instructions with a version using + // the hybrid target. + RangeExtensionThunkARM64 hybridView(ARM64EC, hybridTarget); + uint8_t buf[sizeof(arm64Thunk)]; + hybridView.setRVA(rva); + hybridView.writeTo(buf); + uint32_t addrp = *reinterpret_cast(buf); + uint32_t add = *reinterpret_cast(buf + sizeof(uint32_t)); + ctx.dynamicRelocs->set(this, addrp); + ctx.dynamicRelocs->set(Arm64XRelocVal(this, sizeof(uint32_t)), add); +} + LocalImportChunk::LocalImportChunk(COFFLinkerContext &c, Defined *s) : sym(s), ctx(c) { setAlignment(ctx.config.wordsize); @@ -1258,7 +1271,8 @@ void DynamicRelocsChunk::finalize() { } // Set the reloc value. The reloc entry must be allocated beforehand. -void DynamicRelocsChunk::set(uint32_t rva, Arm64XRelocVal value) { +void DynamicRelocsChunk::set(Arm64XRelocVal offset, Arm64XRelocVal value) { + uint32_t rva = offset.get(); auto entry = llvm::find_if(arm64xRelocs, [rva](const Arm64XDynamicRelocEntry &e) { return e.offset.get() == rva; diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index d03a64cc6b812..6d88f5ec73776 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -193,6 +193,8 @@ class NonSectionChunk : public Chunk { // allowed ranges. Return the additional space required for the extension. virtual uint32_t extendRanges() { return 0; }; + virtual Defined *getEntryThunk() const { return nullptr; }; + static bool classof(const Chunk *c) { return c->kind() >= OtherKind; } protected: @@ -633,7 +635,7 @@ class ImportThunkChunkARM64EC : public ImportThunkChunk { bool verifyRanges() override; uint32_t extendRanges() override; - Defined *exitThunk; + Defined *exitThunk = nullptr; Defined *sym = nullptr; bool extended = false; @@ -675,6 +677,26 @@ class RangeExtensionThunkARM64 : public NonSectionCodeChunk { MachineTypes machine; }; +// A chunk used to guarantee the same address for a function in both views of +// a hybrid image. Similar to RangeExtensionThunkARM64 chunks, it calls the +// target symbol using a BR instruction. It also contains an entry thunk for EC +// compatibility and additional ARM64X relocations that swap targets between +// views. +class SameAddressThunkARM64EC : public RangeExtensionThunkARM64 { +public: + explicit SameAddressThunkARM64EC(Defined *t, Defined *hybridTarget, + Defined *entryThunk) + : RangeExtensionThunkARM64(ARM64EC, t), hybridTarget(hybridTarget), + entryThunk(entryThunk) {} + + Defined *getEntryThunk() const override { return entryThunk; } + void setDynamicRelocs(COFFLinkerContext &ctx) const; + +private: + Defined *hybridTarget; + Defined *entryThunk; +}; + // Windows-specific. // See comments for DefinedLocalImport class. class LocalImportChunk : public NonSectionChunk { @@ -843,13 +865,13 @@ class Arm64XRelocVal { public: Arm64XRelocVal(uint64_t value = 0) : value(value) {} Arm64XRelocVal(Defined *sym, int32_t offset = 0) : sym(sym), value(offset) {} - Arm64XRelocVal(Chunk *chunk, int32_t offset = 0) + Arm64XRelocVal(const Chunk *chunk, int32_t offset = 0) : chunk(chunk), value(offset) {} uint64_t get() const; private: Defined *sym = nullptr; - Chunk *chunk = nullptr; + const Chunk *chunk = nullptr; uint64_t value; }; @@ -884,7 +906,7 @@ class DynamicRelocsChunk : public NonSectionChunk { arm64xRelocs.emplace_back(type, size, offset, value); } - void set(uint32_t rva, Arm64XRelocVal value); + void set(Arm64XRelocVal offset, Arm64XRelocVal value); private: std::vector arm64xRelocs; @@ -940,6 +962,8 @@ inline bool Chunk::isHotPatchable() const { inline Defined *Chunk::getEntryThunk() const { if (auto *c = dyn_cast(this)) return c->entryThunk; + if (auto *c = dyn_cast(this)) + return c->getEntryThunk(); return nullptr; } diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h index 91b6e632fa7ed..a03bb57641670 100644 --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -223,6 +223,9 @@ struct Configuration { StringRef manifestUIAccess = "'false'"; StringRef manifestFile; + // used for /arm64xsameaddress + std::vector> sameAddresses; + // used for /dwodir StringRef dwoDir; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 192a998229e92..7580b469e4f87 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -500,7 +500,9 @@ void LinkerDriver::parseDirectives(InputFile *file) { file->symtab.parseAlternateName(arg->getValue()); break; case OPT_arm64xsameaddress: - if (!file->symtab.isEC()) + if (file->symtab.isEC()) + parseSameAddress(arg->getValue()); + else Warn(ctx) << arg->getSpelling() << " is not allowed in non-ARM64EC files (" << toString(file) << ")"; @@ -2295,6 +2297,13 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { args.filtered(OPT_dependentloadflag, OPT_dependentloadflag_opt)) parseDependentLoadFlags(arg); + for (auto *arg : args.filtered(OPT_arm64xsameaddress)) { + if (ctx.hybridSymtab) + parseSameAddress(arg->getValue()); + else + Warn(ctx) << arg->getSpelling() << " is allowed only on EC targets"; + } + if (tar) { llvm::TimeTraceScope timeScope("Reproducer: response file"); tar->append( @@ -2668,12 +2677,46 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { createECExportThunks(); // Resolve remaining undefined symbols and warn about imported locals. + std::vector aliases; ctx.forEachSymtab( - [&](SymbolTable &symtab) { symtab.resolveRemainingUndefines(); }); + [&](SymbolTable &symtab) { symtab.resolveRemainingUndefines(aliases); }); if (errorCount()) return; + ctx.forEachActiveSymtab([](SymbolTable &symtab) { + symtab.initializeECThunks(); + symtab.initializeLoadConfig(); + }); + + // Identify unreferenced COMDAT sections. + if (config->doGC) { + if (config->mingw) { + // markLive doesn't traverse .eh_frame, but the personality function is + // only reached that way. The proper solution would be to parse and + // traverse the .eh_frame section, like the ELF linker does. + // For now, just manually try to retain the known possible personality + // functions. This doesn't bring in more object files, but only marks + // functions that already have been included to be retained. + ctx.forEachSymtab([&](SymbolTable &symtab) { + for (const char *n : {"__gxx_personality_v0", "__gcc_personality_v0", + "rust_eh_personality"}) { + Defined *d = dyn_cast_or_null(symtab.findUnderscore(n)); + if (d && !d->isGCRoot) { + d->isGCRoot = true; + config->gcroot.push_back(d); + } + } + }); + } + + markLive(ctx); + } + + ctx.symtab.initializeSameAddressThunks(); + for (auto alias : aliases) + alias->resolveWeakAlias(); + if (config->mingw) { // Make sure the crtend.o object is the last object file. This object // file can contain terminating section chunks that need to be placed @@ -2765,35 +2808,6 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { if (auto *arg = args.getLastArg(OPT_print_symbol_order)) config->printSymbolOrder = arg->getValue(); - if (ctx.symtab.isEC()) - ctx.symtab.initializeECThunks(); - ctx.forEachActiveSymtab( - [](SymbolTable &symtab) { symtab.initializeLoadConfig(); }); - - // Identify unreferenced COMDAT sections. - if (config->doGC) { - if (config->mingw) { - // markLive doesn't traverse .eh_frame, but the personality function is - // only reached that way. The proper solution would be to parse and - // traverse the .eh_frame section, like the ELF linker does. - // For now, just manually try to retain the known possible personality - // functions. This doesn't bring in more object files, but only marks - // functions that already have been included to be retained. - ctx.forEachSymtab([&](SymbolTable &symtab) { - for (const char *n : {"__gxx_personality_v0", "__gcc_personality_v0", - "rust_eh_personality"}) { - Defined *d = dyn_cast_or_null(symtab.findUnderscore(n)); - if (d && !d->isGCRoot) { - d->isGCRoot = true; - config->gcroot.push_back(d); - } - } - }); - } - - markLive(ctx); - } - // Needs to happen after the last call to addFile(). convertResources(); diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 5a9bd5c6d9682..b500ac8bba569 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -214,6 +214,8 @@ class LinkerDriver { void parsePDBPageSize(StringRef); void parseSection(StringRef); + void parseSameAddress(StringRef); + // Parses a MS-DOS stub file void parseDosStub(StringRef path); diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index d8b41c7f45400..dc4039f116f25 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -328,6 +328,22 @@ void LinkerDriver::parseSwaprun(StringRef arg) { } while (!arg.empty()); } +void LinkerDriver::parseSameAddress(StringRef arg) { + auto mangledName = getArm64ECMangledFunctionName(arg); + Symbol *sym = ctx.symtab.addUndefined(mangledName ? *mangledName : arg); + + // MSVC appears to generate thunks even for non-hybrid ARM64EC images. + // As a side effect, the native symbol is pulled in. Since this is used + // in the CRT for thread-local constructors, it results in the image + // containing unnecessary native code. As these thunks don't appear to + // be useful, we limit this behavior to actual hybrid targets. This may + // change if compatibility becomes necessary. + if (ctx.config.machine != ARM64X) + return; + Symbol *nativeSym = ctx.hybridSymtab->addUndefined(arg); + ctx.config.sameAddresses.emplace_back(sym, nativeSym); +} + // An RAII temporary file class that automatically removes a temporary file. namespace { class TemporaryFile { diff --git a/lld/COFF/MarkLive.cpp b/lld/COFF/MarkLive.cpp index f40810c6805aa..78f5030e8fc2b 100644 --- a/lld/COFF/MarkLive.cpp +++ b/lld/COFF/MarkLive.cpp @@ -49,7 +49,10 @@ void markLive(COFFLinkerContext &ctx) { addSym(file->impchkThunk->exitThunk); }; - addSym = [&](Symbol *b) { + addSym = [&](Symbol *s) { + Defined *b = s->getDefined(); + if (!b) + return; if (auto *sym = dyn_cast(b)) { enqueue(sym->getChunk()); } else if (auto *sym = dyn_cast(b)) { diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td index 0d66b49a4fdb8..2c393cc94b5e3 100644 --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -31,6 +31,9 @@ multiclass B_priv { def align : P<"align", "Section alignment">; def aligncomm : P<"aligncomm", "Set common symbol alignment">; def alternatename : P<"alternatename", "Define weak alias">; +def arm64xsameaddress + : P<"arm64xsameaddress", "Generate a thunk for the symbol with the same " + "address in both native and EC views on ARM64X.">; def base : P<"base", "Base address of the program">; def color_diagnostics: Flag<["--"], "color-diagnostics">, HelpText<"Alias for --color-diagnostics=always">; @@ -373,4 +376,3 @@ def tlbid : P_priv<"tlbid">; def tlbout : P_priv<"tlbout">; def verbose_all : P_priv<"verbose">; def guardsym : P_priv<"guardsym">; -def arm64xsameaddress : P_priv<"arm64xsameaddress">; diff --git a/lld/COFF/SymbolTable.cpp b/lld/COFF/SymbolTable.cpp index 189e75dfc3ff5..de04cdff6483d 100644 --- a/lld/COFF/SymbolTable.cpp +++ b/lld/COFF/SymbolTable.cpp @@ -452,7 +452,7 @@ void SymbolTable::reportUnresolvable() { reportProblemSymbols(undefs, /*localImports=*/nullptr, true); } -void SymbolTable::resolveRemainingUndefines() { +void SymbolTable::resolveRemainingUndefines(std::vector &aliases) { llvm::TimeTraceScope timeScope("Resolve remaining undefined symbols"); SmallPtrSet undefs; DenseMap localImports; @@ -468,8 +468,10 @@ void SymbolTable::resolveRemainingUndefines() { StringRef name = undef->getName(); // A weak alias may have been resolved, so check for that. - if (undef->resolveWeakAlias()) + if (undef->getWeakAlias()) { + aliases.push_back(undef); continue; + } // If we can resolve a symbol by removing __imp_ prefix, do that. // This odd rule is for compatibility with MSVC linker. @@ -620,10 +622,10 @@ void SymbolTable::initializeECThunks() { return; for (auto it : entryThunks) { - auto *to = dyn_cast(it.second); + Defined *to = it.second->getDefined(); if (!to) continue; - auto *from = dyn_cast(it.first); + auto *from = dyn_cast_or_null(it.first->getDefined()); // We need to be able to add padding to the function and fill it with an // offset to its entry thunks. To ensure that padding the function is // feasible, functions are required to be COMDAT symbols with no offset. @@ -642,7 +644,8 @@ void SymbolTable::initializeECThunks() { Symbol *sym = exitThunks.lookup(file->thunkSym); if (!sym) sym = exitThunks.lookup(file->impECSym); - file->impchkThunk->exitThunk = dyn_cast_or_null(sym); + if (sym) + file->impchkThunk->exitThunk = sym->getDefined(); } // On ARM64EC, the __imp_ symbol references the auxiliary IAT, while the @@ -659,6 +662,35 @@ void SymbolTable::initializeECThunks() { }); } +void SymbolTable::initializeSameAddressThunks() { + for (auto iter : ctx.config.sameAddresses) { + auto sym = dyn_cast_or_null(iter.first->getDefined()); + if (!sym || !sym->isLive()) + continue; + auto nativeSym = + dyn_cast_or_null(iter.second->getDefined()); + if (!nativeSym || !nativeSym->isLive()) + continue; + Defined *entryThunk = sym->getChunk()->getEntryThunk(); + if (!entryThunk) + continue; + + // Replace symbols with symbols referencing the thunk. Store the original + // symbol as equivalent DefinedSynthetic instances for use in the thunk + // itself. + auto symClone = make(sym->getName(), sym->getChunk(), + sym->getValue()); + auto nativeSymClone = make( + nativeSym->getName(), nativeSym->getChunk(), nativeSym->getValue()); + SameAddressThunkARM64EC *thunk = + make(nativeSymClone, symClone, entryThunk); + sameAddressThunks.push_back(thunk); + + replaceSymbol(sym, sym->getName(), thunk); + replaceSymbol(nativeSym, nativeSym->getName(), thunk); + } +} + Symbol *SymbolTable::addUndefined(StringRef name, InputFile *f, bool overrideLazy) { auto [s, wasInserted] = insert(name, f); diff --git a/lld/COFF/SymbolTable.h b/lld/COFF/SymbolTable.h index 7eb067640dc85..aadd366c7d39f 100644 --- a/lld/COFF/SymbolTable.h +++ b/lld/COFF/SymbolTable.h @@ -31,6 +31,7 @@ class DefinedAbsolute; class DefinedRegular; class ImportThunkChunk; class LazyArchive; +class SameAddressThunkARM64EC; class SectionChunk; class Symbol; @@ -67,7 +68,7 @@ class SymbolTable { // Try to resolve any undefined symbols and update the symbol table // accordingly, then print an error message for any remaining undefined // symbols and warn about imported local symbols. - void resolveRemainingUndefines(); + void resolveRemainingUndefines(std::vector &aliases); // Try to resolve undefined symbols with alternate names. void resolveAlternateNames(); @@ -140,6 +141,7 @@ class SymbolTable { void addEntryThunk(Symbol *from, Symbol *to); void addExitThunk(Symbol *from, Symbol *to); void initializeECThunks(); + void initializeSameAddressThunks(); void reportDuplicate(Symbol *existing, InputFile *newFile, SectionChunk *newSc = nullptr, @@ -159,6 +161,8 @@ class SymbolTable { // A list of EC EXP+ symbols. std::vector expSymbols; + std::vector sameAddressThunks; + // A list of DLL exports. std::vector exports; llvm::DenseSet directivesExports; diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index 62019f19292a1..21ab9d17a26f9 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -314,6 +314,7 @@ class Writer { uint32_t dataDirOffset64; OutputSection *textSec; + OutputSection *wowthkSec; OutputSection *hexpthkSec; OutputSection *bssSec; OutputSection *rdataSec; @@ -1076,8 +1077,10 @@ void Writer::createSections() { // Try to match the section order used by link.exe. textSec = createSection(".text", code | r | x); - if (isArm64EC(ctx.config.machine)) + if (isArm64EC(ctx.config.machine)) { + wowthkSec = createSection(".wowthk", code | r | x); hexpthkSec = createSection(".hexpthk", code | r | x); + } bssSec = createSection(".bss", bss | r | w); rdataSec = createSection(".rdata", data | r); buildidSec = createSection(".buildid", data | r); @@ -1129,6 +1132,9 @@ void Writer::createSections() { if (hasIdata) locateImportTables(); + for (auto thunk : ctx.symtab.sameAddressThunks) + wowthkSec->addChunk(thunk); + // Then create an OutputSection for each section. // '$' and all following characters in input section names are // discarded when determining output section. So, .text$foo @@ -2310,6 +2316,14 @@ void Writer::createECChunks() { ctx.symtab.findUnderscore("__arm64x_redirection_metadata"); replaceSymbol(entryPointsSym, entryPointsSym->getName(), entryPoints); + + for (auto thunk : ctx.symtab.sameAddressThunks) { + // Relocation values are set later in setECSymbols. + ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE, sizeof(uint32_t), + thunk); + ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE, sizeof(uint32_t), + Arm64XRelocVal(thunk, sizeof(uint32_t))); + } } // MinGW specific. Gather all relocations that are imported from a DLL even @@ -2519,6 +2533,9 @@ void Writer::setECSymbols() { chpeSym->getRVA() + offsetof(chpe_metadata, ExtraRFETableSize), pdata.last->getRVA() + pdata.last->getSize() - pdata.first->getRVA()); } + + for (SameAddressThunkARM64EC *thunk : ctx.symtab.sameAddressThunks) + thunk->setDynamicRelocs(ctx); } // Write section contents to a mmap'ed file. diff --git a/lld/test/COFF/arm64x-sameaddress.test b/lld/test/COFF/arm64x-sameaddress.test index c69be9d268c3b..819d19b10065f 100644 --- a/lld/test/COFF/arm64x-sameaddress.test +++ b/lld/test/COFF/arm64x-sameaddress.test @@ -3,16 +3,103 @@ RUN: split-file %s %t.dir && cd %t.dir RUN: llvm-mc -filetype=obj -triple=arm64ec-windows func-arm64ec.s -o func-arm64ec.obj RUN: llvm-mc -filetype=obj -triple=aarch64-windows func-arm64.s -o func-arm64.obj +RUN: llvm-mc -filetype=obj -triple=arm64ec-windows ref-arm64ec.s -o ref-arm64ec.obj +RUN: llvm-mc -filetype=obj -triple=aarch64-windows ref-arm64.s -o ref-arm64.obj RUN: llvm-mc -filetype=obj -triple=arm64ec-windows drectve.s -o drectve.obj RUN: llvm-mc -filetype=obj -triple=aarch64-windows drectve.s -o drectve-arm64.obj RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj RUN: llvm-mc -filetype=obj -triple=aarch64-windows %S/Inputs/loadconfig-arm64.s -o loadconfig-arm64.obj RUN: lld-link -machine:arm64x -dll -noentry -out:out.dll loadconfig-arm64.obj loadconfig-arm64ec.obj \ -RUN: func-arm64.obj func-arm64ec.obj drectve.obj +RUN: func-arm64.obj func-arm64ec.obj ref-arm64.obj ref-arm64ec.obj drectve.obj + +RUN: llvm-objdump -d out.dll | FileCheck --check-prefix=DISASM %s +DISASM: 000000180001000 <.text>: +DISASM-NEXT: 180001000: d2800020 mov x0, #0x1 // =1 +DISASM-NEXT: 180001004: d65f03c0 ret +DISASM-NEXT: ... +DISASM-NEXT: 180002000: 00000019 udf #0x19 +DISASM-NEXT: 180002004: d2800040 mov x0, #0x2 // =2 +DISASM-NEXT: 180002008: d65f03c0 ret +DISASM-NEXT: 18000200c: 0000000d udf #0xd +DISASM-NEXT: 180002010: f0fffff0 adrp x16, 0x180001000 <.text> +DISASM-NEXT: 180002014: 91000210 add x16, x16, #0x0 +DISASM-NEXT: 180002018: d61f0200 br x16 +DISASM-NEXT: 18000201c: d2800060 mov x0, #0x3 // =3 +DISASM-NEXT: 180002020: d65f03c0 ret + +RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s +TESTSEC: 10200000 10200000 10200000 + +RUN: llvm-readobj --coff-load-config out.dll | FileCheck --check-prefix=DYNRELOCS %s +DYNRELOCS: DynamicRelocations [ +DYNRELOCS-NEXT: Version: 0x1 +DYNRELOCS-NEXT: Arm64X [ +DYNRELOCS-NEXT: Entry [ +DYNRELOCS-NEXT: RVA: 0x7C +DYNRELOCS-NEXT: Type: VALUE +DYNRELOCS-NEXT: Size: 0x2 +DYNRELOCS-NEXT: Value: 0x8664 +DYNRELOCS-NEXT: ] +DYNRELOCS-NEXT: Entry [ +DYNRELOCS-NEXT: RVA: 0x150 +DYNRELOCS-NEXT: Type: VALUE +DYNRELOCS-NEXT: Size: 0x4 +DYNRELOCS-NEXT: Value: 0x3150 +DYNRELOCS-NEXT: ] +DYNRELOCS-NEXT: Entry [ +DYNRELOCS-NEXT: RVA: 0x154 +DYNRELOCS-NEXT: Type: VALUE +DYNRELOCS-NEXT: Size: 0x4 +DYNRELOCS-NEXT: Value: 0x140 +DYNRELOCS-NEXT: ] +DYNRELOCS-NEXT: Entry [ +DYNRELOCS-NEXT: RVA: 0x2010 +DYNRELOCS-NEXT: Type: VALUE +DYNRELOCS-NEXT: Size: 0x4 +DYNRELOCS-NEXT: Value: 0x90000010 +DYNRELOCS-NEXT: ] +DYNRELOCS-NEXT: Entry [ +DYNRELOCS-NEXT: RVA: 0x2014 +DYNRELOCS-NEXT: Type: VALUE +DYNRELOCS-NEXT: Size: 0x4 +DYNRELOCS-NEXT: Value: 0x91001210 +DYNRELOCS-NEXT: ] +DYNRELOCS-NEXT: ] +DYNRELOCS-NEXT: ] RUN: lld-link -machine:arm64x -dll -noentry -out:out-cmd.dll loadconfig-arm64.obj loadconfig-arm64ec.obj \ -RUN: func-arm64.obj func-arm64ec.obj -arm64xsameaddress:func +RUN: func-arm64.obj func-arm64ec.obj ref-arm64.obj ref-arm64ec.obj -arm64xsameaddress:func +RUN: llvm-objdump -d out-cmd.dll | FileCheck --check-prefix=DISASM %s +RUN: llvm-readobj --hex-dump=.test out-cmd.dll | FileCheck --check-prefix=TESTSEC %s +RUN: llvm-readobj --coff-load-config out-cmd.dll | FileCheck --check-prefix=DYNRELOCS %s + +RUN: lld-link -machine:arm64x -dll -noentry -out:out-both.dll loadconfig-arm64.obj loadconfig-arm64ec.obj \ +RUN: func-arm64.obj func-arm64ec.obj ref-arm64.obj ref-arm64ec.obj drectve.obj -arm64xsameaddress:func +RUN: llvm-objdump -d out-both.dll | FileCheck --check-prefix=DISASM %s +RUN: llvm-readobj --hex-dump=.test out-both.dll | FileCheck --check-prefix=TESTSEC %s +RUN: llvm-readobj --coff-load-config out-both.dll | FileCheck --check-prefix=DYNRELOCS %s + +Check that if any of the sameaddress symbols is not alive, the thunk is not generated. + +RUN: lld-link -machine:arm64x -dll -noentry -out:out-live1.dll loadconfig-arm64.obj loadconfig-arm64ec.obj \ +RUN: func-arm64.obj func-arm64ec.obj ref-arm64ec.obj drectve.obj +RUN: llvm-objdump -d out-live1.dll | FileCheck --check-prefix=DISASM-LIVE1 %s +DISASM-LIVE1: 0000000180001000 <.text>: +DISASM-LIVE1-NEXT: 180001000: 00000009 udf #0x9 +DISASM-LIVE1-NEXT: 180001004: d2800040 mov x0, #0x2 // =2 +DISASM-LIVE1-NEXT: 180001008: d65f03c0 ret +DISASM-LIVE1-NEXT: 18000100c: d2800060 mov x0, #0x3 // =3 +DISASM-LIVE1-NEXT: 180001010: d65f03c0 ret +DISASM-LIVE1-NOT: br + +RUN: lld-link -machine:arm64x -dll -noentry -out:out-live2.dll loadconfig-arm64.obj loadconfig-arm64ec.obj \ +RUN: func-arm64.obj func-arm64ec.obj ref-arm64.obj drectve.obj +RUN: llvm-objdump -d out-live2.dll | FileCheck --check-prefix=DISASM-LIVE2 %s +DISASM-LIVE2: 0000000180001000 <.text>: +DISASM-LIVE2-NEXT: 180001000: d2800020 mov x0, #0x1 // =1 +DISASM-LIVE2-NEXT: 180001004: d65f03c0 ret +DISASM-LIVE2-NOT: br RUN: lld-link -machine:arm64ec -dll -noentry -out:out-ec.dll loadconfig-arm64ec.obj func-arm64ec.obj drectve.obj @@ -20,6 +107,10 @@ RUN: lld-link -machine:arm64x -dll -noentry -out:out-warn.dll loadconfig-arm64.o RUN: func-arm64.obj func-arm64ec.obj drectve-arm64.obj 2>&1 | FileCheck --check-prefix=WARN %s WARN: lld-link: warning: -arm64xsameaddress: is not allowed in non-ARM64EC files (drectve-arm64.obj) +RUN: lld-link -machine:arm64 -dll -noentry -out:out-warn2.dll loadconfig-arm64.obj \ +RUN: func-arm64.obj -arm64xsameaddress:func 2>&1 | FileCheck --check-prefix=WARN2 %s +WARN2: lld-link: warning: -arm64xsameaddress: is allowed only on EC targets + #--- func-arm64.s .section .text,"xr",discard,func .globl func @@ -27,6 +118,10 @@ func: mov x0, #1 ret +#--- ref-arm64.s + .section .test,"dr" + .rva func + #--- func-arm64ec.s .section .text,"xr",discard,"#func" .globl "#func" @@ -43,14 +138,16 @@ entry_thunk: mov x0, #3 ret - .section .test,"dr" - .rva func - .section .hybmp$x,"yi" .symidx "#func" .symidx entry_thunk .word 1 +#--- ref-arm64ec.s + .section .test,"dr" + .rva func + .rva "#func" + #--- drectve.s .section .drectve, "yn" .ascii " -arm64xsameaddress:func"