llvm-project/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
Rahul Joshi 99786f20ee
[LLVM][Intrinsics] Refactor IITDescriptor (#190011)
The main change is to eliminate the use of "Argument" terminology when
dealing with overloaded types since overloaded types can be either
argument or return values, and some additional renaming for clarity.

1. Rename `Tys` argument to various intrinsic APIs to `OverloadTys` to
better reflect its meaning.
2. Rename `IITDescriptorKind::Argument` to
`IITDescriptorKind::Overloaded` to better convey that it's an overloaded
type. Removed "Argument" suffix for other kinds for dependent types.
3. Rename `ArgKind` to `AnyKind`, `getArgumentNumber` to
`getOverloadIndex`, `getArgumentKind` to `getOverloadKind`,
`getRefArgNumber` to `getRefOverloadIndex`, and `IIT_ARG` to `IIT_ANY`.
4. Rename `IIT_ANYPTR` (used to represent a pointer qualified with
address space) to `IIT_PTR_AS` to clearly distinguish it from
`llvm_anyptr_ty`
5. Change the packing of [ref overload index & overload index] for
`VecOfAnyPtrsToElt` to pack the overload index into the lower bits, so
we can use the `getOverloadIndex` function to get the overload index.
2026-04-02 06:19:01 -07:00

1036 lines
37 KiB
C++

//===- IntrinsicEmitter.cpp - Generate intrinsic information --------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This tablegen backend emits information about intrinsic functions.
//
//===----------------------------------------------------------------------===//
#include "CodeGenIntrinsics.h"
#include "SequenceToOffsetTable.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/ModRef.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TableGen/CodeGenHelpers.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/StringToOffsetTable.h"
#include "llvm/TableGen/TableGenBackend.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cctype>
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <vector>
using namespace llvm;
static cl::OptionCategory GenIntrinsicCat("Options for -gen-intrinsic-enums");
static cl::opt<std::string>
IntrinsicPrefix("intrinsic-prefix",
cl::desc("Generate intrinsics with this target prefix"),
cl::value_desc("target prefix"), cl::cat(GenIntrinsicCat));
namespace {
class IntrinsicEmitter {
const RecordKeeper &Records;
public:
IntrinsicEmitter(const RecordKeeper &R) : Records(R) {}
void run(raw_ostream &OS, bool Enums);
void EmitEnumInfo(const CodeGenIntrinsicTable &Ints, raw_ostream &OS);
void EmitAnyKind(raw_ostream &OS);
void EmitIITInfo(raw_ostream &OS);
void EmitTargetInfo(const CodeGenIntrinsicTable &Ints, raw_ostream &OS);
void EmitIntrinsicToNameTable(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS);
void EmitIntrinsicToOverloadTable(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS);
void EmitIntrinsicToPrettyPrintTable(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS);
void EmitIntrinsicBitTable(
const CodeGenIntrinsicTable &Ints, raw_ostream &OS, StringRef Guard,
StringRef TableName, StringRef Comment,
function_ref<bool(const CodeGenIntrinsic &Int)> GetProperty);
void EmitGenerator(const CodeGenIntrinsicTable &Ints, raw_ostream &OS);
void EmitAttributes(const CodeGenIntrinsicTable &Ints, raw_ostream &OS);
void EmitPrettyPrintArguments(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS);
void EmitIntrinsicToBuiltinMap(const CodeGenIntrinsicTable &Ints,
bool IsClang, raw_ostream &OS);
};
// Helper class to use with `TableGen::Emitter::OptClass`.
template <bool Enums> class IntrinsicEmitterOpt : public IntrinsicEmitter {
public:
IntrinsicEmitterOpt(const RecordKeeper &R) : IntrinsicEmitter(R) {}
void run(raw_ostream &OS) { IntrinsicEmitter::run(OS, Enums); }
};
} // End anonymous namespace
//===----------------------------------------------------------------------===//
// IntrinsicEmitter Implementation
//===----------------------------------------------------------------------===//
void IntrinsicEmitter::run(raw_ostream &OS, bool Enums) {
emitSourceFileHeader("Intrinsic Function Source Fragment", OS);
CodeGenIntrinsicTable Ints(Records);
if (Enums) {
// Emit the enum information.
EmitEnumInfo(Ints, OS);
// Emit AnyKind for Intrinsics.h.
EmitAnyKind(OS);
} else {
// Emit IIT_Info constants.
EmitIITInfo(OS);
// Emit the target metadata.
EmitTargetInfo(Ints, OS);
// Emit the intrinsic ID -> name table.
EmitIntrinsicToNameTable(Ints, OS);
// Emit the intrinsic ID -> overload table.
EmitIntrinsicToOverloadTable(Ints, OS);
// Emit the intrinsic declaration generator.
EmitGenerator(Ints, OS);
// Emit the intrinsic parameter attributes.
EmitAttributes(Ints, OS);
// Emit the intrinsic ID -> pretty print table.
EmitIntrinsicToPrettyPrintTable(Ints, OS);
// Emit Pretty Print attribute.
EmitPrettyPrintArguments(Ints, OS);
// Emit code to translate Clang builtins into LLVM intrinsics.
EmitIntrinsicToBuiltinMap(Ints, true, OS);
// Emit code to translate MS builtins into LLVM intrinsics.
EmitIntrinsicToBuiltinMap(Ints, false, OS);
}
}
void IntrinsicEmitter::EmitEnumInfo(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS) {
// Find the TargetSet for which to generate enums. There will be an initial
// set with an empty target prefix which will include target independent
// intrinsics like dbg.value.
using TargetSet = CodeGenIntrinsicTable::TargetSet;
const TargetSet *Set = nullptr;
for (const auto &Target : Ints.getTargets()) {
if (Target.Name == IntrinsicPrefix) {
Set = &Target;
break;
}
}
if (!Set) {
// The first entry is for target independent intrinsics, so drop it.
auto KnowTargets = Ints.getTargets().drop_front();
PrintFatalError([KnowTargets](raw_ostream &OS) {
OS << "tried to generate intrinsics for unknown target "
<< IntrinsicPrefix << "\nKnown targets are: ";
interleaveComma(KnowTargets, OS,
[&OS](const TargetSet &Target) { OS << Target.Name; });
OS << '\n';
});
}
// Generate a complete header for target specific intrinsics.
std::optional<IfDefEmitter> IfDef;
std::optional<IncludeGuardEmitter> IncGuard;
std::optional<NamespaceEmitter> NS;
if (IntrinsicPrefix.empty()) {
IfDef.emplace(OS, "GET_INTRINSIC_ENUM_VALUES");
} else {
std::string UpperPrefix = StringRef(IntrinsicPrefix).upper();
IncGuard.emplace(
OS, formatv("LLVM_IR_INTRINSIC_{}_ENUMS_H", UpperPrefix).str());
NS.emplace(OS, "llvm::Intrinsic");
OS << formatv("enum {}Intrinsics : unsigned {{\n", UpperPrefix);
}
OS << "// Enum values for intrinsics.\n";
bool First = true;
for (const CodeGenIntrinsic &Int : Ints[*Set]) {
OS << " " << Int.EnumName;
// Assign a value to the first intrinsic in this target set so that all
// intrinsic ids are distinct.
if (First) {
OS << " = " << Set->Offset + 1;
First = false;
}
OS << ", ";
if (Int.EnumName.size() < 40)
OS.indent(40 - Int.EnumName.size());
OS << formatv(
" // {} ({})\n", Int.Name,
SrcMgr.getFormattedLocationNoOffset(Int.TheDef->getLoc().front()));
}
// Emit num_intrinsics into the target neutral enum.
if (IntrinsicPrefix.empty())
OS << formatv(" num_intrinsics = {}\n", Ints.size() + 1);
else
OS << "}; // enum\n";
}
void IntrinsicEmitter::EmitAnyKind(raw_ostream &OS) {
if (!IntrinsicPrefix.empty())
return;
IfDefEmitter IfDef(OS, "GET_INTRINSIC_ANYKIND");
OS << "// llvm::Intrinsic::IITDescriptor::AnyKind.\n";
if (const auto RecAnyKind = Records.getDef("AnyKind")) {
for (const auto &RV : RecAnyKind->getValues())
OS << " AK_" << RV.getName() << " = " << *RV.getValue() << ",\n";
} else {
OS << "#error \"AnyKind is not defined\"\n";
}
}
void IntrinsicEmitter::EmitIITInfo(raw_ostream &OS) {
IfDefEmitter IfDef(OS, "GET_INTRINSIC_IITINFO");
std::array<StringRef, 256> RecsByNumber;
auto IIT_Base = Records.getAllDerivedDefinitionsIfDefined("IIT_Base");
for (const Record *Rec : IIT_Base) {
auto Number = Rec->getValueAsInt("Number");
assert(0 <= Number && Number < (int)RecsByNumber.size() &&
"IIT_Info.Number should be uint8_t");
assert(RecsByNumber[Number].empty() && "Duplicate IIT_Info.Number");
RecsByNumber[Number] = Rec->getName();
}
if (IIT_Base.size() > 0) {
for (unsigned I = 0, E = RecsByNumber.size(); I < E; ++I)
if (!RecsByNumber[I].empty())
OS << " " << RecsByNumber[I] << " = " << I << ",\n";
} else {
OS << "#error \"class IIT_Base is not defined\"\n";
}
}
void IntrinsicEmitter::EmitTargetInfo(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS) {
IfDefEmitter IfDef(OS, "GET_INTRINSIC_TARGET_DATA");
OS << R"(// Target mapping.
struct IntrinsicTargetInfo {
StringLiteral Name;
size_t Offset;
size_t Count;
};
static constexpr IntrinsicTargetInfo TargetInfos[] = {
)";
for (const auto [Name, Offset, Count] : Ints.getTargets())
OS << formatv(" {{\"{}\", {}, {}},\n", Name, Offset, Count);
OS << "};\n";
}
/// Helper function to emit a bit table for intrinsic properties.
/// This is used for both overload and pretty print bit tables.
void IntrinsicEmitter::EmitIntrinsicBitTable(
const CodeGenIntrinsicTable &Ints, raw_ostream &OS, StringRef Guard,
StringRef TableName, StringRef Comment,
function_ref<bool(const CodeGenIntrinsic &Int)> GetProperty) {
IfDefEmitter IfDef(OS, Guard);
OS << formatv("// {}\n", Comment);
OS << formatv("static constexpr uint8_t {}[] = {{\n", TableName);
OS << " 0\n ";
for (auto [I, Int] : enumerate(Ints)) {
// Add one to the index so we emit a null bit for the invalid #0 intrinsic.
size_t Idx = I + 1;
if (Idx % 8 == 0)
OS << ",\n 0";
if (GetProperty(Int))
OS << " | (1<<" << Idx % 8 << ')';
}
OS << "\n};\n\n";
OS << formatv("return ({}[id/8] & (1 << (id%8))) != 0;\n", TableName);
}
void IntrinsicEmitter::EmitIntrinsicToNameTable(
const CodeGenIntrinsicTable &Ints, raw_ostream &OS) {
// Built up a table of the intrinsic names.
constexpr StringLiteral NotIntrinsic = "not_intrinsic";
StringToOffsetTable Table;
Table.GetOrAddStringOffset(NotIntrinsic);
for (const auto &Int : Ints)
Table.GetOrAddStringOffset(Int.Name);
IfDefEmitter IfDef(OS, "GET_INTRINSIC_NAME_TABLE");
OS << R"(// Intrinsic ID to name table.
// Note that entry #0 is the invalid intrinsic!
)";
Table.EmitStringTableDef(OS, "IntrinsicNameTable");
OS << R"(
static constexpr unsigned IntrinsicNameOffsetTable[] = {
)";
OS << formatv(" {}, // {}\n", Table.GetStringOffset(NotIntrinsic),
NotIntrinsic);
for (const auto &Int : Ints)
OS << formatv(" {}, // {}\n", Table.GetStringOffset(Int.Name), Int.Name);
OS << "\n}; // IntrinsicNameOffsetTable\n";
}
void IntrinsicEmitter::EmitIntrinsicToOverloadTable(
const CodeGenIntrinsicTable &Ints, raw_ostream &OS) {
EmitIntrinsicBitTable(
Ints, OS, "GET_INTRINSIC_OVERLOAD_TABLE", "OTable",
"Intrinsic ID to overload bitset.",
[](const CodeGenIntrinsic &Int) { return Int.isOverloaded; });
}
using TypeSigTy = SmallVector<unsigned char>;
/// Computes type signature of the intrinsic \p Int.
static TypeSigTy ComputeTypeSignature(const CodeGenIntrinsic &Int) {
TypeSigTy TypeSig;
const Record *TypeInfo = Int.TheDef->getValueAsDef("TypeInfo");
const ListInit *TypeList = TypeInfo->getValueAsListInit("TypeSig");
for (const auto *TypeListEntry : TypeList->getElements())
TypeSig.emplace_back(cast<IntInit>(TypeListEntry)->getValue());
return TypeSig;
}
// Pack the type signature into 32-bit fixed encoding word.
static std::optional<uint32_t> encodePacked(const TypeSigTy &TypeSig) {
if (TypeSig.size() > 8)
return std::nullopt;
uint32_t Result = 0;
for (unsigned char C : reverse(TypeSig)) {
if (C > 15)
return std::nullopt;
Result = (Result << 4) | C;
}
return Result;
}
void IntrinsicEmitter::EmitGenerator(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS) {
// Note: the code below can be switched to use 32-bit fixed encoding by
// flipping the flag below.
constexpr bool Use16BitFixedEncoding = true;
using FixedEncodingTy =
std::conditional_t<Use16BitFixedEncoding, uint16_t, uint32_t>;
constexpr unsigned FixedEncodingBits = sizeof(FixedEncodingTy) * CHAR_BIT;
constexpr unsigned MSBPosition = FixedEncodingBits - 1;
// Mask with all bits 1 except the most significant bit.
constexpr unsigned Mask = (1U << MSBPosition) - 1;
StringRef FixedEncodingTypeName =
Use16BitFixedEncoding ? "uint16_t" : "uint32_t";
// If we can compute a 16/32-bit fixed encoding for this intrinsic, do so and
// capture it in this vector, otherwise store a ~0U.
std::vector<FixedEncodingTy> FixedEncodings;
SequenceToOffsetTable<TypeSigTy> LongEncodingTable;
FixedEncodings.reserve(Ints.size());
// Compute the unique argument type info.
for (const CodeGenIntrinsic &Int : Ints) {
// Get the signature for the intrinsic.
TypeSigTy TypeSig = ComputeTypeSignature(Int);
// Check to see if we can encode it into a 16/32 bit word.
std::optional<uint32_t> Result = encodePacked(TypeSig);
if (Result && (*Result & Mask) == *Result) {
FixedEncodings.push_back(static_cast<FixedEncodingTy>(*Result));
continue;
}
LongEncodingTable.add(TypeSig);
// This is a placehold that we'll replace after the table is laid out.
FixedEncodings.push_back(static_cast<FixedEncodingTy>(~0U));
}
LongEncodingTable.layout();
IfDefEmitter IfDef(OS, "GET_INTRINSIC_GENERATOR_GLOBAL");
OS << formatv(R"(// Global intrinsic function declaration type table.
using FixedEncodingTy = {};
static constexpr FixedEncodingTy IIT_Table[] = {{
)",
FixedEncodingTypeName);
unsigned MaxOffset = 0;
for (auto [Idx, FixedEncoding, Int] : enumerate(FixedEncodings, Ints)) {
if ((Idx & 7) == 7)
OS << "\n ";
// If the entry fit in the table, just emit it.
if ((FixedEncoding & Mask) == FixedEncoding) {
OS << "0x" << Twine::utohexstr(FixedEncoding) << ", ";
continue;
}
TypeSigTy TypeSig = ComputeTypeSignature(Int);
unsigned Offset = LongEncodingTable.get(TypeSig);
MaxOffset = std::max(MaxOffset, Offset);
// Otherwise, emit the offset into the long encoding table. We emit it this
// way so that it is easier to read the offset in the .def file.
OS << formatv("(1U<<{}) | {}, ", MSBPosition, Offset);
}
OS << "0\n};\n\n";
// verify that all offsets will fit in 16/32 bits.
if ((MaxOffset & Mask) != MaxOffset)
PrintFatalError("Offset of long encoding table exceeds encoding bits");
// Emit the shared table of register lists.
OS << "static constexpr unsigned char IIT_LongEncodingTable[] = {\n";
if (!LongEncodingTable.empty())
LongEncodingTable.emit(
OS, [](raw_ostream &OS, unsigned char C) { OS << (unsigned)C; });
OS << " 255\n};\n";
}
/// Returns the effective MemoryEffects for intrinsic \p Int.
static MemoryEffects getEffectiveME(const CodeGenIntrinsic &Int) {
MemoryEffects ME = Int.ME;
// TODO: IntrHasSideEffects should affect not only readnone intrinsics.
if (ME.doesNotAccessMemory() && Int.hasSideEffects)
ME = MemoryEffects::unknown();
return ME;
}
static bool compareFnAttributes(const CodeGenIntrinsic *L,
const CodeGenIntrinsic *R) {
auto TieBoolAttributes = [](const CodeGenIntrinsic *I) -> auto {
// Sort throwing intrinsics after non-throwing intrinsics.
return std::tie(I->canThrow, I->isNoDuplicate, I->isNoMerge, I->isNoReturn,
I->isNoCallback, I->isNoSync, I->isNoFree, I->isWillReturn,
I->isCold, I->isConvergent, I->isSpeculatable,
I->hasSideEffects, I->isStrictFP,
I->isNoCreateUndefOrPoison);
};
auto TieL = TieBoolAttributes(L);
auto TieR = TieBoolAttributes(R);
if (TieL != TieR)
return TieL < TieR;
// Try to order by readonly/readnone attribute.
uint32_t LME = getEffectiveME(*L).toIntValue();
uint32_t RME = getEffectiveME(*R).toIntValue();
if (LME != RME)
return LME > RME;
return false;
}
/// Returns true if \p Int has a non-empty set of function attributes. Note that
/// NoUnwind = !canThrow, so we need to negate it's sense to test if the
// intrinsic has NoUnwind attribute.
static bool hasFnAttributes(const CodeGenIntrinsic &Int) {
return !Int.canThrow || Int.isNoReturn || Int.isNoCallback || Int.isNoSync ||
Int.isNoFree || Int.isWillReturn || Int.isCold || Int.isNoDuplicate ||
Int.isNoMerge || Int.isConvergent || Int.isSpeculatable ||
Int.isStrictFP || Int.isNoCreateUndefOrPoison ||
getEffectiveME(Int) != MemoryEffects::unknown();
}
namespace {
struct FnAttributeComparator {
bool operator()(const CodeGenIntrinsic *L, const CodeGenIntrinsic *R) const {
return compareFnAttributes(L, R);
}
};
struct AttributeComparator {
bool operator()(const CodeGenIntrinsic *L, const CodeGenIntrinsic *R) const {
// This comparator is used to unique just the argument attributes of an
// intrinsic without considering any function attributes.
return L->ArgumentAttributes < R->ArgumentAttributes;
}
};
} // End anonymous namespace
/// Returns the name of the IR enum for argument attribute kind \p Kind.
static StringRef getArgAttrEnumName(CodeGenIntrinsic::ArgAttrKind Kind) {
switch (Kind) {
case CodeGenIntrinsic::NoCapture:
llvm_unreachable("Handled separately");
case CodeGenIntrinsic::NoAlias:
return "NoAlias";
case CodeGenIntrinsic::NoUndef:
return "NoUndef";
case CodeGenIntrinsic::NonNull:
return "NonNull";
case CodeGenIntrinsic::Returned:
return "Returned";
case CodeGenIntrinsic::ReadOnly:
return "ReadOnly";
case CodeGenIntrinsic::WriteOnly:
return "WriteOnly";
case CodeGenIntrinsic::ReadNone:
return "ReadNone";
case CodeGenIntrinsic::ImmArg:
return "ImmArg";
case CodeGenIntrinsic::Alignment:
return "Alignment";
case CodeGenIntrinsic::Dereferenceable:
return "Dereferenceable";
case CodeGenIntrinsic::Range:
return "Range";
}
llvm_unreachable("Unknown CodeGenIntrinsic::ArgAttrKind enum");
}
/// EmitAttributes - This emits the Intrinsic::getAttributes method.
void IntrinsicEmitter::EmitAttributes(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS) {
IfDefEmitter IfDef(OS, "GET_INTRINSIC_ATTRIBUTES");
OS << R"(// Add parameter attributes that are not common to all intrinsics.
static AttributeSet getIntrinsicArgAttributeSet(LLVMContext &C, unsigned ID,
Type *ArgType) {
unsigned BitWidth = ArgType->getScalarSizeInBits();
switch (ID) {
default: llvm_unreachable("Invalid attribute set number");)";
// Compute unique argument attribute sets.
std::map<SmallVector<CodeGenIntrinsic::ArgAttribute, 0>, unsigned>
UniqArgAttributes;
for (const CodeGenIntrinsic &Int : Ints) {
for (auto &Attrs : Int.ArgumentAttributes) {
if (Attrs.empty())
continue;
unsigned ID = UniqArgAttributes.size();
if (!UniqArgAttributes.try_emplace(Attrs, ID).second)
continue;
assert(is_sorted(Attrs) && "Argument attributes are not sorted");
OS << formatv(R"(
case {}:
return AttributeSet::get(C, {{
)",
ID);
for (const CodeGenIntrinsic::ArgAttribute &Attr : Attrs) {
if (Attr.Kind == CodeGenIntrinsic::NoCapture) {
OS << " Attribute::getWithCaptureInfo(C, "
"CaptureInfo::none()),\n";
continue;
}
StringRef AttrName = getArgAttrEnumName(Attr.Kind);
if (Attr.Kind == CodeGenIntrinsic::Alignment ||
Attr.Kind == CodeGenIntrinsic::Dereferenceable)
OS << formatv(" Attribute::get(C, Attribute::{}, {}),\n",
AttrName, Attr.Value);
else if (Attr.Kind == CodeGenIntrinsic::Range)
// This allows implicitTrunc because the range may only fit the
// type based on rules implemented in the IR verifier. E.g. the
// [-1, 1] range for ucmp/scmp intrinsics requires a minimum i2 type.
// Give the verifier a chance to diagnose this instead of asserting
// here.
OS << formatv(" Attribute::get(C, Attribute::{}, "
"ConstantRange(APInt(BitWidth, {}, /*isSigned=*/true, "
"/*implicitTrunc=*/true), APInt(BitWidth, {}, "
"/*isSigned=*/true, /*implicitTrunc=*/true))),\n",
AttrName, (int64_t)Attr.Value, (int64_t)Attr.Value2);
else
OS << formatv(" Attribute::get(C, Attribute::{}),\n", AttrName);
}
OS << " });";
}
}
OS << R"(
}
} // getIntrinsicArgAttributeSet
)";
// Compute unique function attribute sets. Note that ID 255 will be used for
// intrinsics with no function attributes.
std::map<const CodeGenIntrinsic *, unsigned, FnAttributeComparator>
UniqFnAttributes;
OS << R"(
static AttributeSet getIntrinsicFnAttributeSet(LLVMContext &C, unsigned ID) {
switch (ID) {
default: llvm_unreachable("Invalid attribute set number");)";
for (const CodeGenIntrinsic &Int : Ints) {
if (!hasFnAttributes(Int))
continue;
unsigned ID = UniqFnAttributes.size();
if (!UniqFnAttributes.try_emplace(&Int, ID).second)
continue;
OS << formatv(R"(
case {}: // {}
return AttributeSet::get(C, {{
)",
ID, Int.Name);
auto addAttribute = [&OS](StringRef Attr) {
OS << formatv(" Attribute::get(C, Attribute::{}),\n", Attr);
};
if (!Int.canThrow)
addAttribute("NoUnwind");
if (Int.isNoReturn)
addAttribute("NoReturn");
if (Int.isNoCallback)
addAttribute("NoCallback");
if (Int.isNoSync)
addAttribute("NoSync");
if (Int.isNoFree)
addAttribute("NoFree");
if (Int.isWillReturn)
addAttribute("WillReturn");
if (Int.isCold)
addAttribute("Cold");
if (Int.isNoDuplicate)
addAttribute("NoDuplicate");
if (Int.isNoMerge)
addAttribute("NoMerge");
if (Int.isConvergent)
addAttribute("Convergent");
if (Int.isSpeculatable)
addAttribute("Speculatable");
if (Int.isStrictFP)
addAttribute("StrictFP");
if (Int.isNoCreateUndefOrPoison)
addAttribute("NoCreateUndefOrPoison");
const MemoryEffects ME = getEffectiveME(Int);
if (ME != MemoryEffects::unknown()) {
OS << formatv(" // {}\n", ME);
OS << formatv(" Attribute::getWithMemoryEffects(C, "
"MemoryEffects::createFromIntValue({})),\n",
ME.toIntValue());
}
OS << " });";
}
OS << R"(
}
} // getIntrinsicFnAttributeSet)";
// Compute unique argument attributes.
std::map<const CodeGenIntrinsic *, unsigned, AttributeComparator>
UniqAttributes;
for (const CodeGenIntrinsic &Int : Ints) {
unsigned ID = UniqAttributes.size();
UniqAttributes.try_emplace(&Int, ID);
}
const uint8_t UniqAttributesBitSize = Log2_32_Ceil(UniqAttributes.size());
// Note, max value is used to indicate no function attributes.
const uint8_t UniqFnAttributesBitSize =
Log2_32_Ceil(UniqFnAttributes.size() + 1);
const uint32_t NoFunctionAttrsID =
maskTrailingOnes<uint32_t>(UniqFnAttributesBitSize);
uint8_t AttributesMapDataBitSize =
PowerOf2Ceil(UniqAttributesBitSize + UniqFnAttributesBitSize);
if (AttributesMapDataBitSize < 8)
AttributesMapDataBitSize = 8;
else if (AttributesMapDataBitSize > 64)
PrintFatalError("Packed ID of IntrinsicsToAttributesMap exceeds 64b!");
// Assign a packed ID for each intrinsic. The lower bits will be its
// "argument attribute ID" (index in UniqAttributes) and upper bits will be
// its "function attribute ID" (index in UniqFnAttributes).
OS << formatv("\nstatic constexpr uint{}_t IntrinsicsToAttributesMap[] = {{",
AttributesMapDataBitSize);
for (const CodeGenIntrinsic &Int : Ints) {
uint32_t FnAttrIndex =
hasFnAttributes(Int) ? UniqFnAttributes[&Int] : NoFunctionAttrsID;
OS << formatv("\n {} << {} | {}, // {}", FnAttrIndex,
UniqAttributesBitSize, UniqAttributes[&Int], Int.Name);
}
OS << R"(
}; // IntrinsicsToAttributesMap
)";
// For a given intrinsic, its attributes are constructed by populating the
// local array `AS` below with its non-empty argument attributes followed by
// function attributes if any. Each argument attribute is constructed as:
//
// getIntrinsicArgAttributeSet(C, ArgAttrID, FT->getContainedType(ArgNo));
//
// Create a table that records, for each argument attributes, the list of
// <ArgNo, ArgAttrID> pairs that are needed to construct its argument
// attributes. These tables for all intrinsics will be concatenated into one
// large table and then for each intrinsic, we remember the Staring index and
// number of size of its slice of entries (i.e., number of arguments with
// non-empty attributes), so that we can build the attribute list for an
// intrinsic without using a switch-case.
using ArgNoAttrIDPair = std::pair<uint16_t, uint16_t>;
// Emit the table of concatenated <ArgNo, AttrId> using SequenceToOffsetTable
// so that entries can be reused if possible. Individual sequences in this
// table do not have any terminator.
using ArgAttrIDSubTable = SmallVector<ArgNoAttrIDPair>;
SequenceToOffsetTable<ArgAttrIDSubTable> ArgAttrIdSequenceTable(std::nullopt);
SmallVector<ArgAttrIDSubTable> ArgAttrIdSubTables(
UniqAttributes.size()); // Indexed by UniqueID.
// Find the max number of attributes to create the local array.
unsigned MaxNumAttrs = 0;
for (const auto [IntPtr, UniqueID] : UniqAttributes) {
const CodeGenIntrinsic &Int = *IntPtr;
ArgAttrIDSubTable SubTable;
for (const auto &[ArgNo, Attrs] : enumerate(Int.ArgumentAttributes)) {
if (Attrs.empty())
continue;
uint16_t ArgAttrID = UniqArgAttributes.find(Attrs)->second;
SubTable.emplace_back((uint16_t)ArgNo, ArgAttrID);
}
ArgAttrIdSubTables[UniqueID] = SubTable;
if (!SubTable.empty())
ArgAttrIdSequenceTable.add(SubTable);
unsigned NumAttrs = SubTable.size() + hasFnAttributes(Int);
MaxNumAttrs = std::max(MaxNumAttrs, NumAttrs);
}
ArgAttrIdSequenceTable.layout();
if (ArgAttrIdSequenceTable.size() >= std::numeric_limits<uint16_t>::max())
PrintFatalError("Size of ArgAttrIdTable exceeds supported limit");
// Emit the 2 tables (flattened ArgNo, ArgAttrID) and ArgAttributesInfoTable.
OS << formatv(R"(
namespace {{
struct ArgNoAttrIDPair {{
uint16_t ArgNo, ArgAttrID;
};
} // namespace
// Number of entries: {}
static constexpr ArgNoAttrIDPair ArgAttrIdTable[] = {{
)",
ArgAttrIdSequenceTable.size());
ArgAttrIdSequenceTable.emit(OS, [](raw_ostream &OS, ArgNoAttrIDPair Elem) {
OS << formatv("{{{}, {}}", Elem.first, Elem.second);
});
OS << formatv(R"(}; // ArgAttrIdTable
namespace {{
struct ArgAttributesInfo {{
uint16_t StartIndex;
uint16_t NumAttrs;
};
} // namespace
// Number of entries: {}
static constexpr ArgAttributesInfo ArgAttributesInfoTable[] = {{
)",
ArgAttrIdSubTables.size());
for (const auto &SubTable : ArgAttrIdSubTables) {
unsigned NumAttrs = SubTable.size();
unsigned StartIndex = NumAttrs ? ArgAttrIdSequenceTable.get(SubTable) : 0;
OS << formatv(" {{{}, {}},\n", StartIndex, NumAttrs);
}
OS << "}; // ArgAttributesInfoTable\n";
// Now emit the Intrinsic::getAttributes function. This will first map
// from intrinsic ID -> unique arg/function attr ID (using the
// IntrinsicsToAttributesMap) table. Then it will use the unique arg ID to
// construct all the argument attributes (using the ArgAttributesInfoTable and
// ArgAttrIdTable) and then add on the function attributes if any.
OS << formatv(R"(
template <typename IDTy>
inline std::pair<uint32_t, uint32_t> unpackID(const IDTy PackedID) {{
constexpr uint8_t UniqAttributesBitSize = {};
const uint32_t FnAttrID = PackedID >> UniqAttributesBitSize;
const uint32_t ArgAttrID = PackedID &
maskTrailingOnes<uint32_t>(UniqAttributesBitSize);
return {{FnAttrID, ArgAttrID};
}
AttributeList Intrinsic::getAttributes(LLVMContext &C, ID id,
FunctionType *FT) {{
if (id == 0)
return AttributeList();
auto [FnAttrID, ArgAttrID] = unpackID(IntrinsicsToAttributesMap[id - 1]);
using PairTy = std::pair<unsigned, AttributeSet>;
alignas(PairTy) char ASStorage[sizeof(PairTy) * {}];
PairTy *AS = reinterpret_cast<PairTy *>(ASStorage);
// Construct an ArrayRef for easier range checking.
ArrayRef<ArgAttributesInfo> ArgAttributesInfoTableAR(ArgAttributesInfoTable);
if (ArgAttrID >= ArgAttributesInfoTableAR.size())
llvm_unreachable("Invalid arguments attribute ID");
auto [StartIndex, NumAttrs] = ArgAttributesInfoTableAR[ArgAttrID];
for (unsigned Idx = 0; Idx < NumAttrs; ++Idx) {{
auto [ArgNo, ArgAttrID] = ArgAttrIdTable[StartIndex + Idx];
AS[Idx] = {{ArgNo,
getIntrinsicArgAttributeSet(C, ArgAttrID, FT->getContainedType(ArgNo))};
}
if (FnAttrID != {}) {
AS[NumAttrs++] = {{AttributeList::FunctionIndex,
getIntrinsicFnAttributeSet(C, FnAttrID)};
}
return AttributeList::get(C, ArrayRef(AS, NumAttrs));
}
AttributeSet Intrinsic::getFnAttributes(LLVMContext &C, ID id) {{
if (id == 0)
return AttributeSet();
auto [FnAttrID, _] = unpackID(IntrinsicsToAttributesMap[id - 1]);
if (FnAttrID == {})
return AttributeSet();
return getIntrinsicFnAttributeSet(C, FnAttrID);
}
)",
UniqAttributesBitSize, MaxNumAttrs, NoFunctionAttrsID,
NoFunctionAttrsID);
}
void IntrinsicEmitter::EmitIntrinsicToPrettyPrintTable(
const CodeGenIntrinsicTable &Ints, raw_ostream &OS) {
EmitIntrinsicBitTable(Ints, OS, "GET_INTRINSIC_PRETTY_PRINT_TABLE", "PPTable",
"Intrinsic ID to pretty print bitset.",
[](const CodeGenIntrinsic &Int) {
return !Int.PrettyPrintFunctions.empty();
});
}
void IntrinsicEmitter::EmitPrettyPrintArguments(
const CodeGenIntrinsicTable &Ints, raw_ostream &OS) {
IfDefEmitter IfDef(OS, "GET_INTRINSIC_PRETTY_PRINT_ARGUMENTS");
OS << R"(
void Intrinsic::printImmArg(ID IID, unsigned ArgIdx, raw_ostream &OS, const Constant *ImmArgVal) {
using namespace Intrinsic;
switch (IID) {
)";
for (const CodeGenIntrinsic &Int : Ints) {
if (Int.PrettyPrintFunctions.empty())
continue;
OS << " case " << Int.EnumName << ":\n";
OS << " switch (ArgIdx) {\n";
for (const auto [ArgIdx, ArgName, FuncName] : Int.PrettyPrintFunctions) {
OS << " case " << ArgIdx << ":\n";
OS << " OS << \"" << ArgName << "=\";\n";
if (!FuncName.empty()) {
OS << " ";
if (!Int.TargetPrefix.empty())
OS << Int.TargetPrefix << "::";
OS << FuncName << "(OS, ImmArgVal);\n";
}
OS << " return;\n";
}
OS << " }\n";
OS << " break;\n";
}
OS << R"( default:
break;
}
})";
}
void IntrinsicEmitter::EmitIntrinsicToBuiltinMap(
const CodeGenIntrinsicTable &Ints, bool IsClang, raw_ostream &OS) {
StringRef CompilerName = IsClang ? "Clang" : "MS";
StringRef UpperCompilerName = IsClang ? "CLANG" : "MS";
// map<TargetPrefix, pair<map<BuiltinName, EnumName>, CommonPrefix>.
// Note that we iterate over both the maps in the code below and both
// iterations need to iterate in sorted key order. For the inner map, entries
// need to be emitted in the sorted order of `BuiltinName` with `CommonPrefix`
// rempved, because we use std::lower_bound to search these entries. For the
// outer map as well, entries need to be emitted in sorter order of
// `TargetPrefix` as we use std::lower_bound to search these entries.
using BIMEntryTy =
std::pair<std::map<StringRef, StringRef>, std::optional<StringRef>>;
std::map<StringRef, BIMEntryTy> BuiltinMap;
for (const CodeGenIntrinsic &Int : Ints) {
StringRef BuiltinName = IsClang ? Int.ClangBuiltinName : Int.MSBuiltinName;
if (BuiltinName.empty())
continue;
// Get the map for this target prefix.
auto &[Map, CommonPrefix] = BuiltinMap[Int.TargetPrefix];
if (!Map.try_emplace(BuiltinName, Int.EnumName).second)
PrintFatalError(Int.TheDef->getLoc(),
"Intrinsic '" + Int.TheDef->getName() + "': duplicate " +
CompilerName + " builtin name!");
// Update common prefix.
if (!CommonPrefix) {
// For the first builtin for this target, initialize the common prefix.
CommonPrefix = BuiltinName;
continue;
}
// Update the common prefix. Note that this assumes that `take_front` will
// never set the `Data` pointer in CommonPrefix to nullptr.
const char *Mismatch = mismatch(*CommonPrefix, BuiltinName).first;
*CommonPrefix = CommonPrefix->take_front(Mismatch - CommonPrefix->begin());
}
// Populate the string table with the names of all the builtins after
// removing this common prefix.
StringToOffsetTable Table;
for (const auto &[TargetPrefix, Entry] : BuiltinMap) {
auto &[Map, CommonPrefix] = Entry;
for (auto &[BuiltinName, EnumName] : Map) {
StringRef Suffix = BuiltinName.substr(CommonPrefix->size());
Table.GetOrAddStringOffset(Suffix);
}
}
IfDefEmitter IfDef(
OS,
formatv("GET_LLVM_INTRINSIC_FOR_{}_BUILTIN", UpperCompilerName).str());
OS << formatv(R"(
// Get the LLVM intrinsic that corresponds to a builtin. This is used by the
// C front-end. The builtin name is passed in as BuiltinName, and a target
// prefix (e.g. 'ppc') is passed in as TargetPrefix.
Intrinsic::ID
Intrinsic::getIntrinsicFor{}Builtin(StringRef TargetPrefix,
StringRef BuiltinName) {{
using namespace Intrinsic;
)",
CompilerName);
if (BuiltinMap.empty()) {
OS << "return not_intrinsic;\n";
return;
}
if (!Table.empty()) {
Table.EmitStringTableDef(OS, "BuiltinNames");
OS << R"(
struct BuiltinEntry {
ID IntrinsicID;
unsigned StrTabOffset;
const char *getName() const { return BuiltinNames[StrTabOffset].data(); }
bool operator<(StringRef RHS) const {
return strncmp(getName(), RHS.data(), RHS.size()) < 0;
}
};
)";
}
// Emit a per target table of bultin names.
bool HasTargetIndependentBuiltins = false;
StringRef TargetIndepndentCommonPrefix;
for (const auto &[TargetPrefix, Entry] : BuiltinMap) {
const auto &[Map, CommonPrefix] = Entry;
if (!TargetPrefix.empty()) {
OS << formatv(" // Builtins for {0}.\n", TargetPrefix);
} else {
OS << " // Target independent builtins.\n";
HasTargetIndependentBuiltins = true;
TargetIndepndentCommonPrefix = *CommonPrefix;
}
// Emit the builtin table for this target prefix.
OS << formatv(" static constexpr BuiltinEntry {}Names[] = {{\n",
TargetPrefix);
for (const auto &[BuiltinName, EnumName] : Map) {
StringRef Suffix = BuiltinName.substr(CommonPrefix->size());
OS << formatv(" {{{}, {}}, // {}\n", EnumName,
*Table.GetStringOffset(Suffix), BuiltinName);
}
OS << formatv(" }; // {}Names\n\n", TargetPrefix);
}
// After emitting the builtin tables for all targets, emit a lookup table for
// all targets. We will use binary search, similar to the table for builtin
// names to lookup into this table.
OS << R"(
struct TargetEntry {
StringLiteral TargetPrefix;
ArrayRef<BuiltinEntry> Names;
StringLiteral CommonPrefix;
bool operator<(StringRef RHS) const {
return TargetPrefix < RHS;
};
};
static constexpr TargetEntry TargetTable[] = {
)";
for (const auto &[TargetPrefix, Entry] : BuiltinMap) {
const auto &[Map, CommonPrefix] = Entry;
if (TargetPrefix.empty())
continue;
OS << formatv(R"( {{"{0}", {0}Names, "{1}"},)", TargetPrefix,
CommonPrefix)
<< "\n";
}
OS << " };\n";
// Now for the actual lookup, first check the target independent table if
// we emitted one.
if (HasTargetIndependentBuiltins) {
OS << formatv(R"(
// Check if it's a target independent builtin.
// Copy the builtin name so we can use it in consume_front without clobbering
// if for the lookup in the target specific table.
StringRef Suffix = BuiltinName;
if (Suffix.consume_front("{}")) {{
auto II = lower_bound(Names, Suffix);
if (II != std::end(Names) && II->getName() == Suffix)
return II->IntrinsicID;
}
)",
TargetIndepndentCommonPrefix);
}
// If a target independent builtin was not found, lookup the target specific.
OS << R"(
auto TI = lower_bound(TargetTable, TargetPrefix);
if (TI == std::end(TargetTable) || TI->TargetPrefix != TargetPrefix)
return not_intrinsic;
// This is the last use of BuiltinName, so no need to copy before using it in
// consume_front.
if (!BuiltinName.consume_front(TI->CommonPrefix))
return not_intrinsic;
auto II = lower_bound(TI->Names, BuiltinName);
if (II == std::end(TI->Names) || II->getName() != BuiltinName)
return not_intrinsic;
return II->IntrinsicID;
}
)";
}
static TableGen::Emitter::OptClass<IntrinsicEmitterOpt</*Enums=*/true>>
X("gen-intrinsic-enums", "Generate intrinsic enums");
static TableGen::Emitter::OptClass<IntrinsicEmitterOpt</*Enums=*/false>>
Y("gen-intrinsic-impl", "Generate intrinsic implementation code");