Skip to content

Commit fb01a28

Browse files
authored
[LLD][COFF] Implement support for hybrid IAT on ARM64X (llvm#124189)
In hybrid images, the PE header references a single IAT for both native and EC views, merging entries where possible. When merging isn't feasible, different imports are grouped together, and ARM64X relocations are emitted as needed.
1 parent 1c4341d commit fb01a28

File tree

5 files changed

+706
-20
lines changed

5 files changed

+706
-20
lines changed

lld/COFF/Chunks.cpp

+23-4
Original file line numberDiff line numberDiff line change
@@ -1172,11 +1172,12 @@ uint64_t Arm64XRelocVal::get() const {
11721172

11731173
size_t Arm64XDynamicRelocEntry::getSize() const {
11741174
switch (type) {
1175+
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL:
1176+
return sizeof(uint16_t); // Just a header.
11751177
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE:
11761178
return sizeof(uint16_t) + size; // A header and a payload.
11771179
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA:
1178-
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL:
1179-
llvm_unreachable("unsupported type");
1180+
return 2 * sizeof(uint16_t); // A header and a delta.
11801181
}
11811182
llvm_unreachable("invalid type");
11821183
}
@@ -1186,6 +1187,9 @@ void Arm64XDynamicRelocEntry::writeTo(uint8_t *buf) const {
11861187
*out = (offset.get() & 0xfff) | (type << 12);
11871188

11881189
switch (type) {
1190+
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL:
1191+
*out |= ((bit_width(size) - 1) << 14); // Encode the size.
1192+
break;
11891193
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE:
11901194
*out |= ((bit_width(size) - 1) << 14); // Encode the size.
11911195
switch (size) {
@@ -1203,8 +1207,23 @@ void Arm64XDynamicRelocEntry::writeTo(uint8_t *buf) const {
12031207
}
12041208
break;
12051209
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA:
1206-
case IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL:
1207-
llvm_unreachable("unsupported type");
1210+
int delta = value.get();
1211+
// Negative offsets use a sign bit in the header.
1212+
if (delta < 0) {
1213+
*out |= 1 << 14;
1214+
delta = -delta;
1215+
}
1216+
// Depending on the value, the delta is encoded with a shift of 2 or 3 bits.
1217+
if (delta & 7) {
1218+
assert(!(delta & 3));
1219+
delta >>= 2;
1220+
} else {
1221+
*out |= (1 << 15);
1222+
delta >>= 3;
1223+
}
1224+
out[1] = delta;
1225+
assert(!(delta & ~0xffff));
1226+
break;
12081227
}
12091228
}
12101229

lld/COFF/DLL.cpp

+135-11
Original file line numberDiff line numberDiff line change
@@ -716,52 +716,176 @@ class ExportOrdinalChunk : public NonSectionChunk {
716716
void IdataContents::create(COFFLinkerContext &ctx) {
717717
std::vector<std::vector<DefinedImportData *>> v = binImports(ctx, imports);
718718

719+
// In hybrid images, EC and native code are usually very similar,
720+
// resulting in a highly similar set of imported symbols. Consequently,
721+
// their import tables can be shared, with ARM64X relocations handling any
722+
// differences. Identify matching import files used by EC and native code, and
723+
// merge them into a single hybrid import entry.
724+
if (ctx.hybridSymtab) {
725+
for (std::vector<DefinedImportData *> &syms : v) {
726+
std::vector<DefinedImportData *> hybridSyms;
727+
ImportFile *prev = nullptr;
728+
for (DefinedImportData *sym : syms) {
729+
ImportFile *file = sym->file;
730+
// At this stage, symbols are sorted by base name, ensuring that
731+
// compatible import files, if present, are adjacent. Check if the
732+
// current symbol's file imports the same symbol as the previously added
733+
// one (if any and if it was not already merged). Additionally, verify
734+
// that one of them is native while the other is EC. In rare cases,
735+
// separate matching import entries may exist within the same namespace,
736+
// which cannot be merged.
737+
if (!prev || file->isEC() == prev->isEC() ||
738+
!file->isSameImport(prev)) {
739+
// We can't merge the import file, just add it to hybridSyms
740+
// and set prev to its file so that we can try to match the next
741+
// symbol.
742+
hybridSyms.push_back(sym);
743+
prev = file;
744+
continue;
745+
}
746+
747+
// A matching symbol may appear in syms in any order. The native variant
748+
// exposes a subset of EC symbols and chunks, so always use the EC
749+
// variant as the hybrid import file. If the native file was already
750+
// added, replace it with the EC symbol in hybridSyms. Otherwise, the EC
751+
// variant is already pushed, so we can simply merge it.
752+
if (file->isEC()) {
753+
hybridSyms.pop_back();
754+
hybridSyms.push_back(sym);
755+
}
756+
757+
// Merge import files by storing their hybrid form in the corresponding
758+
// file class.
759+
prev->hybridFile = file;
760+
file->hybridFile = prev;
761+
prev = nullptr; // A hybrid import file cannot be merged again.
762+
}
763+
764+
// Sort symbols by type: native-only files first, followed by merged
765+
// hybrid files, and then EC-only files.
766+
llvm::stable_sort(hybridSyms,
767+
[](DefinedImportData *a, DefinedImportData *b) {
768+
if (a->file->hybridFile)
769+
return !b->file->hybridFile && b->file->isEC();
770+
return !a->file->isEC() && b->file->isEC();
771+
});
772+
syms = std::move(hybridSyms);
773+
}
774+
}
775+
719776
// Create .idata contents for each DLL.
720777
for (std::vector<DefinedImportData *> &syms : v) {
721778
// Create lookup and address tables. If they have external names,
722779
// we need to create hintName chunks to store the names.
723780
// If they don't (if they are import-by-ordinals), we store only
724781
// ordinal values to the table.
725782
size_t base = lookups.size();
783+
Chunk *lookupsTerminator = nullptr, *addressesTerminator = nullptr;
726784
for (DefinedImportData *s : syms) {
727785
uint16_t ord = s->getOrdinal();
786+
HintNameChunk *hintChunk = nullptr;
787+
Chunk *lookupsChunk, *addressesChunk;
788+
728789
if (s->getExternalName().empty()) {
729-
lookups.push_back(make<OrdinalOnlyChunk>(ctx, ord));
730-
addresses.push_back(make<OrdinalOnlyChunk>(ctx, ord));
790+
lookupsChunk = make<OrdinalOnlyChunk>(ctx, ord);
791+
addressesChunk = make<OrdinalOnlyChunk>(ctx, ord);
731792
} else {
732-
auto *c = make<HintNameChunk>(s->getExternalName(), ord);
733-
lookups.push_back(make<LookupChunk>(ctx, c));
734-
addresses.push_back(make<LookupChunk>(ctx, c));
735-
hints.push_back(c);
793+
hintChunk = make<HintNameChunk>(s->getExternalName(), ord);
794+
lookupsChunk = make<LookupChunk>(ctx, hintChunk);
795+
addressesChunk = make<LookupChunk>(ctx, hintChunk);
796+
hints.push_back(hintChunk);
736797
}
737798

738-
if (s->file->impECSym) {
799+
// Detect the first EC-only import in the hybrid IAT. Emit null chunk
800+
// as a terminator for the native view, and add an ARM64X relocation to
801+
// replace it with the correct import for the EC view.
802+
//
803+
// Additionally, for MSVC compatibility, store the lookup and address
804+
// chunks and append them at the end of EC-only imports, where a null
805+
// terminator chunk would typically be placed. Since they appear after
806+
// the native terminator, they will be ignored in the native view.
807+
// In the EC view, they should act as terminators, so emit ZEROFILL
808+
// relocations overriding them.
809+
if (ctx.hybridSymtab && !lookupsTerminator && s->file->isEC() &&
810+
!s->file->hybridFile) {
811+
lookupsTerminator = lookupsChunk;
812+
addressesTerminator = addressesChunk;
813+
lookupsChunk = make<NullChunk>(ctx);
814+
addressesChunk = make<NullChunk>(ctx);
815+
816+
Arm64XRelocVal relocVal = hintChunk;
817+
if (!hintChunk)
818+
relocVal = (1ULL << 63) | ord;
819+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE,
820+
sizeof(uint64_t), lookupsChunk, relocVal);
821+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE,
822+
sizeof(uint64_t), addressesChunk, relocVal);
823+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL,
824+
sizeof(uint64_t), lookupsTerminator);
825+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL,
826+
sizeof(uint64_t), addressesTerminator);
827+
}
828+
829+
lookups.push_back(lookupsChunk);
830+
addresses.push_back(addressesChunk);
831+
832+
if (s->file->isEC()) {
739833
auto chunk = make<AuxImportChunk>(s->file);
740834
auxIat.push_back(chunk);
741835
s->file->impECSym->setLocation(chunk);
742836

743837
chunk = make<AuxImportChunk>(s->file);
744838
auxIatCopy.push_back(chunk);
745839
s->file->auxImpCopySym->setLocation(chunk);
840+
} else if (ctx.hybridSymtab) {
841+
// Fill the auxiliary IAT with null chunks for native-only imports.
842+
auxIat.push_back(make<NullChunk>(ctx));
843+
auxIatCopy.push_back(make<NullChunk>(ctx));
746844
}
747845
}
748846
// Terminate with null values.
749-
lookups.push_back(make<NullChunk>(ctx));
750-
addresses.push_back(make<NullChunk>(ctx));
751-
if (ctx.config.machine == ARM64EC) {
847+
lookups.push_back(lookupsTerminator ? lookupsTerminator
848+
: make<NullChunk>(ctx));
849+
addresses.push_back(addressesTerminator ? addressesTerminator
850+
: make<NullChunk>(ctx));
851+
if (ctx.symtabEC) {
752852
auxIat.push_back(make<NullChunk>(ctx));
753853
auxIatCopy.push_back(make<NullChunk>(ctx));
754854
}
755855

756-
for (int i = 0, e = syms.size(); i < e; ++i)
856+
for (int i = 0, e = syms.size(); i < e; ++i) {
757857
syms[i]->setLocation(addresses[base + i]);
858+
if (syms[i]->file->hybridFile)
859+
syms[i]->file->hybridFile->impSym->setLocation(addresses[base + i]);
860+
}
758861

759862
// Create the import table header.
760863
dllNames.push_back(make<StringChunk>(syms[0]->getDLLName()));
761864
auto *dir = make<ImportDirectoryChunk>(dllNames.back());
762865
dir->lookupTab = lookups[base];
763866
dir->addressTab = addresses[base];
764867
dirs.push_back(dir);
868+
869+
if (ctx.hybridSymtab) {
870+
// If native-only imports exist, they will appear as a prefix to all
871+
// imports. Emit ARM64X relocations to skip them in the EC view.
872+
uint32_t nativeOnly =
873+
llvm::find_if(syms,
874+
[](DefinedImportData *s) { return s->file->isEC(); }) -
875+
syms.begin();
876+
if (nativeOnly) {
877+
ctx.dynamicRelocs->add(
878+
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
879+
Arm64XRelocVal(
880+
dir, offsetof(ImportDirectoryTableEntry, ImportLookupTableRVA)),
881+
nativeOnly * sizeof(uint64_t));
882+
ctx.dynamicRelocs->add(
883+
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
884+
Arm64XRelocVal(dir, offsetof(ImportDirectoryTableEntry,
885+
ImportAddressTableRVA)),
886+
nativeOnly * sizeof(uint64_t));
887+
}
888+
}
765889
}
766890
// Add null terminator.
767891
dirs.push_back(make<NullChunk>(sizeof(ImportDirectoryTableEntry), 4));

lld/COFF/InputFiles.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -1129,15 +1129,21 @@ void ObjFile::enqueuePdbFile(StringRef path, ObjFile *fromFile) {
11291129
}
11301130

11311131
ImportFile::ImportFile(COFFLinkerContext &ctx, MemoryBufferRef m)
1132-
: InputFile(ctx.symtab, ImportKind, m), live(!ctx.config.doGC) {}
1132+
: InputFile(ctx.getSymtab(getMachineType(m)), ImportKind, m),
1133+
live(!ctx.config.doGC) {}
11331134

1134-
MachineTypes ImportFile::getMachineType() const {
1135+
MachineTypes ImportFile::getMachineType(MemoryBufferRef m) {
11351136
uint16_t machine =
1136-
reinterpret_cast<const coff_import_header *>(mb.getBufferStart())
1137-
->Machine;
1137+
reinterpret_cast<const coff_import_header *>(m.getBufferStart())->Machine;
11381138
return MachineTypes(machine);
11391139
}
11401140

1141+
bool ImportFile::isSameImport(const ImportFile *other) const {
1142+
if (!externalName.empty())
1143+
return other->externalName == externalName;
1144+
return hdr->OrdinalHint == other->hdr->OrdinalHint;
1145+
}
1146+
11411147
ImportThunkChunk *ImportFile::makeImportThunk() {
11421148
switch (hdr->Machine) {
11431149
case AMD64:

lld/COFF/InputFiles.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,15 @@ class ImportFile : public InputFile {
351351
explicit ImportFile(COFFLinkerContext &ctx, MemoryBufferRef m);
352352

353353
static bool classof(const InputFile *f) { return f->kind() == ImportKind; }
354-
MachineTypes getMachineType() const override;
354+
MachineTypes getMachineType() const override { return getMachineType(mb); }
355+
static MachineTypes getMachineType(MemoryBufferRef m);
356+
bool isSameImport(const ImportFile *other) const;
357+
bool isEC() const { return impECSym != nullptr; }
355358

356359
DefinedImportData *impSym = nullptr;
357360
Defined *thunkSym = nullptr;
358361
ImportThunkChunkARM64EC *impchkThunk = nullptr;
362+
ImportFile *hybridFile = nullptr;
359363
std::string dllName;
360364

361365
private:

0 commit comments

Comments
 (0)