Skip to content

Commit faa2b2c

Browse files
committed
[LLD][COFF] Implement support for hybrid IAT on ARM64X
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 12f780d commit faa2b2c

File tree

5 files changed

+639
-20
lines changed

5 files changed

+639
-20
lines changed

lld/COFF/Chunks.cpp

Lines changed: 23 additions & 4 deletions
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

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

719+
// Merge compatible EC and native import files in hybrid images.
720+
if (ctx.hybridSymtab) {
721+
for (std::vector<DefinedImportData *> &syms : v) {
722+
// At this point, symbols are sorted by base name, ensuring that
723+
// compatible import files, if present, are adjacent.
724+
std::vector<DefinedImportData *> hybridSyms;
725+
ImportFile *prev = nullptr;
726+
for (DefinedImportData *sym : syms) {
727+
ImportFile *file = sym->file;
728+
if (!prev || file->isEC() == prev->isEC() ||
729+
!file->isSameImport(prev)) {
730+
hybridSyms.push_back(sym);
731+
prev = file;
732+
continue;
733+
}
734+
735+
// The native variant exposes a subset of EC symbols and chunks. Use the
736+
// EC variant to represent both.
737+
if (file->isEC()) {
738+
hybridSyms.pop_back();
739+
hybridSyms.push_back(sym);
740+
}
741+
742+
prev->hybridFile = file;
743+
file->hybridFile = prev;
744+
prev = nullptr;
745+
}
746+
747+
// Sort symbols by type: native-only files first, followed by merged
748+
// hybrid files, and then EC-only files.
749+
llvm::stable_sort(hybridSyms,
750+
[](DefinedImportData *a, DefinedImportData *b) {
751+
if (a->file->hybridFile)
752+
return !b->file->hybridFile && b->file->isEC();
753+
return !a->file->isEC() && b->file->isEC();
754+
});
755+
syms = std::move(hybridSyms);
756+
}
757+
}
758+
719759
// Create .idata contents for each DLL.
720760
for (std::vector<DefinedImportData *> &syms : v) {
721761
// Create lookup and address tables. If they have external names,
722762
// we need to create hintName chunks to store the names.
723763
// If they don't (if they are import-by-ordinals), we store only
724764
// ordinal values to the table.
725765
size_t base = lookups.size();
766+
Chunk *lookupsTerminator = nullptr, *addressesTerminator = nullptr;
726767
for (DefinedImportData *s : syms) {
727768
uint16_t ord = s->getOrdinal();
769+
HintNameChunk *hintChunk = nullptr;
770+
Chunk *lookupsChunk, *addressesChunk;
771+
728772
if (s->getExternalName().empty()) {
729-
lookups.push_back(make<OrdinalOnlyChunk>(ctx, ord));
730-
addresses.push_back(make<OrdinalOnlyChunk>(ctx, ord));
773+
lookupsChunk = make<OrdinalOnlyChunk>(ctx, ord);
774+
addressesChunk = make<OrdinalOnlyChunk>(ctx, ord);
731775
} 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);
776+
hintChunk = make<HintNameChunk>(s->getExternalName(), ord);
777+
lookupsChunk = make<LookupChunk>(ctx, hintChunk);
778+
addressesChunk = make<LookupChunk>(ctx, hintChunk);
779+
hints.push_back(hintChunk);
736780
}
737781

738-
if (s->file->impECSym) {
782+
// Detect the first EC-only import in the hybrid IAT. Emit null chunks
783+
// and add an ARM64X relocation to replace it with the import for the EC
784+
// view. Additionally, use the original chunks as import terminators
785+
// and zero them with ARM64X relocations. Since these chunks appear
786+
// after the null terminator in the native view, they are always ignored
787+
// by the loader. However, MSVC emits them for some reason.
788+
if (ctx.hybridSymtab && !lookupsTerminator && s->file->isEC() &&
789+
!s->file->hybridFile) {
790+
lookupsTerminator = lookupsChunk;
791+
addressesTerminator = addressesChunk;
792+
lookupsChunk = make<NullChunk>(ctx);
793+
addressesChunk = make<NullChunk>(ctx);
794+
795+
Arm64XRelocVal relocVal = hintChunk;
796+
if (!hintChunk)
797+
relocVal = (1ULL << 63) | ord;
798+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE,
799+
sizeof(uint64_t), lookupsChunk, relocVal);
800+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE,
801+
sizeof(uint64_t), addressesChunk, relocVal);
802+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL,
803+
sizeof(uint64_t), lookupsTerminator);
804+
ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_ZEROFILL,
805+
sizeof(uint64_t), addressesTerminator);
806+
}
807+
808+
lookups.push_back(lookupsChunk);
809+
addresses.push_back(addressesChunk);
810+
811+
if (s->file->isEC()) {
739812
auto chunk = make<AuxImportChunk>(s->file);
740813
auxIat.push_back(chunk);
741814
s->file->impECSym->setLocation(chunk);
742815

743816
chunk = make<AuxImportChunk>(s->file);
744817
auxIatCopy.push_back(chunk);
745818
s->file->auxImpCopySym->setLocation(chunk);
819+
} else if (ctx.hybridSymtab) {
820+
// Fill the auxiliary IAT with null chunks for native-only imports.
821+
auxIat.push_back(make<NullChunk>(ctx));
822+
auxIatCopy.push_back(make<NullChunk>(ctx));
746823
}
747824
}
748825
// Terminate with null values.
749-
lookups.push_back(make<NullChunk>(ctx));
750-
addresses.push_back(make<NullChunk>(ctx));
751-
if (ctx.config.machine == ARM64EC) {
826+
lookups.push_back(lookupsTerminator ? lookupsTerminator
827+
: make<NullChunk>(ctx));
828+
addresses.push_back(addressesTerminator ? addressesTerminator
829+
: make<NullChunk>(ctx));
830+
if (ctx.symtabEC) {
752831
auxIat.push_back(make<NullChunk>(ctx));
753832
auxIatCopy.push_back(make<NullChunk>(ctx));
754833
}
755834

756-
for (int i = 0, e = syms.size(); i < e; ++i)
835+
for (int i = 0, e = syms.size(); i < e; ++i) {
757836
syms[i]->setLocation(addresses[base + i]);
837+
if (syms[i]->file->hybridFile)
838+
syms[i]->file->hybridFile->impSym->setLocation(addresses[base + i]);
839+
}
758840

759841
// Create the import table header.
760842
dllNames.push_back(make<StringChunk>(syms[0]->getDLLName()));
761843
auto *dir = make<ImportDirectoryChunk>(dllNames.back());
762844
dir->lookupTab = lookups[base];
763845
dir->addressTab = addresses[base];
764846
dirs.push_back(dir);
847+
848+
if (ctx.hybridSymtab) {
849+
// If native-only imports exist, emit ARM64X relocations, skipping them in
850+
// the EC view.
851+
uint32_t nativeOnly =
852+
llvm::find_if(syms,
853+
[](DefinedImportData *s) { return s->file->isEC(); }) -
854+
syms.begin();
855+
if (nativeOnly) {
856+
ctx.dynamicRelocs->add(
857+
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
858+
Arm64XRelocVal(
859+
dir, offsetof(ImportDirectoryTableEntry, ImportLookupTableRVA)),
860+
nativeOnly * sizeof(uint64_t));
861+
ctx.dynamicRelocs->add(
862+
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
863+
Arm64XRelocVal(dir, offsetof(ImportDirectoryTableEntry,
864+
ImportAddressTableRVA)),
865+
nativeOnly * sizeof(uint64_t));
866+
}
867+
}
765868
}
766869
// Add null terminator.
767870
dirs.push_back(make<NullChunk>(sizeof(ImportDirectoryTableEntry), 4));

lld/COFF/InputFiles.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,15 +1100,21 @@ void ObjFile::enqueuePdbFile(StringRef path, ObjFile *fromFile) {
11001100
}
11011101

11021102
ImportFile::ImportFile(COFFLinkerContext &ctx, MemoryBufferRef m)
1103-
: InputFile(ctx.symtab, ImportKind, m), live(!ctx.config.doGC) {}
1103+
: InputFile(ctx.getSymtab(getMachineType(m)), ImportKind, m),
1104+
live(!ctx.config.doGC) {}
11041105

1105-
MachineTypes ImportFile::getMachineType() const {
1106+
MachineTypes ImportFile::getMachineType(MemoryBufferRef m) {
11061107
uint16_t machine =
1107-
reinterpret_cast<const coff_import_header *>(mb.getBufferStart())
1108-
->Machine;
1108+
reinterpret_cast<const coff_import_header *>(m.getBufferStart())->Machine;
11091109
return MachineTypes(machine);
11101110
}
11111111

1112+
bool ImportFile::isSameImport(const ImportFile *other) const {
1113+
if (!externalName.empty())
1114+
return other->externalName == externalName;
1115+
return hdr->OrdinalHint == other->hdr->OrdinalHint;
1116+
}
1117+
11121118
ImportThunkChunk *ImportFile::makeImportThunk() {
11131119
switch (hdr->Machine) {
11141120
case AMD64:

lld/COFF/InputFiles.h

Lines changed: 5 additions & 1 deletion
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)