Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -6483,6 +6483,15 @@ - **Max** Takes the max of the two values, which are required to be integers. + * - 8 + - **MergeTargetID** + Merge two string values in the format of triple-`target-ID + `. + The key must be `target-id`. If there is a mismatch in triple or + processor of the source and destination target ID's, or if a feature + is in both source and destination target ID's but with different + signs, 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 @@ -1457,6 +1457,16 @@ break; } + case Module::MergeTargetID: { + Assert(ID->getString() == "target-id", + "Invalid key for 'MergeTargetID' module flag (expected 'target-id')", + Op->getOperand(1)); + 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,8 +1405,100 @@ makeArrayRef(Elts.begin(), Elts.end()))); break; } - } + case Module::MergeTargetID: { + StringRef DstTargetID = cast(DstOp->getOperand(2))->getString(); + StringRef SrcTargetID = cast(SrcOp->getOperand(2))->getString(); + + // Check if source and target triple and cpu matches. An empty target ID + // represents a 'generic' cpu which can link with any other target ID. + StringRef DstTripleCPU = DstTargetID.split(':').first; + StringRef SrcTripleCPU = SrcTargetID.split(':').first; + if (!DstTargetID.empty() && !SrcTargetID.empty() && + DstTripleCPU != SrcTripleCPU) + return stringErr("linking module flags '" + ID->getString() + + "': IDs have conflicting values ('" + SrcTargetID + + "' from '" + SrcM->getModuleIdentifier() + "' with '" + + DstTargetID + "' from '" + DstM.getModuleIdentifier() + + "'"); + + // Extract features from target ID. The first ':' delimits the + // "-" portion from the features, which are then also + // delimited by ':'. Features are postfixed with '+' or '-'. + // Returns false if the format is invalid. + auto GetFeatures = [](StringRef TargetID, + std::map &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; + }; + // Use std::map instead of StringMap to get the features in alphabetical + // order. + std::map DstFeatures; + std::map SrcFeatures; + // Diagnose target ID's 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.first); + if (Loc != SrcFeatures.end() && Loc->second != F.second) + return stringErr("linking module flags '" + ID->getString() + + "': IDs have conflicting values ('" + SrcTargetID + + "' from '" + SrcM->getModuleIdentifier() + + "' with '" + DstTargetID + "' from '" + + DstM.getModuleIdentifier() + "'"); + } + + // Merge features from source target ID into destination target ID. + for (const auto &F : SrcFeatures) { + auto Loc = DstFeatures.find(F.first); + if (Loc == DstFeatures.end()) { + DstFeatures[F.first] = F.second; + } + } + + // Create a target ID containing new features merged from the source + // target ID. + std::string MergedTargetID = + DstTripleCPU.empty() ? SrcTripleCPU.str() : DstTripleCPU.str(); + for (const auto &F : DstFeatures) + MergedTargetID = + MergedTargetID + ":" + F.first.str() + (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; + } + } } // Check all of the requirements. 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,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908" } 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,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx900" } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-diff-triple.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-diff-triple.ll @@ -0,0 +1,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdpal--gfx908" } 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,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"" } 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,5 @@ +; This file is used with module-flags-target-id-dst-*.ll + +; Invalid target id: feature must ends with +/-. +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack" } 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,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 1, !"foo", i32 37 } Index: llvm/test/Linker/Inputs/module-flags-target-id-src-sram-ecc-off-xnack-on.ll =================================================================== --- /dev/null +++ llvm/test/Linker/Inputs/module-flags-target-id-src-sram-ecc-off-xnack-on.ll @@ -0,0 +1,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+" } 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,4 @@ +; This file is used with module-flags-target-id-dst-*.ll + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack-" } 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,38 @@ +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-default.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=NOCHANGE,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-empty.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=NOCHANGE,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-sram-ecc-off-xnack-on.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=BOTH,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-off.ll -S -o - \ +; RUN: 2>&1 | 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-triple.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=DIFFTRIPLE %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-diff-cpu.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=DIFFCPU %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. + +; COMMON: !llvm.module.flags = !{!0} +; NOCHANGE: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908"} +; BOTH: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+"} +; XNACK: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack-"} + +; INVALID: error: invalid module flag 'target-id': incorrect format ('amdgcn-amd-amdhsa--gfx908:xnack' +; DIFFTRIPLE: error: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdpal--gfx908' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx908' from '{{.*}}' +; DIFFCPU: error: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdhsa--gfx900' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx908' from '{{.*}}' +; CONFLICT2: error: cannot link '{{.*}}' which has target-id with '{{.*}}' which does not have target-id. + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908" } Index: llvm/test/Linker/module-flags-target-id-dst-empty.ll =================================================================== --- /dev/null +++ llvm/test/Linker/module-flags-target-id-dst-empty.ll @@ -0,0 +1,39 @@ +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-default.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=DEFAULT,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-empty.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=EMPTY,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-sram-ecc-off-xnack-on.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=BOTH,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-xnack-off.ll -S -o - \ +; RUN: 2>&1 | 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: llvm-link %s %p/Inputs/module-flags-target-id-src-diff-triple.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=DIFFTRIPLE,COMMON %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-diff-cpu.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=DIFFCPU,COMMON %s + +; RUN: not 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. + +; COMMON: !llvm.module.flags = !{!0} +; DEFAULT: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908"} +; EMPTY: !0 = !{i32 8, !"target-id", !""} +; BOTH: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+"} +; XNACK: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:xnack-"} +; DIFFTRIPLE: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdpal--gfx908"} +; DIFFCPU: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx900"} + +; INVALID: error: invalid module flag 'target-id': incorrect format ('amdgcn-amd-amdhsa--gfx908:xnack' +; NONE: error: cannot link 'llvm-link' which has target-id with '{{.*}}' which does not have target-id + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"" } 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,30 @@ +; 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-sram-ecc-off-xnack-on.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: error: cannot link '{{.*}}' which has target-id with '{{.*}}' which does not have target-id. + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 1, !"foo", i32 37 } Index: llvm/test/Linker/module-flags-target-id-dst-sram-ecc-off-xnack-on.ll =================================================================== --- /dev/null +++ llvm/test/Linker/module-flags-target-id-dst-sram-ecc-off-xnack-on.ll @@ -0,0 +1,34 @@ +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-default.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=NOCHANGE %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-empty.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefixes=NOCHANGE %s + +; RUN: llvm-link %s %p/Inputs/module-flags-target-id-src-sram-ecc-off-xnack-on.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=NOCHANGE %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-diff-triple.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=DIFFTRIPLE %s + +; RUN: not llvm-link %s %p/Inputs/module-flags-target-id-src-diff-cpu.ll -S -o - \ +; RUN: 2>&1 | FileCheck -check-prefix=DIFFCPU %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: !llvm.module.flags = !{!0} +; NOCHANGE: !0 = !{i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+"} + +; DIFFTRIPLE: error: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdpal--gfx908' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+' from '{{.*}}' +; DIFFCPU: error: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdhsa--gfx900' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+' from '{{.*}}' +; NOXNACK: error: linking module flags 'target-id': IDs have conflicting values ('amdgcn-amd-amdhsa--gfx908:xnack-' from '{{.*}}' with 'amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+' from '{{.*}}' + +; INVALID: error: invalid module flag 'target-id': incorrect format ('amdgcn-amd-amdhsa--gfx908:xnack' + +!llvm.module.flags = !{ !0 } +!0 = !{ i32 8, !"target-id", !"amdgcn-amd-amdhsa--gfx908:sram-ecc-:xnack+" } Index: llvm/test/Verifier/module-flags-target-id-invalid-key.ll =================================================================== --- /dev/null +++ llvm/test/Verifier/module-flags-target-id-invalid-key.ll @@ -0,0 +1,7 @@ +; RUN: not opt -verify %s 2>&1 | FileCheck %s + +; CHECK: Invalid key for 'MergeTargetID' module flag (expected 'target-id') + +!llvm.module.flags = !{ !0 } + +!0 = !{ i32 8, !"foo", !"amdgcn-amd-amdhsa-gfx908" } Index: llvm/test/Verifier/module-flags-target-id-invalid-value.ll =================================================================== --- /dev/null +++ llvm/test/Verifier/module-flags-target-id-invalid-value.ll @@ -0,0 +1,7 @@ +; RUN: not opt -verify %s 2>&1 | FileCheck %s + +; CHECK: invalid value for 'MergeTargetID' module flag (expected string) + +!llvm.module.flags = !{ !0 } + +!0 = !{ i32 8, !"target-id", i32 37 }