Skip to content

Commit 7f0e407

Browse files
authored
[NFCI][TableGen] Make Intrinsic::getAttributes table driven (#152349)
This a follow on to llvm/llvm-project#152219 to reduce both code and frame size of `Intrinsic::getAttributes` further. Currently, this function consists of several switch cases (one per unique argument attributes) that populates the local `AS` array with all non-empty argument attributes for that intrinsic by calling `getIntrinsicArgAttributeSet`. This change makes this code table driven and implements `Intrinsic::getAttributes` without any switch cases, which reduces the code size of this function on all platforms and in addition reduces the frame size by a factor of 10 on Windows. This is achieved by: 1. Emitting table `ArgAttrIdTable` containing a concatenated list of `<ArgNo, AttrID>` entries across all unique arguments. 2. Emitting table `ArgAttributesInfoTable` (indexed by unique arguments-ID) to store the starting index and number of non-empty arg attributes. 3. Reserving unique function-ID 255 to indicate that the intrinsic has no function attributes (to replace `HasFnAttr` setup in each switch case). 4. Using a simple table lookup and for loop to build the list of argument and function attributes for a given intrinsic. Experimental data shows that with release builds and assertions disabled, this change reduces the code size for GCC and Clang builds on Linux by ~9KB for a modest (80/152 byte) increase in frame size. For Windows, it reduces the code size by 20KB and frame size from 4736 bytes to 461 bytes which is 10x reduction. Actual data is as follows: ``` Current trunk: Compiler gcc-13.3.0 clang-18.1.3 MSVC 19.43.34810.0 code size 0x35a9 0x370c 0x5581 frame size 0x120 0x118 0x1280 table driven Intrinsic::getAttributes: code size 0xcfb 0xcd0 0x1cf frame size 0x1b8 0x188 0x1A0 Total savings (code + data) 9212 bytes 9790 bytes 20119 bytes ``` Total savings above accounts for the additional data size for the 2 new tables, which in this experiment was: `ArgAttributesInfoTable` = 314 bytes and `ArgAttrIdTable` = 888 bytes. Coupled with the earlier llvm/llvm-project#152219, this achieves a 46x reduction in frame size for this function in Windows release builds.
1 parent 8d26252 commit 7f0e407

File tree

2 files changed

+132
-62
lines changed

2 files changed

+132
-62
lines changed

llvm/test/TableGen/intrinsic-attrs.td

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ def int_deref_ptr_ret : Intrinsic<[llvm_ptr_ty], [], [Dereferenceable<RetIndex,
2727
// CHECK: static constexpr uint16_t IntrinsicsToAttributesMap[] = {
2828
// CHECK: 0 << 8 | 0, // llvm.deref.ptr.ret
2929
// CHECK: 1 << 8 | 1, // llvm.random.gen
30+
// CHECK: }; // IntrinsicsToAttributesMap
31+
32+
// CHECK: static constexpr ArgNoAttrIDPair ArgAttrIdTable[] = {
33+
// CHECK-NEXT: {0, 0},
34+
// CHECK: }; // ArgAttrIdTable
35+
36+
// CHECK: static constexpr ArgAttributesInfo ArgAttributesInfoTable[] = {
37+
// CHECK-NEXT: {0, 1},
38+
// CHECK-NEXT: {0, 0},
39+
// CHECK-NEXT: }; // ArgAttributesInfoTable
3040

3141
// CHECK: getAttributes(LLVMContext &C, ID id,
3242
// CHECK-NEXT: FunctionType *FT) {
33-
// CHECK: case 1:
34-
// CHECK-NEXT: HasFnAttr = true;
35-
// CHECK-NEXT: break;
36-
// CHECK-NEXT: case 0:
37-
// CHECK-NEXT: AS[0] = {0, getIntrinsicArgAttributeSet(C, 0, FT->getContainedType(0))};
38-
// CHECK-NEXT: HasFnAttr = true;
39-
// CHECK-NEXT: NumAttrs = 1
40-
// CHECK-NEXT: break;

llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp

Lines changed: 122 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,8 @@ static AttributeSet getIntrinsicArgAttributeSet(LLVMContext &C, unsigned ID,
561561
} // getIntrinsicArgAttributeSet
562562
)";
563563

564-
// Compute unique function attribute sets.
564+
// Compute unique function attribute sets. Note that ID 255 will be used for
565+
// intrinsics with no function attributes.
565566
std::map<const CodeGenIntrinsic *, unsigned, FnAttributeComparator>
566567
UniqFnAttributes;
567568
OS << R"(
@@ -570,6 +571,8 @@ static AttributeSet getIntrinsicFnAttributeSet(LLVMContext &C, unsigned ID) {
570571
default: llvm_unreachable("Invalid attribute set number");)";
571572

572573
for (const CodeGenIntrinsic &Int : Ints) {
574+
if (!hasFnAttributes(Int))
575+
continue;
573576
unsigned ID = UniqFnAttributes.size();
574577
if (!UniqFnAttributes.try_emplace(&Int, ID).second)
575578
continue;
@@ -621,96 +624,161 @@ static AttributeSet getIntrinsicFnAttributeSet(LLVMContext &C, unsigned ID) {
621624
622625
static constexpr uint16_t IntrinsicsToAttributesMap[] = {)";
623626

624-
// Compute the maximum number of attribute arguments and the map. For function
625-
// attributes, we only consider whether the intrinsics has any function
626-
// arguments or not.
627+
// Compute unique argument attributes.
627628
std::map<const CodeGenIntrinsic *, unsigned, AttributeComparator>
628629
UniqAttributes;
629630
for (const CodeGenIntrinsic &Int : Ints) {
630631
unsigned ID = UniqAttributes.size();
631632
UniqAttributes.try_emplace(&Int, ID);
632633
}
633634

634-
// Emit an array of AttributeList. Most intrinsics will have at least one
635-
// entry, for the function itself (index ~1), which is usually nounwind.
636-
for (const CodeGenIntrinsic &Int : Ints) {
637-
uint16_t FnAttrIndex = UniqFnAttributes[&Int];
638-
OS << formatv("\n {} << 8 | {}, // {}", FnAttrIndex,
639-
UniqAttributes[&Int], Int.Name);
640-
}
635+
constexpr uint16_t NoFunctionAttrsID = 255;
636+
if (UniqAttributes.size() > 256)
637+
PrintFatalError("Too many unique argument attributes for table!");
638+
// Note, ID 255 is used to indicate no function attributes.
639+
if (UniqFnAttributes.size() > 255)
640+
PrintFatalError("Too many unique function attributes for table!");
641641

642642
// Assign a 16-bit packed ID for each intrinsic. The lower 8-bits will be its
643643
// "argument attribute ID" (index in UniqAttributes) and upper 8 bits will be
644644
// its "function attribute ID" (index in UniqFnAttributes).
645-
if (UniqAttributes.size() > 256)
646-
PrintFatalError("Too many unique argument attributes for table!");
647-
if (UniqFnAttributes.size() > 256)
648-
PrintFatalError("Too many unique function attributes for table!");
645+
for (const CodeGenIntrinsic &Int : Ints) {
646+
uint16_t FnAttrIndex =
647+
hasFnAttributes(Int) ? UniqFnAttributes[&Int] : NoFunctionAttrsID;
648+
OS << formatv("\n {} << 8 | {}, // {}", FnAttrIndex,
649+
UniqAttributes[&Int], Int.Name);
650+
}
649651

650652
OS << R"(
651-
};
653+
}; // IntrinsicsToAttributesMap
654+
)";
655+
656+
// For a given intrinsic, its attributes are constructed by populating the
657+
// local array `AS` below with its non-empty argument attributes followed by
658+
// function attributes if any. Each argument attribute is constructed as:
659+
//
660+
// getIntrinsicArgAttributeSet(C, ArgAttrID, FT->getContainedType(ArgNo));
661+
//
662+
// Create a table that records, for each argument attributes, the set of
663+
// <ArgNo, ArgAttrID> pairs that are needed to construct its argument
664+
// attributes. These tables for all intrinsics will be concatenated into one
665+
// large table and then for each intrinsic, we remember the Staring index and
666+
// number of size of its slice of entries (i.e., number of arguments with
667+
// non-empty attributes), so that we can build the attribute list for an
668+
// intrinsic without using a switch-case.
669+
670+
// Find the max number of attributes to create the local array and create
671+
// a concatenated list of <ArgNo, AttrID> pairs.
672+
struct ArgNoAttrIDPair {
673+
uint16_t ArgNo, ArgAttrID;
674+
ArgNoAttrIDPair(uint16_t ArgNo, uint16_t ArgAttrID)
675+
: ArgNo(ArgNo), ArgAttrID(ArgAttrID) {}
676+
};
677+
678+
// For each unique ID in UniqAttributes, reacord the starting index in the
679+
// flattened ArgNoAttrIDPair table, and the number of non-empty arg
680+
// attributes.
681+
struct ArgAttributesInfo {
682+
uint16_t StartIndex;
683+
uint16_t NumAttrs;
684+
ArgAttributesInfo(uint16_t StartIndex, uint16_t NumAttrs)
685+
: StartIndex(StartIndex), NumAttrs(NumAttrs) {}
686+
};
687+
SmallVector<ArgNoAttrIDPair> ArgAttrIdTable;
688+
SmallVector<ArgAttributesInfo> ArgAttributesInfoTable(UniqAttributes.size(),
689+
{0, 0});
652690

653-
AttributeList Intrinsic::getAttributes(LLVMContext &C, ID id,
654-
FunctionType *FT) {)";
655-
// Find the max number of attributes to create the local array.
656691
unsigned MaxNumAttrs = 0;
657692
for (const auto [IntPtr, UniqueID] : UniqAttributes) {
658693
const CodeGenIntrinsic &Int = *IntPtr;
659-
unsigned NumAttrs =
660-
llvm::count_if(Int.ArgumentAttributes,
661-
[](const auto &Attrs) { return !Attrs.empty(); });
694+
unsigned NumAttrs = 0;
695+
unsigned StartIndex = ArgAttrIdTable.size();
696+
697+
for (const auto &[ArgNo, Attrs] : enumerate(Int.ArgumentAttributes)) {
698+
if (Attrs.empty())
699+
continue;
700+
701+
uint16_t ArgAttrID = UniqArgAttributes.find(Attrs)->second;
702+
ArgAttrIdTable.emplace_back((uint16_t)ArgNo, ArgAttrID);
703+
++NumAttrs;
704+
}
705+
706+
// Record the start index and size of the list for this unique ID.
707+
if (NumAttrs)
708+
ArgAttributesInfoTable[UniqueID] =
709+
ArgAttributesInfo(StartIndex, NumAttrs);
710+
662711
NumAttrs += hasFnAttributes(Int);
663712
MaxNumAttrs = std::max(MaxNumAttrs, NumAttrs);
664713
}
665714

715+
if (ArgAttrIdTable.size() >= std::numeric_limits<uint16_t>::max())
716+
PrintFatalError("Size of ArgAttrIdTable exceeds supported limit");
717+
718+
// Emit the 2 tables (flattened ArgNo, ArgAttrID) and ArgAttrIdTableIndex
719+
OS << R"(
720+
namespace {
721+
struct ArgNoAttrIDPair {
722+
uint16_t ArgNo, ArgAttrID;
723+
};
724+
} // namespace
725+
726+
static constexpr ArgNoAttrIDPair ArgAttrIdTable[] = {
727+
)";
728+
for (const auto &[ArgNo, ArgAttrID] : ArgAttrIdTable)
729+
OS << formatv(" {{{}, {}},\n", ArgNo, ArgAttrID);
730+
OS << R"(}; // ArgAttrIdTable
731+
732+
namespace {
733+
struct ArgAttributesInfo {
734+
uint16_t StartIndex;
735+
uint16_t NumAttrs;
736+
};
737+
} // namespace
738+
739+
static constexpr ArgAttributesInfo ArgAttributesInfoTable[] = {
740+
)";
741+
for (const auto &[StartIndex, NumAttrs] : ArgAttributesInfoTable)
742+
OS << formatv(" {{{}, {}},\n", StartIndex, NumAttrs);
743+
OS << "}; // ArgAttributesInfoTable\n";
744+
745+
// Now emit the Intrinsic::getAttributes function. This will first map
746+
// from intrinsic ID -> unique arg/function attr ID (using the
747+
// IntrinsicsToAttributesMap) table. Then it will use the unique arg ID to
748+
// construct all the argument attributes (using the ArgAttributesInfoTable and
749+
// ArgAttrIdTable) and then add on the function attributes if any.
666750
OS << formatv(R"(
751+
AttributeList Intrinsic::getAttributes(LLVMContext &C, ID id,
752+
FunctionType *FT) {{
667753
if (id == 0)
668754
return AttributeList();
669755
670756
uint16_t PackedID = IntrinsicsToAttributesMap[id - 1];
671757
uint8_t FnAttrID = PackedID >> 8;
758+
uint8_t ArgAttrID = PackedID & 0xFF;
672759
std::pair<unsigned, AttributeSet> AS[{}];
673-
unsigned NumAttrs = 0;
674-
bool HasFnAttr = false;
675-
switch(PackedID & 0xFF) {{
676-
default: llvm_unreachable("Invalid attribute number");
677-
)",
678-
MaxNumAttrs);
679760
680-
for (const auto [IntPtr, UniqueID] : UniqAttributes) {
681-
OS << formatv(" case {}:\n", UniqueID);
682-
const CodeGenIntrinsic &Int = *IntPtr;
683-
684-
unsigned NumAttrs = 0;
685-
686-
for (const auto &[AttrIdx, Attrs] : enumerate(Int.ArgumentAttributes)) {
687-
if (Attrs.empty())
688-
continue;
761+
// Construct an ArrayRef for easier range checking.
762+
ArrayRef<ArgAttributesInfo> ArgAttributesInfoTableAR(ArgAttributesInfoTable);
763+
if (ArgAttrID >= ArgAttributesInfoTableAR.size())
764+
llvm_unreachable("Invalid arguments attribute ID");
689765
690-
unsigned ArgAttrID = UniqArgAttributes.find(Attrs)->second;
691-
OS << formatv(" AS[{}] = {{{}, getIntrinsicArgAttributeSet(C, {}, "
692-
"FT->getContainedType({}))};\n",
693-
NumAttrs++, AttrIdx, ArgAttrID, AttrIdx);
694-
}
695-
696-
if (hasFnAttributes(Int))
697-
OS << " HasFnAttr = true;\n";
698-
699-
if (NumAttrs)
700-
OS << formatv(" NumAttrs = {};\n", NumAttrs);
701-
OS << " break;\n";
766+
auto [StartIndex, NumAttrs] = ArgAttributesInfoTableAR[ArgAttrID];
767+
for (unsigned Idx = 0; Idx < NumAttrs; ++Idx) {{
768+
auto [ArgNo, ArgAttrID] = ArgAttrIdTable[StartIndex + Idx];
769+
AS[Idx] = {{ArgNo,
770+
getIntrinsicArgAttributeSet(C, ArgAttrID, FT->getContainedType(ArgNo))};
702771
}
703-
704-
OS << R"( }
705-
if (HasFnAttr) {
706-
AS[NumAttrs++] = {AttributeList::FunctionIndex,
772+
if (FnAttrID != {}) {
773+
AS[NumAttrs++] = {{AttributeList::FunctionIndex,
707774
getIntrinsicFnAttributeSet(C, FnAttrID)};
708775
}
709776
return AttributeList::get(C, ArrayRef(AS, NumAttrs));
710777
}
711778
#endif // GET_INTRINSIC_ATTRIBUTES
712779
713-
)";
780+
)",
781+
MaxNumAttrs, NoFunctionAttrsID);
714782
}
715783

716784
void IntrinsicEmitter::EmitIntrinsicToBuiltinMap(

0 commit comments

Comments
 (0)