fix: improve propagating external functions info to kernel

When relocation points to symbol that is not defined within module mark
it as optional. When symbol is available at dynamic linking time then
info from the function is retrieved but when the symbol is not available
then ignore the dependency.

Any unresolved symbol needed for module linking is already handled
in a separate place.

Related-To: NEO-16243, NEO-16263, NEO-16262, NEO-16268
Signed-off-by: Mateusz Jablonski <mateusz.jablonski@intel.com>
This commit is contained in:
Mateusz Jablonski
2025-09-26 12:21:34 +00:00
committed by Compute-Runtime-Automation
parent a440a3e8ea
commit 15d0feeda8
8 changed files with 127 additions and 29 deletions

View File

@@ -37,6 +37,9 @@ uint32_t getExtFuncDependencies(const FuncNameToIdMapT &funcNameToId, const Func
auto funcDep = funcDependencies[i];
if (funcNameToId.count(funcDep->callerFuncName) == 0 ||
funcNameToId.count(funcDep->usedFuncName) == 0) {
if (funcDep->optional) {
continue;
}
return ERROR_EXTERNAL_FUNCTION_INFO_MISSING;
}
size_t callerId = funcNameToId.at(funcDep->callerFuncName);
@@ -75,6 +78,9 @@ uint32_t resolveExtFuncDependencies(const ExternalFunctionInfosT &externalFuncti
uint32_t resolveKernelDependencies(const ExternalFunctionInfosT &externalFunctionInfos, const FuncNameToIdMapT &funcNameToId, const KernelDependenciesT &kernelDependencies, const KernelDescriptorMapT &nameToKernelDescriptor) {
for (auto &kernelDep : kernelDependencies) {
if (funcNameToId.count(kernelDep->usedFuncName) == 0) {
if (kernelDep->optional) {
continue;
}
return ERROR_EXTERNAL_FUNCTION_INFO_MISSING;
} else if (nameToKernelDescriptor.count(kernelDep->kernelName) == 0) {
return ERROR_KERNEL_DESCRIPTOR_MISSING;

View File

@@ -35,11 +35,13 @@ struct ExternalFunctionInfo {
struct ExternalFunctionUsageKernel {
std::string usedFuncName;
std::string kernelName;
bool optional = false;
};
struct ExternalFunctionUsageExtFunc {
std::string usedFuncName;
std::string callerFuncName;
bool optional = false;
};
using ExternalFunctionInfosT = std::vector<ExternalFunctionInfo *>;

View File

@@ -232,6 +232,7 @@ bool LinkerInput::addSymbol(Elf::Elf<numBits> &elf, const SectionNameToSegmentId
auto symbolSectionName = elf.getSectionName(elfSymbol.shndx);
auto segment = getSegmentForSection(symbolSectionName);
if (segment == SegmentType::unknown) {
externalSymbols.push_back(symbolName);
return false;
}
@@ -298,6 +299,7 @@ void LinkerInput::decodeElfSymbolTableAndRelocations(Elf::Elf<numBits> &elf, con
void LinkerInput::parseRelocationForExtFuncUsage(const RelocationInfo &relocInfo, const std::string &kernelName) {
bool isExternalSymbol = false;
auto shouldIgnoreRelocation = [&](const RelocationInfo &relocInfo) {
if (relocInfo.symbolName.empty()) {
return true;
@@ -311,10 +313,8 @@ void LinkerInput::parseRelocationForExtFuncUsage(const RelocationInfo &relocInfo
return true;
}
for (auto specialRelocationName : {implicitArgsRelocationSymbolName, Linker::perThreadOff, Linker::subDeviceID}) {
if (relocInfo.symbolName == specialRelocationName) {
return true;
}
if (std::ranges::find(externalSymbols, relocInfo.symbolName) != externalSymbols.end()) {
isExternalSymbol = true;
}
return false;
};
@@ -328,10 +328,10 @@ void LinkerInput::parseRelocationForExtFuncUsage(const RelocationInfo &relocInfo
return relocInfo.offset >= symbol.offset && relocInfo.offset < symbol.offset + symbol.size;
});
if (callerIt != extFuncSymbols.end()) {
extFunDependencies.push_back({relocInfo.symbolName, callerIt->first});
extFunDependencies.push_back({relocInfo.symbolName, callerIt->first, isExternalSymbol});
}
} else {
kernelDependencies.push_back({relocInfo.symbolName, kernelName});
kernelDependencies.push_back({relocInfo.symbolName, kernelName, isExternalSymbol});
}
}

View File

@@ -183,6 +183,7 @@ struct LinkerInput : NEO::NonCopyableAndNonMovableClass {
Traits traits;
SymbolMap symbols;
std::vector<std::string> externalSymbols;
std::vector<std::pair<std::string, SymbolInfo>> extFuncSymbols;
Relocations dataRelocations;
RelocationsPerInstSegment textRelocations;

View File

@@ -24,6 +24,7 @@ struct WhiteBox<NEO::LinkerInput> : NEO::LinkerInput {
using BaseClass::dataRelocations;
using BaseClass::exportedFunctionsSegmentId;
using BaseClass::externalSymbols;
using BaseClass::extFuncSymbols;
using BaseClass::extFunDependencies;
using BaseClass::kernelDependencies;

View File

@@ -57,7 +57,7 @@ struct MockElf : public NEO::Elf::Elf<numBits> {
relocations.emplace_back(reloc);
}
void setupSecionNames(std::unordered_map<uint32_t, std::string> map) {
void setupSectionNames(std::unordered_map<uint32_t, std::string> map) {
sectionNames = map;
overrideSectionNames = true;
}

View File

@@ -97,10 +97,16 @@ struct ExternalFunctionsTests : public ::testing::Test {
nameToKernelDescriptor[kernelName] = kd.get();
}
void addFuncDependency(const std::string &calleeName, const std::string &callerName) {
funcDependenciesStorage.push_back({calleeName, callerName});
funcDependenciesStorage.push_back({calleeName, callerName, false});
}
void addKernelDependency(const std::string &calleeName, const std::string &kernelCallerName) {
kernelDependenciesStorage.push_back({calleeName, kernelCallerName});
kernelDependenciesStorage.push_back({calleeName, kernelCallerName, false});
}
void addOptionalFuncDependency(const std::string &calleeName, const std::string &callerName) {
funcDependenciesStorage.push_back({calleeName, callerName, true});
}
void addOptionalKernelDependency(const std::string &calleeName, const std::string &kernelCallerName) {
kernelDependenciesStorage.push_back({calleeName, kernelCallerName, true});
}
void clear() {
@@ -157,6 +163,27 @@ TEST_F(ExternalFunctionsTests, GivenMissingExtFuncInLookupMapWhenResolvingExtFun
EXPECT_EQ(ERROR_EXTERNAL_FUNCTION_INFO_MISSING, error);
}
TEST_F(ExternalFunctionsTests, GivenMissingOptionalExtFuncInLookupMapWhenResolvingExtFuncDependenciesThenReturnSuccess) {
addOptionalFuncDependency("fun1", "fun0");
set();
auto error = resolveExtFuncDependencies(extFuncInfo, funcNameToId, functionDependencies);
EXPECT_EQ(RESOLVE_SUCCESS, error);
clear();
addOptionalFuncDependency("fun1", "fun0");
addExternalFunction("fun1", {});
set();
error = resolveExtFuncDependencies(extFuncInfo, funcNameToId, functionDependencies);
EXPECT_EQ(RESOLVE_SUCCESS, error);
clear();
addOptionalFuncDependency("fun1", "fun0");
addExternalFunction("fun0", {});
set();
error = resolveExtFuncDependencies(extFuncInfo, funcNameToId, functionDependencies);
EXPECT_EQ(RESOLVE_SUCCESS, error);
}
TEST_F(ExternalFunctionsTests, GivenMissingExtFuncInLookupMapWhenResolvingKernelDependenciesThenReturnError) {
addKernel("kernel");
addKernelDependency("fun0", "kernel");
@@ -164,6 +191,13 @@ TEST_F(ExternalFunctionsTests, GivenMissingExtFuncInLookupMapWhenResolvingKernel
auto error = resolveKernelDependencies(extFuncInfo, funcNameToId, kernelDependencies, nameToKernelDescriptor);
EXPECT_EQ(ERROR_EXTERNAL_FUNCTION_INFO_MISSING, error);
}
TEST_F(ExternalFunctionsTests, GivenMissingOptionalExtFuncInLookupMapWhenResolvingKernelDependenciesThenReturnSuccess) {
addKernel("kernel");
addOptionalKernelDependency("fun0", "kernel");
set();
auto error = resolveKernelDependencies(extFuncInfo, funcNameToId, kernelDependencies, nameToKernelDescriptor);
EXPECT_EQ(RESOLVE_SUCCESS, error);
}
TEST_F(ExternalFunctionsTests, GivenMissingKernelInLookupMapWhenResolvingKernelDependenciesThenReturnError) {
addExternalFunction("fun0", {});
@@ -294,3 +328,28 @@ TEST_F(ExternalFunctionsTests, GivenValidFunctionAndKernelDependenciesWhenResolv
EXPECT_FALSE(nameToKernelDescriptor["kernel1"]->kernelAttributes.flags.hasIndirectCalls);
EXPECT_TRUE(nameToKernelDescriptor["kernel2"]->kernelAttributes.flags.hasIndirectCalls);
}
TEST_F(ExternalFunctionsTests, GivenValidFunctionAndKernelOptionalDependenciesWhenResolvingDependenciesThenSetAppropriateHasIndirectfCallsAndReturnSuccess) {
addKernel("kernel0");
addKernel("kernel1");
addKernel("kernel2");
addExternalFunction("fun0", {.hasIndirectCalls = false});
addExternalFunction("fun1", {.hasIndirectCalls = true});
addExternalFunction("fun2", {.hasIndirectCalls = false});
addOptionalFuncDependency("fun1", "fun0");
addOptionalKernelDependency("fun0", "kernel0");
addOptionalKernelDependency("fun2", "kernel1");
addOptionalKernelDependency("fun2", "kernel2");
set();
nameToKernelDescriptor["kernel2"]->kernelAttributes.flags.hasIndirectCalls = true;
auto error = resolveExternalDependencies(extFuncInfo, kernelDependencies, functionDependencies, nameToKernelDescriptor);
EXPECT_EQ(RESOLVE_SUCCESS, error);
EXPECT_TRUE(extFuncInfo[funcNameToId["fun0"]]->hasIndirectCalls);
EXPECT_TRUE(extFuncInfo[funcNameToId["fun1"]]->hasIndirectCalls);
EXPECT_FALSE(extFuncInfo[funcNameToId["fun2"]]->hasIndirectCalls);
EXPECT_TRUE(nameToKernelDescriptor["kernel0"]->kernelAttributes.flags.hasIndirectCalls);
EXPECT_FALSE(nameToKernelDescriptor["kernel1"]->kernelAttributes.flags.hasIndirectCalls);
EXPECT_TRUE(nameToKernelDescriptor["kernel2"]->kernelAttributes.flags.hasIndirectCalls);
}

View File

@@ -388,7 +388,7 @@ TEST(LinkerInputTests, GivenTwoGlobalSymbolsOfTypeFunctionEachPointingToDifferen
sectionNames[1] = ".data.const";
sectionNames[2] = ".text.hello";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0x1234000, 8, 0, Elf::STT_FUNC, Elf::STB_GLOBAL);
@@ -405,7 +405,7 @@ TEST(LinkerInputTests, GivenGlobalSymbolOfTypeObjectPointingToDataGlobalSectionW
std::unordered_map<uint32_t, std::string> sectionNames;
sectionNames[0] = ".text.abc";
sectionNames[1] = ".data.global";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0x20, 8, 1, Elf::STT_OBJECT, Elf::STB_GLOBAL);
@@ -428,7 +428,7 @@ TEST(LinkerInputTests, GivenGlobalSymbolOfTypeObjectPointingToDataConstSectionWh
std::unordered_map<uint32_t, std::string> sectionNames;
sectionNames[0] = ".text.abc";
sectionNames[1] = ".data.const";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0, 8, 1, Elf::STT_OBJECT, Elf::STB_GLOBAL);
@@ -452,7 +452,7 @@ TEST(LinkerInputTests, GivenGlobalSymbolOfTypeFuncPointingToFunctionsSectionWhen
std::unordered_map<uint32_t, std::string> sectionNames;
sectionNames[0] = ".text.abc";
sectionNames[1] = functionsSectionName.str();
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0, 32, 1, Elf::STT_FUNC, Elf::STB_GLOBAL);
@@ -479,7 +479,7 @@ TEST(LinkerInputTests, GivenGlobalSymbolOfTypeDifferentThantObjectOrFuncWhenDeco
std::unordered_map<uint32_t, std::string> sectionNames;
sectionNames[0] = ".text.abc";
sectionNames[1] = ".data.const";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0, 8, 0, Elf::STT_NOTYPE, Elf::STB_GLOBAL);
@@ -490,21 +490,24 @@ TEST(LinkerInputTests, GivenGlobalSymbolOfTypeDifferentThantObjectOrFuncWhenDeco
EXPECT_EQ(0U, linkerInput.getSymbols().size());
}
TEST(LinkerInputTests, GivenGlobalSymbolPointingToSectionDifferentThanInstructionsOrDataWhenDecodingElfThenItIsIgnored) {
TEST(LinkerInputTests, GivenGlobalSymbolPointingToSectionDifferentThanInstructionsOrDataWhenDecodingElfThenItIsIgnoredAndAddedToExternalSymbols) {
MockElf<NEO::Elf::EI_CLASS_64> elf64;
std::unordered_map<uint32_t, std::string> sectionNames;
sectionNames[0] = ""; // UNDEF section
sectionNames[1] = ".text.abc";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0, 8, 0, Elf::STT_OBJECT, Elf::STB_GLOBAL);
constexpr uint32_t externalSymbol = 3;
elf64.addSymbol(externalSymbol, 0, 8, 0, Elf::STT_OBJECT, Elf::STB_GLOBAL);
WhiteBox<NEO::LinkerInput> linkerInput;
NEO::LinkerInput::SectionNameToSegmentIdMap nameToKernelId = {{"abc", 0}};
NEO::LinkerInput linkerInput = {};
linkerInput.decodeElfSymbolTableAndRelocations(elf64, nameToKernelId);
EXPECT_TRUE(linkerInput.isValid());
EXPECT_EQ(0U, linkerInput.getSymbols().size());
EXPECT_EQ(1U, linkerInput.externalSymbols.size());
EXPECT_EQ(std::to_string(externalSymbol), linkerInput.externalSymbols[0]);
}
TEST(LInkerInputTests, GivenSymbolPointingToInstructionSegmentAndInvalidInstructionSectionNameMappingWhenDecodingElfThenLinkerInputIsInvalid) {
@@ -513,7 +516,7 @@ TEST(LInkerInputTests, GivenSymbolPointingToInstructionSegmentAndInvalidInstruct
std::unordered_map<uint32_t, std::string> sectionNames;
sectionNames[0] = ".text.abc";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.overrideSymbolName = true;
elf64.addSymbol(0, 0, 8, 0, Elf::STT_FUNC, Elf::STB_GLOBAL);
@@ -532,7 +535,7 @@ TEST(LinkerInputTests, GivenInstructionRelocationAndInvalidInstructionSectionNam
sectionNames[0] = ".text.abc";
sectionNames[1] = ".data.const";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.addReloc(64, 0, Zebin::Elf::R_ZE_SYM_ADDR, 0, 0, "0");
elf64.overrideSymbolName = true;
@@ -548,7 +551,7 @@ TEST(LinkerInputTests, GivenRelocationWithSymbolIdOutOfBoundsOfSymbolTableWhenDe
MockElf<NEO::Elf::EI_CLASS_64> elf64;
std::unordered_map<uint32_t, std::string> sectionNames = {{0, ".text.abc"}};
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.addReloc(64, 0, Zebin::Elf::R_ZE_SYM_ADDR, 0, 2, "symbol");
@@ -564,7 +567,7 @@ TEST(LinkerInputTests, GivenRelocationToSectionDifferentThanDataOrInstructionsWh
std::unordered_map<uint32_t, std::string> sectionNames = {{0, ".text.abc"},
{1, "unknown.section"}};
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.addReloc(64, 0, Zebin::Elf::R_ZE_SYM_ADDR, 1, 2, "symbol");
NEO::LinkerInput::SectionNameToSegmentIdMap nameToKernelId;
@@ -584,7 +587,7 @@ TEST(LinkerInputTests, GivenGlobalDataRelocationWithLocalSymbolPointingToConstDa
sectionNames[1] = ".data.const";
sectionNames[2] = ".data.global";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.addReloc(64, 10, Zebin::Elf::R_ZE_SYM_ADDR, 2, 0, "0");
elf64.overrideSymbolName = true;
@@ -617,7 +620,7 @@ TEST(LinkerInputTests, GivenInstructionRelocationWithLocalSymbolPointingToFuncti
sectionNames[0] = ".text.abc";
sectionNames[1] = functionsSectionName.str();
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
elf64.addReloc(64, 10, Zebin::Elf::R_ZE_SYM_ADDR, 0, 0, "0");
elf64.overrideSymbolName = true;
@@ -791,7 +794,7 @@ TEST(LinkerInputTests, GivenExternalFunctionsSymbolsUsedInKernelRelocationsWhenP
EXPECT_EQ(kernelName, mockLinkerInput.kernelDependencies[0].kernelName);
}
TEST(LinkerInputTests, GivenNonFunctionRelocationInKernelRelocationsWhenParsingRelocationsForExtFuncUsageForKernelThenDoNotAddKernelDependency) {
TEST(LinkerInputTests, GivenNonFunctionSymbolRelocationInKernelRelocationsWhenParsingRelocationsForExtFuncUsageForKernelThenDoNotAddKernelDependency) {
WhiteBox<NEO::LinkerInput> mockLinkerInput;
auto &symbols = mockLinkerInput.symbols;
@@ -806,11 +809,8 @@ TEST(LinkerInputTests, GivenNonFunctionRelocationInKernelRelocationsWhenParsingR
symbols.emplace("fun", symbol2);
for (auto nonFuncRelocationName : {
implicitArgsRelocationSymbolName,
std::string_view(".str"),
std::string_view("globalVar"),
Linker::perThreadOff,
Linker::subDeviceID,
std::string_view("")}) {
NEO::LinkerInput::RelocationInfo relocInfo{};
@@ -822,6 +822,35 @@ TEST(LinkerInputTests, GivenNonFunctionRelocationInKernelRelocationsWhenParsingR
EXPECT_EQ(0u, mockLinkerInput.kernelDependencies.size());
}
}
TEST(LinkerInputTests, GivenExternalSymbolRelocationInKernelRelocationsWhenParsingRelocationsForExtFuncUsageForKernelThenAddOptionalKernelDependency) {
WhiteBox<NEO::LinkerInput> mockLinkerInput;
auto &externalSymbols = mockLinkerInput.externalSymbols;
externalSymbols.push_back(std::string(implicitArgsRelocationSymbolName));
externalSymbols.push_back(std::string(Linker::perThreadOff));
externalSymbols.push_back(std::string(Linker::subDeviceID));
for (auto relocationName : {
implicitArgsRelocationSymbolName,
Linker::perThreadOff,
Linker::subDeviceID}) {
NEO::LinkerInput::RelocationInfo relocInfo{};
relocInfo.symbolName = relocationName;
std::string kernelName = "kernel";
mockLinkerInput.parseRelocationForExtFuncUsage(relocInfo, kernelName);
EXPECT_TRUE(mockLinkerInput.extFunDependencies.empty());
}
EXPECT_EQ(3u, mockLinkerInput.kernelDependencies.size());
for (auto &kernelDependency : mockLinkerInput.kernelDependencies) {
EXPECT_TRUE(kernelDependency.optional);
}
}
HWTEST_F(LinkerTests, givenEmptyLinkerInputThenLinkerOutputIsEmpty) {
NEO::LinkerInput linkerInput;
NEO::Linker linker(linkerInput);
@@ -2140,7 +2169,7 @@ TEST_F(LinkerTests, GivenDebugDataWhenApplyingDebugDataRelocationsThenRelocation
sectionNames[4] = ".debug_line";
sectionNames[5] = ".data.const";
elf64.setupSecionNames(std::move(sectionNames));
elf64.setupSectionNames(std::move(sectionNames));
NEO::Elf::Elf<NEO::Elf::EI_CLASS_64>::RelocationInfo reloc0 = {};
reloc0.offset = 64;