blob: 2c44cc06ea1b3ec4d1cdef31caa1b91bd0a6199e [file]
//===--- CodeGenHwModes.cpp -----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// Classes to parse and store HW mode information for instruction selection
//===----------------------------------------------------------------------===//
#include "CodeGenHwModes.h"
#include "SubtargetFeatureInfo.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include <algorithm>
#include <set>
using namespace llvm;
#define DEBUG_TYPE "codegen-hwmodes"
StringRef CodeGenHwModes::DefaultModeName = "DefaultMode";
HwMode::HwMode(const Record *R) {
Name = R->getName();
Predicates = R->getValueAsListOfDefs("Predicates");
}
LLVM_DUMP_METHOD
void HwMode::dump() const {
dbgs() << Name << ": ";
ListSeparator LS;
for (const Record *R : Predicates)
dbgs() << LS << R->getName();
dbgs() << '\n';
}
HwModeSelect::HwModeSelect(const Record *R, CodeGenHwModes &CGH) {
std::vector<const Record *> Modes = R->getValueAsListOfDefs("Modes");
std::vector<const Record *> Objects = R->getValueAsListOfDefs("Objects");
for (auto [Mode, Object] : zip_equal(Modes, Objects)) {
unsigned ModeId = CGH.getHwModeId(Mode);
Items.emplace_back(ModeId, Object);
}
}
LLVM_DUMP_METHOD
void HwModeSelect::dump() const {
dbgs() << '{';
for (const PairType &P : Items)
dbgs() << " (" << P.first << ',' << P.second->getName() << ')';
dbgs() << " }\n";
}
// Helper to represent predicates semantically.
HwModePredicates::HwModePredicates(ArrayRef<const Record *> Preds) {
HasNonAsmPredicates =
!getRequiredFeatures(FeaturesSet, AnyOfFeatureSets, Preds);
}
// DefaultMode is implicitly active only when *none* of the other target
// HwModes are active.
//
// Mathematically: DefaultMode = !(Mode_1 || Mode_2 || ... || Mode_N)
// = !Mode_1 && !Mode_2 && ... && !Mode_N
//
// If Mode_1 requires (FeatureA && FeatureB), then !Mode_1 requires
// (!FeatureA || !FeatureB). We construct these implicit OR-sets for all
// other modes and add them to DefaultMode's requirements.
HwModePredicates
HwModePredicates::createForDefaultMode(const CodeGenHwModes &CGH) {
HwModePredicates SP;
for (unsigned M = 1; M < CGH.getNumModeIds(); ++M) {
const HwMode &Mode = CGH.getMode(M);
HwModePredicates ModePreds(Mode.Predicates);
if (ModePreds.HasNonAsmPredicates) {
LLVM_DEBUG(dbgs().indent(2)
<< "Warning: HwMode '" << Mode.Name
<< "' has non-assembler predicates. Ignoring for "
"DefaultMode negation.\n");
SP.HasNonAsmPredicates = true;
continue;
}
// Negate the features in FeaturesSet: !(F1 && F2) = !F1 || !F2
std::set<SubtargetFeatureLiteral> NegatedFeatures;
for (const auto &Op : ModePreds.FeaturesSet) {
NegatedFeatures.insert({Op.Feature, !Op.IsNot});
}
if (ModePreds.AnyOfFeatureSets.empty()) {
if (!NegatedFeatures.empty())
SP.AnyOfFeatureSets.insert(std::move(NegatedFeatures));
} else if (ModePreds.AnyOfFeatureSets.size() == 1) {
// Negate a mode with a single OR-set:
// !(F1 && F2 && (L1 || L2)) = !F1 || !F2 || (!L1 && !L2)
// In CNF: (!F1 || !F2 || !L1) && (!F1 || !F2 || !L2)
const auto &OrSet = *ModePreds.AnyOfFeatureSets.begin();
for (const auto &Op : OrSet) {
std::set<SubtargetFeatureLiteral> NewOrSet = NegatedFeatures;
NewOrSet.insert({Op.Feature, !Op.IsNot});
SP.AnyOfFeatureSets.insert(std::move(NewOrSet));
}
} else {
LLVM_DEBUG(dbgs().indent(2)
<< "Warning: HwMode '" << Mode.Name
<< "' has multiple complex predicates. Ignoring for "
"DefaultMode negation.\n");
}
}
return SP;
}
void HwModePredicates::add(const HwModePredicates &Other) {
FeaturesSet.insert(Other.FeaturesSet.begin(), Other.FeaturesSet.end());
AnyOfFeatureSets.insert(Other.AnyOfFeatureSets.begin(),
Other.AnyOfFeatureSets.end());
HasNonAsmPredicates |= Other.HasNonAsmPredicates;
}
// Evaluates if the current set of predicates contains a contradiction.
// Performs unit propagation: if we have a known feature F, we can simplify
// OR-sets (A || B) containing F or !F.
// Returns true if the OrSet is satisfied, false otherwise.
// If not satisfied, fills SimplifiedSet with remaining active literals.
static bool simplifyOrSet(const std::set<SubtargetFeatureLiteral> &OrSet,
const std::set<SubtargetFeatureLiteral> &FeaturesSet,
std::set<SubtargetFeatureLiteral> &SimplifiedSet) {
for (const auto &Lit : OrSet) {
if (FeaturesSet.count(Lit))
return true; // Satisfied
if (!FeaturesSet.count({Lit.Feature, !Lit.IsNot}))
SimplifiedSet.insert(Lit);
}
return false;
}
// Evaluates if the current set of predicates contains a contradiction.
// Performs unit propagation: if we have a known feature F, we can simplify
// OR-sets (A || B) containing F or !F.
bool HwModePredicates::isSelfContradictory() {
// Check for immediate contradictions (e.g. requiring both F and !F).
for (const auto &Lit : FeaturesSet) {
if (FeaturesSet.count({Lit.Feature, !Lit.IsNot}))
return true;
}
std::set<std::set<SubtargetFeatureLiteral>> NewAnyOfs;
bool FeaturesChanged = false;
for (const auto &OrSet : AnyOfFeatureSets) {
std::set<SubtargetFeatureLiteral> SimplifiedSet;
if (simplifyOrSet(OrSet, FeaturesSet, SimplifiedSet))
continue; // Discard satisfied set
// All literals in this OR-set are false, causing a contradiction.
if (SimplifiedSet.empty())
return true;
if (SimplifiedSet.size() == 1) {
// Unit clause: only one choice remains, so it must be true.
FeaturesSet.insert(*SimplifiedSet.begin());
FeaturesChanged = true;
} else {
NewAnyOfs.insert(std::move(SimplifiedSet));
}
}
if (FeaturesChanged) {
AnyOfFeatureSets = std::move(NewAnyOfs);
return isSelfContradictory();
}
return false;
}
// Two predicate sets conflict if their union is self-contradictory.
bool HwModePredicates::conflictsWith(const HwModePredicates &Other) const {
HwModePredicates Combined(*this);
Combined.add(Other);
return Combined.isSelfContradictory();
}
CodeGenHwModes::CodeGenHwModes(const RecordKeeper &RK) : Records(RK) {
for (const Record *R : Records.getAllDerivedDefinitions("HwMode")) {
// The default mode needs a definition in the .td sources for TableGen
// to accept references to it. We need to ignore the definition here.
if (R->getName() == DefaultModeName)
continue;
Modes.emplace_back(R);
ModeIds.try_emplace(R, Modes.size());
}
assert(Modes.size() <= 32 && "number of HwModes exceeds maximum of 32");
for (const Record *R : Records.getAllDerivedDefinitions("HwModeSelect")) {
auto P = ModeSelects.emplace(R, HwModeSelect(R, *this));
assert(P.second);
(void)P;
}
}
const HwModePredicates &
CodeGenHwModes::getModePredicates(unsigned ModeId) const {
if (PredicatesByMode.empty()) {
PredicatesByMode.resize(getNumModeIds());
PredicatesByMode[DefaultMode] =
HwModePredicates::createForDefaultMode(*this);
for (unsigned M = 1; M < getNumModeIds(); ++M)
PredicatesByMode[M] = HwModePredicates(getMode(M).Predicates);
}
return PredicatesByMode[ModeId];
}
unsigned CodeGenHwModes::getHwModeId(const Record *R) const {
if (R->getName() == DefaultModeName)
return DefaultMode;
auto F = ModeIds.find(R);
assert(F != ModeIds.end() && "Unknown mode name");
return F->second;
}
const HwModeSelect &CodeGenHwModes::getHwModeSelect(const Record *R) const {
auto F = ModeSelects.find(R);
assert(F != ModeSelects.end() && "Record is not a \"mode select\"");
return F->second;
}
LLVM_DUMP_METHOD
void CodeGenHwModes::dump() const {
dbgs() << "Modes: {\n";
for (const HwMode &M : Modes) {
dbgs() << " ";
M.dump();
}
dbgs() << "}\n";
dbgs() << "ModeIds: {\n";
for (const auto &P : ModeIds)
dbgs() << " " << P.first->getName() << " -> " << P.second << '\n';
dbgs() << "}\n";
dbgs() << "ModeSelects: {\n";
for (const auto &P : ModeSelects) {
dbgs() << " " << P.first->getName() << " -> ";
P.second.dump();
}
dbgs() << "}\n";
}
// Resolves a HwModeSelect record based on pattern predicates.
// Returns a unique resolved record if compatible, or nullptr if ambiguous.
const Record *
CodeGenHwModes::resolveModeSelect(const Record *SelectRec,
ArrayRef<const Record *> PatPreds) const {
if (!SelectRec->isSubClassOf("HwModeSelect"))
return SelectRec;
LLVM_DEBUG(dbgs() << "Trying to resolve HwModeSelect '"
<< SelectRec->getName() << "'\n");
const HwModeSelect &MS = getHwModeSelect(SelectRec);
std::set<const Record *> ResolvedObjects;
// Construct and parse the pattern predicates ONCE here
HwModePredicates PatPredsSet(PatPreds);
for (const auto &[ModeId, Obj] : MS.Items) {
const HwModePredicates &ModePreds = getModePredicates(ModeId);
if (!ModePreds.conflictsWith(PatPredsSet)) {
LLVM_DEBUG(dbgs() << " HwMode '" << getModeName(ModeId, true)
<< "' is compatible -> " << Obj->getName() << "\n");
ResolvedObjects.insert(Obj);
} else {
LLVM_DEBUG(dbgs() << " HwMode '" << getModeName(ModeId, true)
<< "' is incompatible due to semantic conflict\n");
}
}
if (ResolvedObjects.size() == 1) {
const Record *Resolved = *ResolvedObjects.begin();
LLVM_DEBUG(dbgs() << " Resolved to unique object: " << Resolved->getName()
<< "\n");
return Resolved;
}
if (ResolvedObjects.empty()) {
LLVM_DEBUG(dbgs() << " No active modes resolved for '"
<< SelectRec->getName() << "'\n");
} else {
LLVM_DEBUG(
dbgs() << " Multiple active modes resolved to different objects for '"
<< SelectRec->getName() << "'\n");
}
return nullptr;
}