Skip to content

Commit f4de28a

Browse files
authored
[StrTable] Switch intrinsics to StringTable and work around MSVC (llvm#123548)
Historically, the main example of *very* large string tables used the `EmitCharArray` to work around MSVC limitations with string literals, but that was switched (without removing the API) in order to consolidate on a nicer emission primitive. While this large string table in `IntrinsicsImpl.inc` seems to compile correctly on MSVC without the work around in `EmitCharArray` (and that this PR adds back to the nicer emission path), other users have repeatedly hit this MSVC limitation as you can see in the discussion on PR llvm#120534. This PR teaches the string offset table emission to look at the size of the table and switch to the char array emission strategy when the table becomes too large. This work around does have the downside of making compile times worse for large string tables, but that appears unavoidable until we can identify known good MSVC versions and switch to requiring them for all LLVM users. It also reduces searchability of the generated string table -- I looked at emitting a comment with each string but it is tricky because the escaping rules for an inline comment are different from those of of a string literal, and there's no real way to turn the string literal into a comment. While improving the output in this way, also clean up the output to not emit an extraneous empty string at the end of the string table, and update the `StringTable` class to not look for that. It isn't actually used by anything and is wasteful. This PR also switches the `IntrinsicsImpl.inc` string tables over to the new `StringTable` runtime abstraction. I didn't want to do this until landing the MSVC workaround in case it caused even this example to start hitting the MSVC bug, but I wanted to switch here so that I could simplify the API for emitting the string table with the workaround present. With the two different emission strategies, its important to use a very exact syntax and that seems better encapsulated in the API. Last but not least, the `SDNodeInfoEmitter` is updated, including its tests to match the new output. This PR should unblock landing llvm#120534 and letting us switch all of Clang's builtins to use string tables. That PR has all the details motivating the overall effort. Follow-up patches will try to consolidate the remaining users onto the single interface, but those at least were easy to separate into follow-ups and keep this PR somewhat smaller.
1 parent cd57c95 commit f4de28a

File tree

12 files changed

+118
-93
lines changed

12 files changed

+118
-93
lines changed

clang/utils/TableGen/ClangDiagnosticsEmitter.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,8 +1785,7 @@ static void emitDiagArrays(DiagsInGroupTy &DiagsInGroup,
17851785
/// This creates an `llvm::StringTable` of all the diagnostic group names.
17861786
static void emitDiagGroupNames(const StringToOffsetTable &GroupNames,
17871787
raw_ostream &OS) {
1788-
GroupNames.EmitStringLiteralDef(
1789-
OS, "static constexpr llvm::StringTable DiagGroupNames");
1788+
GroupNames.EmitStringTableDef(OS, "DiagGroupNames");
17901789
OS << "\n";
17911790
}
17921791

@@ -1939,9 +1938,6 @@ void clang::EmitClangDiagGroups(const RecordKeeper &Records, raw_ostream &OS) {
19391938
inferPedantic.compute(&DiagsInPedantic, &GroupsInPedantic);
19401939

19411940
StringToOffsetTable GroupNames;
1942-
// Add an empty string to the table first so we can use `llvm::StringTable`.
1943-
// TODO: Factor this into `StringToOffsetTable`.
1944-
GroupNames.GetOrAddStringOffset("");
19451941
for (const auto &[Name, Group] : DiagsInGroup) {
19461942
GroupNames.GetOrAddStringOffset(Name);
19471943
}

llvm/include/llvm/ADT/StringTable.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,11 @@ class StringTable {
7878
// support `constexpr`.
7979
assert(!Table.empty() && "Requires at least a valid empty string.");
8080
assert(Table.data()[0] == '\0' && "Offset zero must be the empty string.");
81-
// Ensure that `strlen` from any offset cannot overflow the end of the table
82-
// by insisting on a null byte at the end. We also insist on the last string
83-
// within the table being *separately* null terminated. This structure is
84-
// used to enable predictable iteration over all the strings when needed.
81+
// Regardless of how many strings are in the table, the last one should also
82+
// be null terminated. This also ensures that computing `strlen` on the
83+
// strings can't accidentally run past the end of the table.
8584
assert(Table.data()[Table.size() - 1] == '\0' &&
8685
"Last byte must be a null byte.");
87-
assert(Table.data()[Table.size() - 2] == '\0' &&
88-
"Next-to-last byte must be a null byte.");
8986
}
9087

9188
// Get a string from the table starting with the provided offset. The returned

llvm/include/llvm/TableGen/StringToOffsetTable.h

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class StringToOffsetTable {
2727
std::string AggregateString;
2828

2929
public:
30+
StringToOffsetTable() {
31+
// Ensure we always put the empty string at offset zero. That lets empty
32+
// initialization also be zero initialization for offsets into the table.
33+
GetOrAddStringOffset("");
34+
}
35+
3036
bool empty() const { return StringOffset.empty(); }
3137
size_t size() const { return AggregateString.size(); }
3238

@@ -51,28 +57,71 @@ class StringToOffsetTable {
5157
return II->second;
5258
}
5359

54-
// Emit the string using string literal concatenation, for better readability
55-
// and searchability.
56-
void EmitStringLiteralDef(raw_ostream &OS, const Twine &Decl,
57-
const Twine &Indent = " ") const {
60+
// Emit a string table definition with the provided name and indent.
61+
//
62+
// When possible, this uses string-literal concatenation to emit the string
63+
// contents in a readable and searchable way. However, for (very) large string
64+
// tables MSVC cannot reliably use string literals and so there we use a large
65+
// character array. We still use a line oriented emission and add comments to
66+
// provide searchability even in this case.
67+
//
68+
// The string table, and its input string contents, are always emitted as both
69+
// `static` and `constexpr`. Both `Name` and (`Name` + "Storage") must be
70+
// valid identifiers to declare.
71+
void EmitStringTableDef(raw_ostream &OS, const Twine &Name,
72+
const Twine &Indent = "") const {
5873
OS << formatv(R"(
5974
#ifdef __GNUC__
6075
#pragma GCC diagnostic push
6176
#pragma GCC diagnostic ignored "-Woverlength-strings"
6277
#endif
63-
{0}{1} = )",
64-
Indent, Decl);
78+
{0}static constexpr char {1}Storage[] = )",
79+
Indent, Name);
80+
81+
// MSVC silently miscompiles string literals longer than 64k in some
82+
// circumstances. When the string table is longer, emit it as an array of
83+
// character literals.
84+
bool UseChars = AggregateString.size() > (64 * 1024);
85+
OS << (UseChars ? "{\n" : "\n");
86+
87+
llvm::ListSeparator LineSep(UseChars ? ",\n" : "\n");
88+
llvm::SmallVector<StringRef> Strings(split(AggregateString, '\0'));
89+
// We should always have an empty string at the start, and because these are
90+
// null terminators rather than separators, we'll have one at the end as
91+
// well. Skip the end one.
92+
assert(Strings.front().empty() && "Expected empty initial string!");
93+
assert(Strings.back().empty() &&
94+
"Expected empty string at the end due to terminators!");
95+
Strings.pop_back();
96+
for (StringRef Str : Strings) {
97+
OS << LineSep << Indent << " ";
98+
// If we can, just emit this as a string literal to be concatenated.
99+
if (!UseChars) {
100+
OS << "\"";
101+
OS.write_escaped(Str);
102+
OS << "\\0\"";
103+
continue;
104+
}
65105

66-
for (StringRef Str : split(AggregateString, '\0')) {
67-
OS << "\n" << Indent << " \"";
68-
OS.write_escaped(Str);
69-
OS << "\\0\"";
106+
llvm::ListSeparator CharSep(", ");
107+
for (char C : Str) {
108+
OS << CharSep << "'";
109+
OS.write_escaped(StringRef(&C, 1));
110+
OS << "'";
111+
}
112+
OS << CharSep << "'\\0'";
70113
}
71-
OS << R"(;
114+
OS << LineSep << Indent << (UseChars ? "};" : " ;");
115+
116+
OS << formatv(R"(
72117
#ifdef __GNUC__
73118
#pragma GCC diagnostic pop
74119
#endif
75-
)";
120+
121+
{0}static constexpr llvm::StringTable {1} =
122+
{0} {1}Storage;
123+
)",
124+
Indent, Name);
76125
}
77126

78127
// Emit the string as one single string.
@@ -110,26 +159,6 @@ class StringToOffsetTable {
110159
}
111160
O << "\"";
112161
}
113-
114-
/// Emit the string using character literals. MSVC has a limitation that
115-
/// string literals cannot be longer than 64K.
116-
void EmitCharArray(raw_ostream &O) {
117-
assert(AggregateString.find(')') == std::string::npos &&
118-
"can't emit raw string with closing parens");
119-
int Count = 0;
120-
O << ' ';
121-
for (char C : AggregateString) {
122-
O << " \'";
123-
O.write_escaped(StringRef(&C, 1));
124-
O << "\',";
125-
Count++;
126-
if (Count > 14) {
127-
O << "\n ";
128-
Count = 0;
129-
}
130-
}
131-
O << '\n';
132-
}
133162
};
134163

135164
} // end namespace llvm

llvm/lib/IR/Intrinsics.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "llvm/IR/Intrinsics.h"
1414
#include "llvm/ADT/StringExtras.h"
15+
#include "llvm/ADT/StringTable.h"
1516
#include "llvm/IR/Function.h"
1617
#include "llvm/IR/IntrinsicsAArch64.h"
1718
#include "llvm/IR/IntrinsicsAMDGPU.h"
@@ -40,7 +41,7 @@ using namespace llvm;
4041

4142
StringRef Intrinsic::getBaseName(ID id) {
4243
assert(id < num_intrinsics && "Invalid intrinsic ID!");
43-
return IntrinsicNameTable + IntrinsicNameOffsetTable[id];
44+
return IntrinsicNameTable[IntrinsicNameOffsetTable[id]];
4445
}
4546

4647
StringRef Intrinsic::getName(ID id) {
@@ -649,20 +650,20 @@ static int lookupLLVMIntrinsicByName(ArrayRef<unsigned> NameOffsetTable,
649650
// `equal_range` requires the comparison to work with either side being an
650651
// offset or the value. Detect which kind each side is to set up the
651652
// compared strings.
652-
const char *LHSStr;
653+
StringRef LHSStr;
653654
if constexpr (std::is_integral_v<decltype(LHS)>) {
654-
LHSStr = &IntrinsicNameTable[LHS];
655+
LHSStr = IntrinsicNameTable[LHS];
655656
} else {
656657
LHSStr = LHS;
657658
}
658-
const char *RHSStr;
659+
StringRef RHSStr;
659660
if constexpr (std::is_integral_v<decltype(RHS)>) {
660-
RHSStr = &IntrinsicNameTable[RHS];
661+
RHSStr = IntrinsicNameTable[RHS];
661662
} else {
662663
RHSStr = RHS;
663664
}
664-
return strncmp(LHSStr + CmpStart, RHSStr + CmpStart, CmpEnd - CmpStart) <
665-
0;
665+
return strncmp(LHSStr.data() + CmpStart, RHSStr.data() + CmpStart,
666+
CmpEnd - CmpStart) < 0;
666667
};
667668
LastLow = Low;
668669
std::tie(Low, High) = std::equal_range(Low, High, Name.data(), Cmp);
@@ -672,7 +673,7 @@ static int lookupLLVMIntrinsicByName(ArrayRef<unsigned> NameOffsetTable,
672673

673674
if (LastLow == NameOffsetTable.end())
674675
return -1;
675-
StringRef NameFound = &IntrinsicNameTable[*LastLow];
676+
StringRef NameFound = IntrinsicNameTable[*LastLow];
676677
if (Name == NameFound ||
677678
(Name.starts_with(NameFound) && Name[NameFound.size()] == '.'))
678679
return LastLow - NameOffsetTable.begin();
@@ -716,7 +717,7 @@ Intrinsic::ID Intrinsic::lookupIntrinsicID(StringRef Name) {
716717

717718
// If the intrinsic is not overloaded, require an exact match. If it is
718719
// overloaded, require either exact or prefix match.
719-
const auto MatchSize = strlen(&IntrinsicNameTable[NameOffsetTable[Idx]]);
720+
const auto MatchSize = IntrinsicNameTable[NameOffsetTable[Idx]].size();
720721
assert(Name.size() >= MatchSize && "Expected either exact or prefix match");
721722
bool IsExactMatch = Name.size() == MatchSize;
722723
return IsExactMatch || Intrinsic::isOverloaded(ID) ? ID

llvm/test/TableGen/MixedCasedMnemonic.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def :MnemonicAlias<"InstB", "BInst">;
4141

4242
// Check that the matcher lower()s the mnemonics it matches.
4343
// MATCHER: static const char MnemonicTable[] =
44-
// MATCHER-NEXT: "\005ainst\005binst";
44+
// MATCHER-NEXT: "\000\005ainst\005binst";
4545

4646
// Check that aInst appears before BInst in the match table.
4747
// This shows that the mnemonics are sorted in a case-insensitive way,

llvm/test/TableGen/SDNodeInfoEmitter/ambiguous-constraints.td

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ def my_node_b : SDNode<"MyTargetISD::NODE", SDTypeProfile<1, 0, [SDTCisVT<0, f32
1414
// CHECK-NEXT: NODE = ISD::BUILTIN_OP_END,
1515
// CHECK-NEXT: };
1616

17-
// CHECK: static const char MyTargetSDNodeNames[] =
17+
// CHECK: static constexpr char MyTargetSDNodeNamesStorage[] =
18+
// CHECK-NEXT: "\0"
1819
// CHECK-NEXT: "MyTargetISD::NODE\0"
19-
// CHECK-NEXT: "\0";
20+
// CHECK-NEXT: ;
2021

2122
// CHECK: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
2223
// CHECK-NEXT: /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
2324
// CHECK-NEXT: };
2425
// CHECK-EMPTY:
2526
// CHECK-NEXT: static const SDNodeDesc MyTargetSDNodeDescs[] = {
26-
// CHECK-NEXT: {1, 0, 0, 0, 0, 0, 0, 0}, // NODE
27+
// CHECK-NEXT: {1, 0, 0, 0, 0, 1, 0, 0}, // NODE
2728
// CHECK-NEXT: };
2829
// CHECK-EMPTY:
2930
// CHECK-NEXT: static const SDNodeInfo MyTargetGenSDNodeInfo(
@@ -54,18 +55,19 @@ def my_node_2b : SDNode<"MyTargetISD::NODE_2", SDTypeProfile<1, 0, [SDTCisVT<0,
5455
// CHECK-EMPTY:
5556
// CHECK-NEXT: } // namespace llvm::MyTargetISD
5657

57-
// CHECK: static const char MyTargetSDNodeNames[] =
58+
// CHECK: static constexpr char MyTargetSDNodeNamesStorage[] =
59+
// CHECK-NEXT: "\0"
5860
// CHECK-NEXT: "MyTargetISD::NODE_1\0"
5961
// CHECK-NEXT: "MyTargetISD::NODE_2\0"
60-
// CHECK-NEXT: "\0";
62+
// CHECK-NEXT: ;
6163

6264
// CHECK: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
6365
// CHECK-NEXT: /* 0 */ {SDTCisVT, 0, 0, MVT::i32},
6466
// CHECK-NEXT: };
6567
// CHECK-EMPTY:
6668
// CHECK-NEXT: static const SDNodeDesc MyTargetSDNodeDescs[] = {
67-
// CHECK-NEXT: {1, 0, 0, 0, 0, 0, 0, 1}, // NODE_1
68-
// CHECK-NEXT: {1, 0, 0, 0, 0, 20, 0, 0}, // NODE_2
69+
// CHECK-NEXT: {1, 0, 0, 0, 0, 1, 0, 1}, // NODE_1
70+
// CHECK-NEXT: {1, 0, 0, 0, 0, 21, 0, 0}, // NODE_2
6971
// CHECK-NEXT: };
7072
// CHECK-EMPTY:
7173
// CHECK-NEXT: static const SDNodeInfo MyTargetGenSDNodeInfo(

llvm/test/TableGen/SDNodeInfoEmitter/basic.td

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ def MyTarget : Target;
2828
// CHECK-NEXT: #pragma GCC diagnostic push
2929
// CHECK-NEXT: #pragma GCC diagnostic ignored "-Woverlength-strings"
3030
// CHECK-NEXT: #endif
31-
// CHECK-NEXT: static const char MyTargetSDNodeNames[] =
32-
// CHECK-NEXT: "\0";
31+
// CHECK-NEXT: static constexpr char MyTargetSDNodeNamesStorage[] =
32+
// CHECK-NEXT: "\0"
33+
// CHECK-NEXT: ;
3334
// CHECK-NEXT: #ifdef __GNUC__
3435
// CHECK-NEXT: #pragma GCC diagnostic pop
3536
// CHECK-NEXT: #endif
3637
// CHECK-EMPTY:
38+
// CHECK-NEXT: static constexpr llvm::StringTable MyTargetSDNodeNames =
39+
// CHECK-NEXT: MyTargetSDNodeNamesStorage;
40+
// CHECK-EMPTY:
3741
// CHECK-NEXT: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
3842
// CHECK-NEXT: /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
3943
// CHECK-NEXT: };
@@ -70,16 +74,17 @@ def my_noop : SDNode<"MyTargetISD::NOOP", SDTypeProfile<0, 0, []>>;
7074
// CHECK-EMPTY:
7175
// CHECK-NEXT: } // namespace llvm::MyTargetISD
7276

73-
// CHECK: static const char MyTargetSDNodeNames[] =
77+
// CHECK: static constexpr char MyTargetSDNodeNamesStorage[] =
78+
// CHECK-NEXT: "\0"
7479
// CHECK-NEXT: "MyTargetISD::NOOP\0"
75-
// CHECK-NEXT: "\0";
80+
// CHECK-NEXT: ;
7681

7782
// CHECK: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
7883
// CHECK-NEXT: /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
7984
// CHECK-NEXT: };
8085
// CHECK-EMPTY:
8186
// CHECK-NEXT: static const SDNodeDesc MyTargetSDNodeDescs[] = {
82-
// CHECK-NEXT: {0, 0, 0, 0, 0, 0, 0, 0}, // NOOP
87+
// CHECK-NEXT: {0, 0, 0, 0, 0, 1, 0, 0}, // NOOP
8388
// CHECK-NEXT: };
8489
// CHECK-EMPTY:
8590
// CHECK-NEXT: static const SDNodeInfo MyTargetGenSDNodeInfo(
@@ -148,11 +153,12 @@ def my_node_3 : SDNode<
148153
// CHECK-EMPTY:
149154
// CHECK-NEXT: } // namespace llvm::MyTargetISD
150155

151-
// CHECK: static const char MyTargetSDNodeNames[] =
156+
// CHECK: static constexpr char MyTargetSDNodeNamesStorage[] =
157+
// CHECK-NEXT: "\0"
152158
// CHECK-NEXT: "MyTargetISD::NODE_1\0"
153159
// CHECK-NEXT: "MyTargetISD::NODE_2\0"
154160
// CHECK-NEXT: "MyTargetISD::NODE_3\0"
155-
// CHECK-NEXT: "\0";
161+
// CHECK-NEXT: ;
156162

157163
// CHECK: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
158164
// CHECK-NEXT: /* 0 */ {SDTCisVT, 1, 0, MVT::i2},
@@ -173,9 +179,9 @@ def my_node_3 : SDNode<
173179
// CHECK-NEXT: };
174180
// CHECK-EMPTY:
175181
// CHECK-NEXT: static const SDNodeDesc MyTargetSDNodeDescs[] = {
176-
// CHECK-NEXT: {1, 1, 0|1<<SDNPHasChain, 0, 0, 0, 0, 2}, // NODE_1
177-
// CHECK-NEXT: {3, 1, 0|1<<SDNPVariadic|1<<SDNPMemOperand, 0, 42, 20, 11, 4}, // NODE_2
178-
// CHECK-NEXT: {2, -1, 0|1<<SDNPHasChain|1<<SDNPOutGlue|1<<SDNPInGlue|1<<SDNPOptInGlue, 0|1<<SDNFIsStrictFP, 24, 40, 2, 13}, // NODE_3
182+
// CHECK-NEXT: {1, 1, 0|1<<SDNPHasChain, 0, 0, 1, 0, 2}, // NODE_1
183+
// CHECK-NEXT: {3, 1, 0|1<<SDNPVariadic|1<<SDNPMemOperand, 0, 42, 21, 11, 4}, // NODE_2
184+
// CHECK-NEXT: {2, -1, 0|1<<SDNPHasChain|1<<SDNPOutGlue|1<<SDNPInGlue|1<<SDNPOptInGlue, 0|1<<SDNFIsStrictFP, 24, 41, 2, 13}, // NODE_3
179185
// CHECK-NEXT: };
180186
// CHECK-EMPTY:
181187
// CHECK-NEXT: static const SDNodeInfo MyTargetGenSDNodeInfo(

llvm/test/TableGen/SDNodeInfoEmitter/namespace.td

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ def node_2 : SDNode<"MyCustomISD::NODE", SDTypeProfile<0, 1, [SDTCisVT<0, i2>]>>
1919
// EMPTY-EMPTY:
2020
// EMPTY-NEXT: } // namespace llvm::EmptyISD
2121

22-
// EMPTY: static const char MyTargetSDNodeNames[] =
23-
// EMPTY-NEXT: "\0";
22+
// EMPTY: static constexpr char MyTargetSDNodeNamesStorage[] =
23+
// EMPTY-NEXT: "\0"
24+
// EMPTY-NEXT: ;
2425

2526
// EMPTY: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
2627
// EMPTY-NEXT: /* dummy */ {SDTCisVT, 0, 0, MVT::INVALID_SIMPLE_VALUE_TYPE}
@@ -43,18 +44,19 @@ def node_2 : SDNode<"MyCustomISD::NODE", SDTypeProfile<0, 1, [SDTCisVT<0, i2>]>>
4344
// COMMON-EMPTY:
4445
// COMMON-NEXT: } // namespace llvm::[[NS]]
4546

46-
// COMMON: static const char MyTargetSDNodeNames[] =
47+
// COMMON: static constexpr char MyTargetSDNodeNamesStorage[] =
48+
// COMMON-NEXT: "\0"
4749
// COMMON-NEXT: "[[NS]]::NODE\0"
48-
// COMMON-NEXT: "\0";
50+
// COMMON-NEXT: ;
4951

5052
// COMMON: static const SDTypeConstraint MyTargetSDTypeConstraints[] = {
5153
// TARGET-NEXT: /* 0 */ {SDTCisVT, 0, 0, MVT::i1},
5254
// CUSTOM-NEXT: /* 0 */ {SDTCisVT, 0, 0, MVT::i2},
5355
// COMMON-NEXT: };
5456
// COMMON-EMPTY:
5557
// COMMON-NEXT: static const SDNodeDesc MyTargetSDNodeDescs[] = {
56-
// TARGET-NEXT: {1, 0, 0, 0, 0, 0, 0, 1}, // NODE
57-
// CUSTOM-NEXT: {0, 1, 0, 0, 0, 0, 0, 1}, // NODE
58+
// TARGET-NEXT: {1, 0, 0, 0, 0, 1, 0, 1}, // NODE
59+
// CUSTOM-NEXT: {0, 1, 0, 0, 0, 1, 0, 1}, // NODE
5860
// COMMON-NEXT: };
5961
// COMMON-EMPTY:
6062
// COMMON-NEXT: static const SDNodeInfo MyTargetGenSDNodeInfo(

0 commit comments

Comments
 (0)