Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -6459,6 +6459,16 @@ - **Max** Takes the max of the two values, which are required to be integers. + * - 8 + - **MergeTargetId** + Merge two string values in target id format. A target id consists of + a triple-cpu id string followed by a list of features delimited by + ':'. Each feature is a string postfixed by '+' or '-'. The features + in the source target id only are added to the destination target id + if their triple-cpu id matches. If a feature is in both source and + destination target ids but with different sign, an error of conflict + module flags will be emitted. + It is an error for a particular unique flag ID to have multiple behaviors, except in the case of **Require** (which adds restrictions on another metadata value) or **Override**. Index: llvm/include/llvm/IR/Module.h =================================================================== --- llvm/include/llvm/IR/Module.h +++ llvm/include/llvm/IR/Module.h @@ -148,9 +148,12 @@ /// Takes the max of the two values, which are required to be integers. Max = 7, + /// Merge target ids. + MergeTargetId = 8, + // Markers: ModFlagBehaviorFirstVal = Error, - ModFlagBehaviorLastVal = Max + ModFlagBehaviorLastVal = MergeTargetId }; /// Checks if Metadata represents a valid ModFlagBehavior, and stores the Index: llvm/lib/IR/Verifier.cpp =================================================================== --- llvm/lib/IR/Verifier.cpp +++ llvm/lib/IR/Verifier.cpp @@ -1456,6 +1456,13 @@ break; } + case Module::MergeTargetId: { + Assert(dyn_cast_or_null(Op->getOperand(2)), + "invalid value for 'MergeTargetId' module flag (expected string)", + Op->getOperand(2)); + break; + } + case Module::Require: { // The value should itself be an MDNode with two operands, a flag ID (an // MDString), and a value. Index: llvm/lib/Linker/IRMover.cpp =================================================================== --- llvm/lib/Linker/IRMover.cpp +++ llvm/lib/Linker/IRMover.cpp @@ -1195,6 +1195,35 @@ /// Merge the linker flags in Src into the Dest module. Error IRLinker::linkModuleFlagsMetadata() { + // A module with MergeTargetId is not allowed to link with a module + // without MergeTargetId. + auto HasMergeTargetIdBehavior = [](Module &M) { + auto *ModFlags = M.getModuleFlagsMetadata(); + if (!ModFlags) + return false; + for (unsigned I = 0, E = ModFlags->getNumOperands(); I != E; ++I) { + MDNode *Op = ModFlags->getOperand(I); + ConstantInt *Behavior = mdconst::extract(Op->getOperand(0)); + if (Behavior->getZExtValue() == Module::MergeTargetId) + return true; + } + return false; + }; + // llvm-link starts with an empty module and adds modules one by one. We + // have to allow the empty module to link with any other module. + if (DstM.getModuleFlagsMetadata()) { + bool SrcHasTargetId = HasMergeTargetIdBehavior(*SrcM); + bool DstHasTargetId = HasMergeTargetIdBehavior(DstM); + if (SrcHasTargetId != DstHasTargetId) { + auto *HasM = SrcHasTargetId ? &*SrcM : &DstM; + auto *NoM = SrcHasTargetId ? &DstM : &*SrcM; + return stringErr("cannot link '" + HasM->getModuleIdentifier() + + "' which has target-id with '" + + NoM->getModuleIdentifier() + + "' which does not have target-id."); + } + } + // If the source module has no module flags, we are done. const NamedMDNode *SrcModFlags = SrcM->getModuleFlagsMetadata(); if (!SrcModFlags) @@ -1376,6 +1405,92 @@ makeArrayRef(Elts.begin(), Elts.end()))); break; } + case Module::MergeTargetId: { + auto DstTargetId = cast(DstOp->getOperand(2))->getString(); + auto SrcTargetId = cast(SrcOp->getOperand(2))->getString(); + + // Extract features from target id. Features are strings ending with '+' + // or '-' delimited by ':'. Returns false if the format is invalid. + auto GetFeatures = [](StringRef TargetId, StringMap &Features) { + auto Splits = TargetId.split(':'); + auto FeatureStr = Splits.second; + while (!FeatureStr.empty()) { + auto Splits = FeatureStr.split(':'); + auto F = Splits.first; + auto Sign = F.back(); + if (Sign != '+' && Sign != '-') + return false; + F = F.drop_back(); + if (F.empty()) + return false; + auto Loc = Features.find(F); + if (Loc != Features.end()) + return false; + Features[F] = Sign == '+'; + FeatureStr = Splits.second; + } + return true; + }; + StringMap DstFeatures; + StringMap SrcFeatures; + // Diagnose target ids whose formats are invalid. + if (!GetFeatures(DstTargetId, DstFeatures)) { + return stringErr("invalid module flag '" + ID->getString() + + "': incorrect format ('" + DstTargetId + "' from '" + + DstM.getModuleIdentifier() + "'"); + } + if (!GetFeatures(SrcTargetId, SrcFeatures)) { + return stringErr("invalid module flag '" + ID->getString() + + "': incorrect format ('" + SrcTargetId + "' from '" + + SrcM->getModuleIdentifier() + "'"); + } + + // If destination and source target id's both contain a feature but with + // different signs, they cannot be merged. + for (const auto &F : DstFeatures) { + auto Loc = SrcFeatures.find(F.getKeyData()); + if (Loc != SrcFeatures.end() && Loc->second != F.second) + return stringErr("linking module flags '" + ID->getString() + + "': IDs have conflicting values ('" + DstTargetId + + "' from '" + SrcM->getModuleIdentifier() + + "' with '" + SrcTargetId + "' from '" + + DstM.getModuleIdentifier() + "'"); + } + + // Merge features from source target id into destination target id. + // This can only be done if source and target triple and cpu matches, + // otherwise the target ids have conflicts. + auto DstTripleCPU = DstTargetId.split(':').first.str(); + auto SrcTripleCPU = SrcTargetId.split(':').first.str(); + for (const auto &F : SrcFeatures) { + auto Loc = DstFeatures.find(F.getKeyData()); + if (Loc == DstFeatures.end()) { + if (DstTripleCPU != SrcTripleCPU) + return stringErr("linking module flags '" + ID->getString() + + "': IDs have conflicting values ('" + DstTargetId + + "' from '" + SrcM->getModuleIdentifier() + + "' with '" + SrcTargetId + "' from '" + + DstM.getModuleIdentifier() + "'"); + DstFeatures[F.getKeyData()] = F.second; + } + } + + // Create a target id whose triple and cpu are the same as the original + // destination target id but contains new features merged from the source + // target id. + std::string MergedTargetId = DstTargetId.split(':').first.str(); + for (const auto &F : DstFeatures) + MergedTargetId = + MergedTargetId + ":" + F.getKeyData() + (F.second ? "+" : "-"); + + // Create the new module flag containing the merged target id. + Metadata *FlagOps[] = {DstOp->getOperand(0), ID, + MDString::get(DstM.getContext(), MergedTargetId)}; + MDNode *Flag = MDNode::get(DstM.getContext(), FlagOps); + DstModFlags->setOperand(DstIndex, Flag); + Flags[ID].first = Flag; + break; + } } } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-default.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-default.ll @@ -0,0 +1,6 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx900" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-diff-cpu.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-diff-cpu.ll @@ -0,0 +1,6 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx909:sramecc-" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-empty.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-empty.ll @@ -0,0 +1,6 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +!0 = !{ i32 8, !"target-id", !"" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-invalid.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-invalid.ll @@ -0,0 +1,7 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +; Invalid target id: feature must ends with +/-. +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-none.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-none.ll @@ -0,0 +1,6 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +!0 = !{ i32 1, !"foo", i32 37 } + +!llvm.module.flags = !{ !0 } \ No newline at end of file Index: llvm/test/Linker/Inputs/module-flags-target-id-src-xnack-off.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-xnack-off.ll @@ -0,0 +1,6 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack-" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-xnack-on-sramecc-off.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-xnack-on-sramecc-off.ll @@ -0,0 +1,6 @@ +; This file is used with module-flags-target-id-dst-*.ll +; RUN: true + +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack+:sramecc-" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/module-flags-target-id-dst-default.ll =================================================================== --- /dev/null +++ llvm/test/Linker/module-flags-target-id-dst-default.ll @@ -0,0 +1,35 @@ +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-default.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefixes=NOCHANGE,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-empty.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefixes=NOCHANGE,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-on-sramecc-off.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefixes=BOTH,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-off.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefixes=XNACK,COMMON %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-invalid.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=INVALID %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-diff-cpu.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-none.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT2 %s + +; Test target id module flags. + +; NOCHANGE: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908"} +; BOTH: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack+:sramecc-"} +; XNACK: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack-"} +; COMMON: !llvm.module.flags = !{!0} + +; INVALID: invalid module flag 'target-id': incorrect format ('amdgcn-amd-amdhsa--gfx908:xnack' +; CONFLICT: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdhsa--gfx908' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx909:sramecc-' from '{{.*}}' +; CONFLICT2: cannot link '{{.*}}' which has target-id with '{{.*}}' which does not have target-id. + +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908" } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/module-flags-target-id-dst-none.ll =================================================================== --- /dev/null +++ llvm/test/Linker/module-flags-target-id-dst-none.ll @@ -0,0 +1,31 @@ +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-default.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-empty.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-on-sramecc-off.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-off.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-invalid.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-diff-cpu.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=CONFLICT %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-none.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=NONE %s + +; Test target id module flags. + +; NONE: !llvm.module.flags = !{!0} +; NONE: !0 = !{i32 1, !"foo", i32 37} + +; CONFLICT: cannot link '{{.*}}' which has target-id with '{{.*}}' which does not have target-id. + +!0 = !{ i32 1, !"foo", i32 37 } + +!llvm.module.flags = !{ !0 } Index: llvm/test/Linker/module-flags-target-id-dst-xnack-on-sramecc-off.ll =================================================================== --- /dev/null +++ llvm/test/Linker/module-flags-target-id-dst-xnack-on-sramecc-off.ll @@ -0,0 +1,30 @@ +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-default.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefix=NOCHANGE %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-empty.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefixes=NOCHANGE %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-on-sramecc-off.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefix=NOCHANGE %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-diff-cpu.ll -S -o - \ +; RUN: | sort | FileCheck -check-prefix=NOCHANGE %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-off.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=NOXNACK %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-invalid.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=INVALID %s + +; Test target id module flags. + +; NOCHANGE: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack+:sramecc-"} +; NOCHANGE: !llvm.module.flags = !{!0} + +; NOXNACK: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdhsa--gfx908:xnack+:sramecc-' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx908:xnack-' from '{{.*}}' + +; INVALID: invalid module flag 'target-id': incorrect format ('amdgcn-amd-amdhsa--gfx908:xnack' + +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack+:sramecc-" } + +!llvm.module.flags = !{ !0 }