[clang] [llvm] [RISC-V][RVY] Fix round-trip and order of arch strings with 'y' (PR #227865)
Alexander Richardson via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 07:52:23 PDT 2026
https://github.com/arichardson updated https://github.com/llvm/llvm-project/pull/227865
>From eae77dbde467be557c11504f1e56738642eeb2fb Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Wed, 30 Sep 2026 13:50:51 -0700
Subject: [PATCH 1/4] [RISC-V][RVY] Fix round-trip and order of arch strings
with 'y'
Because 'y' is not in AllStdExts, singleLetterExtensionRank previously
fell through to the unknown single-letter extension case, placing 'y'
after other single-letter extensions in RISCVISAInfo::toString() and
including 'i' in the output (e.g. 'rv64i2p1_m2p0_y0p910').
Rank 'y' immediately after 'i' and 'e', omit 'i' in RISCVISAInfo::toString()
when 'y' is present, and teach RISCVISAInfo::parseNormalizedArchString()
to accept 'y' as a base ISA and implicitly add the default 'i' extension
version so that normalized RVY arch strings and '.attribute arch'
directives round-trip cleanly. Also simplify RISCVISAInfo::parseArchString()
by merging the 'y' base ISA case with 'i' and 'e', as updateImplication()
already adds 'i'. Support for combining 'e' and 'y' in arch strings is
deferred until the longer base name proposal
(https://lists.riscv.org/g/tech-unprivileged/topic/longer_base_name_proposal/116854896)
is adopted.
This commit was created with the help of AI tools
Co-authored-by: Jessica Clarke <jrtc27 at jrtc27.com>
---
.../Driver/print-supported-extensions-riscv.c | 2 +-
llvm/lib/Support/RISCVISAUtils.cpp | 6 ++++--
llvm/test/MC/RISCV/rvy-build-attributes.s | 14 +++++++-------
llvm/unittests/TargetParser/RISCVISAInfoTest.cpp | 15 +++++++++++----
4 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/clang/test/Driver/print-supported-extensions-riscv.c b/clang/test/Driver/print-supported-extensions-riscv.c
index f916bef0a6519..1d1714314d6e2 100644
--- a/clang/test/Driver/print-supported-extensions-riscv.c
+++ b/clang/test/Driver/print-supported-extensions-riscv.c
@@ -247,8 +247,8 @@
// CHECK-NEXT: xwchc 2.2 'Xwchc' (WCH/QingKe additional compressed opcodes)
// CHECK-EMPTY:
// CHECK-NEXT: Experimental extensions
-// CHECK-NEXT: p 0.21 'P' ('Base P' (Packed SIMD))
// CHECK-NEXT: y 0.910 'Y' ('Base Y' (CHERI))
+// CHECK-NEXT: p 0.21 'P' ('Base P' (Packed SIMD))
// CHECK-NEXT: zibi 0.1 'Zibi' (Branch with Immediate)
// CHECK-NEXT: zicfilp 1.0 'Zicfilp' (Landing pad)
// CHECK-NEXT: zilx 0.1 'Zilx' (Indexed Integer Load Instructions)
diff --git a/llvm/lib/Support/RISCVISAUtils.cpp b/llvm/lib/Support/RISCVISAUtils.cpp
index d6b002e66e7ab..2166d2f30bbb9 100644
--- a/llvm/lib/Support/RISCVISAUtils.cpp
+++ b/llvm/lib/Support/RISCVISAUtils.cpp
@@ -44,15 +44,17 @@ static unsigned singleLetterExtensionRank(char Ext) {
return 0;
case 'e':
return 1;
+ case 'y':
+ return 2;
}
size_t Pos = RISCVISAUtils::AllStdExts.find(Ext);
if (Pos != StringRef::npos)
- return Pos + 2; // Skip 'e' and 'i' from above.
+ return Pos + 3; // Skip 'e', 'i', and 'y' from above.
// If we got an unknown extension letter, then give it an alphabetical
// order, but after all known standard extensions.
- return 2 + RISCVISAUtils::AllStdExts.size() + (Ext - 'a');
+ return 3 + RISCVISAUtils::AllStdExts.size() + (Ext - 'a');
}
// Get the rank for multi-letter extension, lower value meaning higher
diff --git a/llvm/test/MC/RISCV/rvy-build-attributes.s b/llvm/test/MC/RISCV/rvy-build-attributes.s
index f7db55b382ec8..b5abbcb31a8d6 100644
--- a/llvm/test/MC/RISCV/rvy-build-attributes.s
+++ b/llvm/test/MC/RISCV/rvy-build-attributes.s
@@ -14,22 +14,22 @@
# RUN: -mattr=+experimental-y,+zce,+f | FileCheck %s --check-prefix=RV32Y-ZCE-F
## RV64Y + D + C: y is enabled, so zcd is not implied.
-# RV64YDC: .attribute 5, "rv64i2p1_f2p2_d2p2_c2p0_y0p910_zicsr2p0_zca1p0"
+# RV64YDC: .attribute 5, "rv64i2p1_y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
## RV32Y + F + C: y is enabled, so zcf is not implied.
-# RV32YFC: .attribute 5, "rv32i2p1_f2p2_c2p0_y0p910_zicsr2p0_zca1p0"
+# RV32YFC: .attribute 5, "rv32i2p1_y0p910_f2p2_c2p0_zicsr2p0_zca1p0"
## RV32Y + ZCA + F: zca + y implies c on RV32 (y replaces zcf in the implication).
-# RV32Y-ZCA-F: .attribute 5, "rv32i2p1_f2p2_c2p0_y0p910_zicsr2p0_zca1p0"
+# RV32Y-ZCA-F: .attribute 5, "rv32i2p1_y0p910_f2p2_c2p0_zicsr2p0_zca1p0"
## RV64Y + ZCA + D: zca + y implies c on RV64 (y replaces zcd in the implication).
-# RV64Y-ZCA-D: .attribute 5, "rv64i2p1_f2p2_d2p2_c2p0_y0p910_zicsr2p0_zca1p0"
+# RV64Y-ZCA-D: .attribute 5, "rv64i2p1_y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
## RV64Y + ZCA + ZCB: zca + zcb + y does NOT imply zce on RV64Y because Zce is incompatible. zca also implies c.
-# RV64Y-ZCA-ZCB: .attribute 5, "rv64i2p1_c2p0_y0p910_zca1p0_zcb1p0"
+# RV64Y-ZCA-ZCB: .attribute 5, "rv64i2p1_y0p910_c2p0_zca1p0_zcb1p0"
## RV32Y + ZCE: zce implies zca, zcb, zcmp, zcmt. zca implies c.
-# RV32Y-ZCE: .attribute 5, "rv32i2p1_c2p0_y0p910_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
+# RV32Y-ZCE: .attribute 5, "rv32i2p1_y0p910_c2p0_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
## RV32Y + ZCE + F: y is enabled, so zcf is not implied by zce + f. zca + y + f implies c.
-# RV32Y-ZCE-F: .attribute 5, "rv32i2p1_f2p2_c2p0_y0p910_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
+# RV32Y-ZCE-F: .attribute 5, "rv32i2p1_y0p910_f2p2_c2p0_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
diff --git a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
index 3994eaf599a82..9c6077bb41e80 100644
--- a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
@@ -415,6 +415,13 @@ TEST(RISCVISAInfoTest, CanonicalExtensionOrderVP) {
Info.toString(),
"rv64i2p1_f2p2_d2p2_v1p0_p0p21_zicsr2p0_zmmul1p0_zba1p0_zbb1p0_zve32f1p0_"
"zve32x1p0_zve64d1p0_zve64f1p0_zve64x1p0_zvl128b1p0_zvl32b1p0_zvl64b1p0");
+
+ auto MaybeRVY = RISCVISAInfo::parseArchString("rv64y0p910_m_a_f_d_c", true);
+ ASSERT_THAT_EXPECTED(MaybeRVY, Succeeded());
+ // The canonical string should place 'y' immediately after 'i' (before 'm').
+ EXPECT_EQ((*MaybeRVY)->toString(),
+ "rv64i2p1_y0p910_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zmmul1p0_"
+ "zaamo1p0_zalrsc1p0_zca1p0");
}
TEST(ParseArchString, RejectsUnrecognizedExtensionNamesByDefault) {
@@ -1004,10 +1011,10 @@ TEST(OrderedExtensionMap, ExtensionsAreCorrectlyOrdered) {
for (const auto &Ext : Exts)
ExtNames.push_back(Ext.first);
- // FIXME: 'l' and 'y' should be ordered after 'i', 'm', 'c'.
+ // FIXME: 'l' should be ordered after 'i', 'm', 'c'.
EXPECT_THAT(ExtNames,
- ElementsAre("i", "m", "l", "c", "y", "zicsr", "zmfoo", "zfinx",
- "zzfoo", "sbar", "sfoo", "xbar", "xfoo"));
+ ElementsAre("i", "y", "m", "l", "c", "zicsr", "zmfoo", "zfinx",
+ "zzfoo", "sbar", "sfoo", "xbar", "xfoo"));
}
TEST(ParseArchString, ZceImplication) {
@@ -1673,8 +1680,8 @@ R"(All available -march extensions for RISC-V
xwchc 2.2
Experimental extensions
- p 0.21
y 0.910
+ p 0.21
zibi 0.1
zicfilp 1.0 This is a long dummy description
zilx 0.1
>From b777a632f28607dbc07b7371357a528f7eeb0622 Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Thu, 1 Oct 2026 00:02:12 -0700
Subject: [PATCH 2/4] fix round-tripping and simpify the parsing to also allow
rv32iy (matching long base name proposal)
---
llvm/lib/TargetParser/RISCVISAInfo.cpp | 20 ++--
llvm/test/MC/RISCV/rvy-build-attributes.s | 23 +++--
.../TargetParser/RISCVISAInfoTest.cpp | 96 ++++++++++++-------
3 files changed, 87 insertions(+), 52 deletions(-)
diff --git a/llvm/lib/TargetParser/RISCVISAInfo.cpp b/llvm/lib/TargetParser/RISCVISAInfo.cpp
index fae0fdac3f824..6899242358274 100644
--- a/llvm/lib/TargetParser/RISCVISAInfo.cpp
+++ b/llvm/lib/TargetParser/RISCVISAInfo.cpp
@@ -620,23 +620,23 @@ RISCVISAInfo::parseArchString(StringRef Arch, bool EnableExperimentalExtension,
return std::move(E);
ISAInfo->Exts[std::string(1, Baseline)] = {Major, Minor};
- break;
- case 'y': {
- // If the first character is 'y', this is equivalent to "iy".
- // TODO: arch string syntax for RVE+RVY (and y in non-first position) will
- // be included following conclusion of "long base name" syntax
- // https://lists.riscv.org/g/tech-unprivileged/message/1134
+ Arch = Arch.drop_front(ConsumeLength);
+ ConsumeLength = 0;
+ // Allow 'y' immediately after 'i' or 'e' (e.g. rv64iy0p910, rv32ey0p910,
+ // or normalized strings such as rv64i2p1_y0p910).
+ if (!Arch.consume_front("y") && !Arch.consume_front("_y"))
+ break;
+ [[fallthrough]];
+ case 'y':
+ // If the first character is 'y', this is a shorthand for "iy" ('i' will be
+ // added by updateImplication()).
if (auto E = getExtensionVersion("y", Arch, Major, Minor, ConsumeLength,
EnableExperimentalExtension,
ExperimentalExtensionVersionCheck))
return std::move(E);
ISAInfo->Exts["y"] = {Major, Minor};
- auto IVersion = findDefaultVersion("i");
- assert(IVersion && "Default 'i' extension version not found?");
- ISAInfo->Exts["i"] = {IVersion->Major, IVersion->Minor};
break;
- }
case 'g':
// g expands to extensions in RISCVGImplications.
if (!Arch.empty() && isDigit(Arch.front()))
diff --git a/llvm/test/MC/RISCV/rvy-build-attributes.s b/llvm/test/MC/RISCV/rvy-build-attributes.s
index b5abbcb31a8d6..a69aadb03ad48 100644
--- a/llvm/test/MC/RISCV/rvy-build-attributes.s
+++ b/llvm/test/MC/RISCV/rvy-build-attributes.s
@@ -1,17 +1,26 @@
+## Pipe the assembly output through a second llvm-mc invocation to ensure the
+## emitted '.attribute 5, "..."' arch string round-trips cleanly.
# RUN: llvm-mc %s -triple=riscv64 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+d,+c | FileCheck %s --check-prefix=RV64YDC
+# RUN: -mattr=+experimental-y,+d,+c | llvm-mc -triple=riscv64 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV64YDC
# RUN: llvm-mc %s -triple=riscv32 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+f,+c | FileCheck %s --check-prefix=RV32YFC
+# RUN: -mattr=+experimental-y,+f,+c | llvm-mc -triple=riscv32 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV32YFC
# RUN: llvm-mc %s -triple=riscv32 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+zca,+f | FileCheck %s --check-prefix=RV32Y-ZCA-F
+# RUN: -mattr=+experimental-y,+zca,+f | llvm-mc -triple=riscv32 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV32Y-ZCA-F
# RUN: llvm-mc %s -triple=riscv64 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+zca,+d | FileCheck %s --check-prefix=RV64Y-ZCA-D
+# RUN: -mattr=+experimental-y,+zca,+d | llvm-mc -triple=riscv64 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV64Y-ZCA-D
# RUN: llvm-mc %s -triple=riscv64 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+zca,+zcb | FileCheck %s --check-prefix=RV64Y-ZCA-ZCB
+# RUN: -mattr=+experimental-y,+zca,+zcb | llvm-mc -triple=riscv64 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV64Y-ZCA-ZCB
# RUN: llvm-mc %s -triple=riscv32 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+zce | FileCheck %s --check-prefix=RV32Y-ZCE
+# RUN: -mattr=+experimental-y,+zce | llvm-mc -triple=riscv32 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV32Y-ZCE
# RUN: llvm-mc %s -triple=riscv32 -filetype=asm -riscv-add-build-attributes \
-# RUN: -mattr=+experimental-y,+zce,+f | FileCheck %s --check-prefix=RV32Y-ZCE-F
+# RUN: -mattr=+experimental-y,+zce,+f | llvm-mc -triple=riscv32 -filetype=asm \
+# RUN: | FileCheck %s --check-prefix=RV32Y-ZCE-F
## RV64Y + D + C: y is enabled, so zcd is not implied.
# RV64YDC: .attribute 5, "rv64i2p1_y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
diff --git a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
index 9c6077bb41e80..1ac95e24176b8 100644
--- a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
@@ -212,32 +212,52 @@ TEST(ParseArchString, RejectsInvalidBaseISA) {
}
TEST(ParseArchString, RejectsInvalidYPosition) {
- // y in non-first position is rejected.
- for (StringRef Input :
- {"rv32ey0p910", "rv64ey0p910", "rv32iy0p910", "rv64iy0p910"}) {
+ // 'y' is only allowed as a singular base ISA ('rv32y'/'rv64y') or immediately
+ // after 'i' or 'e' ('rv32iy'/'rv32ey'). Anything else should be rejected.
+ for (StringRef Input : {"rv32gy0p910", "rv64gy0p910", "rv32imy0p910",
+ "rv64imy0p910", "rv32i_m_y0p910", "rv64y0p910_y0p910",
+ "rv64iy0p910_y0p910"}) {
EXPECT_EQ(toString(RISCVISAInfo::parseArchString(Input, true).takeError()),
"invalid standard user-level extension 'y'");
}
- for (StringRef Input : {"rv32ey", "rv64ey", "rv32iy", "rv64iy"}) {
+ for (StringRef Input :
+ {"rv32gy", "rv64gy", "rv32imy", "rv64imy", "rv32yy", "rv64iyy"}) {
EXPECT_EQ(
toString(RISCVISAInfo::parseArchString(Input, true, false).takeError()),
"invalid standard user-level extension 'y'");
}
}
-TEST(ParseArchString, MissingBaseISA) {
+TEST(ParseArchString, AcceptsRVYBaseISA) {
// With version check enabled (default), we must specify the version for
- // experimental extension 'y'.
- auto MaybeRV32Y = RISCVISAInfo::parseArchString("rv32y0p910", true);
- ASSERT_THAT_EXPECTED(MaybeRV32Y, Succeeded());
- RISCVISAInfo &InfoRV32Y = **MaybeRV32Y;
- const auto &ExtsRV32Y = InfoRV32Y.getExtensions();
- EXPECT_EQ(ExtsRV32Y.size(), 2UL); // i, y
- EXPECT_TRUE(ExtsRV32Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
- EXPECT_TRUE(ExtsRV32Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
- EXPECT_EQ(InfoRV32Y.getXLen(), 32U);
-
- // rv32y0p910m should succeed and contain i, m, y0p910, zmmul
+ // experimental extension 'y'. Both 'rv32y0p910' (shorthand) and explicit
+ // 'rv32iy0p910' / 'rv32i2p1_y0p910' produce the same result.
+ for (StringRef Input : {"rv32y0p910", "rv32iy0p910", "rv32i2p1_y0p910"}) {
+ auto MaybeRV32Y = RISCVISAInfo::parseArchString(Input, true);
+ ASSERT_THAT_EXPECTED(MaybeRV32Y, Succeeded());
+ RISCVISAInfo &InfoRV32Y = **MaybeRV32Y;
+ const auto &ExtsRV32Y = InfoRV32Y.getExtensions();
+ EXPECT_EQ(ExtsRV32Y.size(), 2UL); // i, y
+ EXPECT_TRUE(ExtsRV32Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
+ EXPECT_TRUE(ExtsRV32Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV32Y.getXLen(), 32U);
+ EXPECT_EQ(InfoRV32Y.toString(), "rv32i2p1_y0p910");
+ }
+
+ for (StringRef Input : {"rv32ey0p910", "rv32e2p0_y0p910"}) {
+ auto MaybeRV32EY = RISCVISAInfo::parseArchString(Input, true);
+ ASSERT_THAT_EXPECTED(MaybeRV32EY, Succeeded());
+ RISCVISAInfo &InfoRV32EY = **MaybeRV32EY;
+ const auto &ExtsRV32EY = InfoRV32EY.getExtensions();
+ EXPECT_EQ(ExtsRV32EY.size(), 2UL); // e, y
+ EXPECT_TRUE(ExtsRV32EY.at("e") == (RISCVISAUtils::ExtensionVersion{2, 0}));
+ EXPECT_TRUE(ExtsRV32EY.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV32EY.getXLen(), 32U);
+ EXPECT_EQ(InfoRV32EY.toString(), "rv32e2p0_y0p910");
+ }
+
+ // rv32y0p910m should succeed and contain i, m, y0p910, zmmul, with 'y'
+ // ordered between 'i' and 'm' and round-tripping through both parsers.
auto MaybeRV32YM = RISCVISAInfo::parseArchString("rv32y0p910m", true);
ASSERT_THAT_EXPECTED(MaybeRV32YM, Succeeded());
RISCVISAInfo &InfoRV32YM = **MaybeRV32YM;
@@ -248,20 +268,33 @@ TEST(ParseArchString, MissingBaseISA) {
(RISCVISAUtils::ExtensionVersion{2, 0}));
EXPECT_TRUE(InfoRV32YM.getExtensions().at("y") ==
(RISCVISAUtils::ExtensionVersion{0, 910}));
+ std::string RV32YMStr = InfoRV32YM.toString();
+ EXPECT_EQ(RV32YMStr, "rv32i2p1_y0p910_m2p0_zmmul1p0");
+ auto RoundTripArch = RISCVISAInfo::parseArchString(RV32YMStr, true);
+ ASSERT_THAT_EXPECTED(RoundTripArch, Succeeded());
+ EXPECT_EQ((*RoundTripArch)->toString(), RV32YMStr);
+ auto RoundTripNorm = RISCVISAInfo::parseNormalizedArchString(RV32YMStr);
+ ASSERT_THAT_EXPECTED(RoundTripNorm, Succeeded());
+ EXPECT_EQ((*RoundTripNorm)->toString(), RV32YMStr);
// We can also parse it without version if we disable the version check.
- auto MaybeRV32YNoVal = RISCVISAInfo::parseArchString("rv32y", true, false);
- ASSERT_THAT_EXPECTED(MaybeRV32YNoVal, Succeeded());
- EXPECT_EQ((*MaybeRV32YNoVal)->getExtensions().size(), 2UL);
-
- auto MaybeRV64Y = RISCVISAInfo::parseArchString("rv64y0p910", true);
- ASSERT_THAT_EXPECTED(MaybeRV64Y, Succeeded());
- RISCVISAInfo &InfoRV64Y = **MaybeRV64Y;
- const auto &ExtsRV64Y = InfoRV64Y.getExtensions();
- EXPECT_EQ(ExtsRV64Y.size(), 2UL); // i, y
- EXPECT_TRUE(ExtsRV64Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
- EXPECT_TRUE(ExtsRV64Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
- EXPECT_EQ(InfoRV64Y.getXLen(), 64U);
+ for (StringRef Input : {"rv32y", "rv32iy", "rv32ey"}) {
+ auto MaybeNoVal = RISCVISAInfo::parseArchString(Input, true, false);
+ ASSERT_THAT_EXPECTED(MaybeNoVal, Succeeded());
+ EXPECT_EQ((*MaybeNoVal)->getExtensions().size(), 2UL);
+ }
+
+ for (StringRef Input : {"rv64y0p910", "rv64iy0p910", "rv64i2p1_y0p910"}) {
+ auto MaybeRV64Y = RISCVISAInfo::parseArchString(Input, true);
+ ASSERT_THAT_EXPECTED(MaybeRV64Y, Succeeded());
+ RISCVISAInfo &InfoRV64Y = **MaybeRV64Y;
+ const auto &ExtsRV64Y = InfoRV64Y.getExtensions();
+ EXPECT_EQ(ExtsRV64Y.size(), 2UL); // i, y
+ EXPECT_TRUE(ExtsRV64Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
+ EXPECT_TRUE(ExtsRV64Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV64Y.getXLen(), 64U);
+ EXPECT_EQ(InfoRV64Y.toString(), "rv64i2p1_y0p910");
+ }
}
TEST(ParseArchString, RejectsUnsupportedBaseISA) {
@@ -415,13 +448,6 @@ TEST(RISCVISAInfoTest, CanonicalExtensionOrderVP) {
Info.toString(),
"rv64i2p1_f2p2_d2p2_v1p0_p0p21_zicsr2p0_zmmul1p0_zba1p0_zbb1p0_zve32f1p0_"
"zve32x1p0_zve64d1p0_zve64f1p0_zve64x1p0_zvl128b1p0_zvl32b1p0_zvl64b1p0");
-
- auto MaybeRVY = RISCVISAInfo::parseArchString("rv64y0p910_m_a_f_d_c", true);
- ASSERT_THAT_EXPECTED(MaybeRVY, Succeeded());
- // The canonical string should place 'y' immediately after 'i' (before 'm').
- EXPECT_EQ((*MaybeRVY)->toString(),
- "rv64i2p1_y0p910_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zmmul1p0_"
- "zaamo1p0_zalrsc1p0_zca1p0");
}
TEST(ParseArchString, RejectsUnrecognizedExtensionNamesByDefault) {
>From 27ec49e2899a4ae715d22ff22bb51eafe81f6775 Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Thu, 1 Oct 2026 00:07:34 -0700
Subject: [PATCH 3/4] clang-format
---
llvm/unittests/TargetParser/RISCVISAInfoTest.cpp | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
index 1ac95e24176b8..6f4d171f2c783 100644
--- a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
@@ -214,9 +214,9 @@ TEST(ParseArchString, RejectsInvalidBaseISA) {
TEST(ParseArchString, RejectsInvalidYPosition) {
// 'y' is only allowed as a singular base ISA ('rv32y'/'rv64y') or immediately
// after 'i' or 'e' ('rv32iy'/'rv32ey'). Anything else should be rejected.
- for (StringRef Input : {"rv32gy0p910", "rv64gy0p910", "rv32imy0p910",
- "rv64imy0p910", "rv32i_m_y0p910", "rv64y0p910_y0p910",
- "rv64iy0p910_y0p910"}) {
+ for (StringRef Input :
+ {"rv32gy0p910", "rv64gy0p910", "rv32imy0p910", "rv64imy0p910",
+ "rv32i_m_y0p910", "rv64y0p910_y0p910", "rv64iy0p910_y0p910"}) {
EXPECT_EQ(toString(RISCVISAInfo::parseArchString(Input, true).takeError()),
"invalid standard user-level extension 'y'");
}
@@ -251,7 +251,8 @@ TEST(ParseArchString, AcceptsRVYBaseISA) {
const auto &ExtsRV32EY = InfoRV32EY.getExtensions();
EXPECT_EQ(ExtsRV32EY.size(), 2UL); // e, y
EXPECT_TRUE(ExtsRV32EY.at("e") == (RISCVISAUtils::ExtensionVersion{2, 0}));
- EXPECT_TRUE(ExtsRV32EY.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_TRUE(ExtsRV32EY.at("y") ==
+ (RISCVISAUtils::ExtensionVersion{0, 910}));
EXPECT_EQ(InfoRV32EY.getXLen(), 32U);
EXPECT_EQ(InfoRV32EY.toString(), "rv32e2p0_y0p910");
}
>From 5c2fae208e6a06359ac7b233b971df1442210891 Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Thu, 1 Oct 2026 07:51:46 -0700
Subject: [PATCH 4/4] use Jessica's suggested approach
---
llvm/lib/TargetParser/RISCVISAInfo.cpp | 33 +++---
llvm/test/MC/RISCV/rvy-build-attributes.s | 14 +--
.../TargetParser/RISCVISAInfoTest.cpp | 108 +++++++++---------
3 files changed, 78 insertions(+), 77 deletions(-)
diff --git a/llvm/lib/TargetParser/RISCVISAInfo.cpp b/llvm/lib/TargetParser/RISCVISAInfo.cpp
index 6899242358274..90d7fda8a28fc 100644
--- a/llvm/lib/TargetParser/RISCVISAInfo.cpp
+++ b/llvm/lib/TargetParser/RISCVISAInfo.cpp
@@ -483,10 +483,15 @@ RISCVISAInfo::parseNormalizedArchString(StringRef Arch) {
else if (Arch.consume_front("rv64"))
XLen = 64;
- if (XLen == 0 || Arch.empty() || (Arch[0] != 'i' && Arch[0] != 'e'))
+ if (XLen == 0 || Arch.empty() ||
+ (Arch[0] != 'i' && Arch[0] != 'e' && Arch[0] != 'y'))
return getError("arch string must begin with valid base ISA");
std::unique_ptr<RISCVISAInfo> ISAInfo(new RISCVISAInfo(XLen));
+ // Plain 'y' always implies 'i' (which is omitted in the normalized arch
+ // string). Y+E requires a long base name arch string.
+ if (Arch[0] == 'y')
+ ISAInfo->Exts["i"] = *findDefaultVersion("i");
// Each extension is of the form ${name}${major_version}p${minor_version}
// and separated by _. Split by _ and then extract the name and version
@@ -613,29 +618,17 @@ RISCVISAInfo::parseArchString(StringRef Arch, bool EnableExperimentalExtension,
"\' should be 'e', 'i', 'g' or 'y'");
case 'e':
case 'i':
- // Baseline is `i` or `e`
+ case 'y':
+ // Baseline is 'i', 'e', or 'y' (which implies 'i' in updateImplication).
+ // TODO: arch string syntax for RVE+RVY (and y in non-first position) will
+ // be included following conclusion of "long base name" syntax
+ // https://lists.riscv.org/g/tech-unprivileged/message/1134
if (auto E = getExtensionVersion(
StringRef(&Baseline, 1), Arch, Major, Minor, ConsumeLength,
EnableExperimentalExtension, ExperimentalExtensionVersionCheck))
return std::move(E);
ISAInfo->Exts[std::string(1, Baseline)] = {Major, Minor};
- Arch = Arch.drop_front(ConsumeLength);
- ConsumeLength = 0;
- // Allow 'y' immediately after 'i' or 'e' (e.g. rv64iy0p910, rv32ey0p910,
- // or normalized strings such as rv64i2p1_y0p910).
- if (!Arch.consume_front("y") && !Arch.consume_front("_y"))
- break;
- [[fallthrough]];
- case 'y':
- // If the first character is 'y', this is a shorthand for "iy" ('i' will be
- // added by updateImplication()).
- if (auto E = getExtensionVersion("y", Arch, Major, Minor, ConsumeLength,
- EnableExperimentalExtension,
- ExperimentalExtensionVersionCheck))
- return std::move(E);
-
- ISAInfo->Exts["y"] = {Major, Minor};
break;
case 'g':
// g expands to extensions in RISCVGImplications.
@@ -1063,6 +1056,10 @@ std::string RISCVISAInfo::toString() const {
ListSeparator LS("_");
for (auto const &Ext : Exts) {
StringRef ExtName = Ext.first;
+ // Plain 'y' always implies 'i' (which is omitted in the normalized arch
+ // string). Y+E requires a long base name arch string.
+ if (ExtName == "i" && Exts.count("y"))
+ continue;
auto ExtInfo = Ext.second;
Arch << LS << ExtName;
Arch << ExtInfo.Major << "p" << ExtInfo.Minor;
diff --git a/llvm/test/MC/RISCV/rvy-build-attributes.s b/llvm/test/MC/RISCV/rvy-build-attributes.s
index a69aadb03ad48..512db1e95f529 100644
--- a/llvm/test/MC/RISCV/rvy-build-attributes.s
+++ b/llvm/test/MC/RISCV/rvy-build-attributes.s
@@ -23,22 +23,22 @@
# RUN: | FileCheck %s --check-prefix=RV32Y-ZCE-F
## RV64Y + D + C: y is enabled, so zcd is not implied.
-# RV64YDC: .attribute 5, "rv64i2p1_y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
+# RV64YDC: .attribute 5, "rv64y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
## RV32Y + F + C: y is enabled, so zcf is not implied.
-# RV32YFC: .attribute 5, "rv32i2p1_y0p910_f2p2_c2p0_zicsr2p0_zca1p0"
+# RV32YFC: .attribute 5, "rv32y0p910_f2p2_c2p0_zicsr2p0_zca1p0"
## RV32Y + ZCA + F: zca + y implies c on RV32 (y replaces zcf in the implication).
-# RV32Y-ZCA-F: .attribute 5, "rv32i2p1_y0p910_f2p2_c2p0_zicsr2p0_zca1p0"
+# RV32Y-ZCA-F: .attribute 5, "rv32y0p910_f2p2_c2p0_zicsr2p0_zca1p0"
## RV64Y + ZCA + D: zca + y implies c on RV64 (y replaces zcd in the implication).
-# RV64Y-ZCA-D: .attribute 5, "rv64i2p1_y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
+# RV64Y-ZCA-D: .attribute 5, "rv64y0p910_f2p2_d2p2_c2p0_zicsr2p0_zca1p0"
## RV64Y + ZCA + ZCB: zca + zcb + y does NOT imply zce on RV64Y because Zce is incompatible. zca also implies c.
-# RV64Y-ZCA-ZCB: .attribute 5, "rv64i2p1_y0p910_c2p0_zca1p0_zcb1p0"
+# RV64Y-ZCA-ZCB: .attribute 5, "rv64y0p910_c2p0_zca1p0_zcb1p0"
## RV32Y + ZCE: zce implies zca, zcb, zcmp, zcmt. zca implies c.
-# RV32Y-ZCE: .attribute 5, "rv32i2p1_y0p910_c2p0_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
+# RV32Y-ZCE: .attribute 5, "rv32y0p910_c2p0_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
## RV32Y + ZCE + F: y is enabled, so zcf is not implied by zce + f. zca + y + f implies c.
-# RV32Y-ZCE-F: .attribute 5, "rv32i2p1_y0p910_f2p2_c2p0_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
+# RV32Y-ZCE-F: .attribute 5, "rv32y0p910_f2p2_c2p0_zicsr2p0_zca1p0_zcb1p0_zce1p0_zcmp1p0_zcmt1p0"
diff --git a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
index 6f4d171f2c783..80da337b451db 100644
--- a/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVISAInfoTest.cpp
@@ -108,6 +108,17 @@ TEST(ParseNormalizedArchString, AcceptsValidBaseISAsAndSetsXLen) {
(RISCVISAUtils::ExtensionVersion{2, 0}));
EXPECT_EQ(InfoRV32E.getXLen(), 32U);
+ auto MaybeRV32Y = RISCVISAInfo::parseNormalizedArchString("rv32y0p910");
+ ASSERT_THAT_EXPECTED(MaybeRV32Y, Succeeded());
+ RISCVISAInfo &InfoRV32Y = **MaybeRV32Y;
+ EXPECT_EQ(InfoRV32Y.getExtensions().size(), 2UL);
+ EXPECT_TRUE(InfoRV32Y.getExtensions().at("i") ==
+ (RISCVISAUtils::ExtensionVersion{2, 1}));
+ EXPECT_TRUE(InfoRV32Y.getExtensions().at("y") ==
+ (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV32Y.getXLen(), 32U);
+ EXPECT_EQ(InfoRV32Y.toString(), "rv32y0p910");
+
auto MaybeRV64I = RISCVISAInfo::parseNormalizedArchString("rv64i2p0");
ASSERT_THAT_EXPECTED(MaybeRV64I, Succeeded());
RISCVISAInfo &InfoRV64I = **MaybeRV64I;
@@ -123,6 +134,17 @@ TEST(ParseNormalizedArchString, AcceptsValidBaseISAsAndSetsXLen) {
EXPECT_TRUE(InfoRV64E.getExtensions().at("e") ==
(RISCVISAUtils::ExtensionVersion{2, 0}));
EXPECT_EQ(InfoRV64E.getXLen(), 64U);
+
+ auto MaybeRV64Y = RISCVISAInfo::parseNormalizedArchString("rv64y0p910");
+ ASSERT_THAT_EXPECTED(MaybeRV64Y, Succeeded());
+ RISCVISAInfo &InfoRV64Y = **MaybeRV64Y;
+ EXPECT_EQ(InfoRV64Y.getExtensions().size(), 2UL);
+ EXPECT_TRUE(InfoRV64Y.getExtensions().at("i") ==
+ (RISCVISAUtils::ExtensionVersion{2, 1}));
+ EXPECT_TRUE(InfoRV64Y.getExtensions().at("y") ==
+ (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV64Y.getXLen(), 64U);
+ EXPECT_EQ(InfoRV64Y.toString(), "rv64y0p910");
}
TEST(ParseNormalizedArchString, AcceptsArbitraryExtensionsAndVersions) {
@@ -212,16 +234,18 @@ TEST(ParseArchString, RejectsInvalidBaseISA) {
}
TEST(ParseArchString, RejectsInvalidYPosition) {
- // 'y' is only allowed as a singular base ISA ('rv32y'/'rv64y') or immediately
- // after 'i' or 'e' ('rv32iy'/'rv32ey'). Anything else should be rejected.
+ // 'y' is only allowed as a base ISA ('rv32y' or 'rv64y'). Anything else
+ // should be rejected as an invalid extension.
for (StringRef Input :
- {"rv32gy0p910", "rv64gy0p910", "rv32imy0p910", "rv64imy0p910",
- "rv32i_m_y0p910", "rv64y0p910_y0p910", "rv64iy0p910_y0p910"}) {
+ {"rv32ey0p910", "rv64ey0p910", "rv32iy0p910", "rv64iy0p910",
+ "rv32gy0p910", "rv64gy0p910", "rv32imy0p910", "rv64imy0p910",
+ "rv32i2p1_y0p910", "rv32i_m_y0p910", "rv64y0p910_y0p910"}) {
EXPECT_EQ(toString(RISCVISAInfo::parseArchString(Input, true).takeError()),
"invalid standard user-level extension 'y'");
}
for (StringRef Input :
- {"rv32gy", "rv64gy", "rv32imy", "rv64imy", "rv32yy", "rv64iyy"}) {
+ {"rv32ey", "rv64ey", "rv32iy", "rv64iy", "rv32gy", "rv64gy", "rv32imy",
+ "rv64imy", "rv32yy", "rv64yy"}) {
EXPECT_EQ(
toString(RISCVISAInfo::parseArchString(Input, true, false).takeError()),
"invalid standard user-level extension 'y'");
@@ -230,35 +254,19 @@ TEST(ParseArchString, RejectsInvalidYPosition) {
TEST(ParseArchString, AcceptsRVYBaseISA) {
// With version check enabled (default), we must specify the version for
- // experimental extension 'y'. Both 'rv32y0p910' (shorthand) and explicit
- // 'rv32iy0p910' / 'rv32i2p1_y0p910' produce the same result.
- for (StringRef Input : {"rv32y0p910", "rv32iy0p910", "rv32i2p1_y0p910"}) {
- auto MaybeRV32Y = RISCVISAInfo::parseArchString(Input, true);
- ASSERT_THAT_EXPECTED(MaybeRV32Y, Succeeded());
- RISCVISAInfo &InfoRV32Y = **MaybeRV32Y;
- const auto &ExtsRV32Y = InfoRV32Y.getExtensions();
- EXPECT_EQ(ExtsRV32Y.size(), 2UL); // i, y
- EXPECT_TRUE(ExtsRV32Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
- EXPECT_TRUE(ExtsRV32Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
- EXPECT_EQ(InfoRV32Y.getXLen(), 32U);
- EXPECT_EQ(InfoRV32Y.toString(), "rv32i2p1_y0p910");
- }
-
- for (StringRef Input : {"rv32ey0p910", "rv32e2p0_y0p910"}) {
- auto MaybeRV32EY = RISCVISAInfo::parseArchString(Input, true);
- ASSERT_THAT_EXPECTED(MaybeRV32EY, Succeeded());
- RISCVISAInfo &InfoRV32EY = **MaybeRV32EY;
- const auto &ExtsRV32EY = InfoRV32EY.getExtensions();
- EXPECT_EQ(ExtsRV32EY.size(), 2UL); // e, y
- EXPECT_TRUE(ExtsRV32EY.at("e") == (RISCVISAUtils::ExtensionVersion{2, 0}));
- EXPECT_TRUE(ExtsRV32EY.at("y") ==
- (RISCVISAUtils::ExtensionVersion{0, 910}));
- EXPECT_EQ(InfoRV32EY.getXLen(), 32U);
- EXPECT_EQ(InfoRV32EY.toString(), "rv32e2p0_y0p910");
- }
-
- // rv32y0p910m should succeed and contain i, m, y0p910, zmmul, with 'y'
- // ordered between 'i' and 'm' and round-tripping through both parsers.
+ // experimental extension 'y'.
+ auto MaybeRV32Y = RISCVISAInfo::parseArchString("rv32y0p910", true);
+ ASSERT_THAT_EXPECTED(MaybeRV32Y, Succeeded());
+ RISCVISAInfo &InfoRV32Y = **MaybeRV32Y;
+ const auto &ExtsRV32Y = InfoRV32Y.getExtensions();
+ EXPECT_EQ(ExtsRV32Y.size(), 2UL); // i, y
+ EXPECT_TRUE(ExtsRV32Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
+ EXPECT_TRUE(ExtsRV32Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV32Y.getXLen(), 32U);
+ EXPECT_EQ(InfoRV32Y.toString(), "rv32y0p910");
+
+ // rv32y0p910m should succeed and contain i, m, y0p910, zmmul, with 'i'
+ // omitted and 'y' ordered before 'm', round-tripping through both parsers.
auto MaybeRV32YM = RISCVISAInfo::parseArchString("rv32y0p910m", true);
ASSERT_THAT_EXPECTED(MaybeRV32YM, Succeeded());
RISCVISAInfo &InfoRV32YM = **MaybeRV32YM;
@@ -270,7 +278,7 @@ TEST(ParseArchString, AcceptsRVYBaseISA) {
EXPECT_TRUE(InfoRV32YM.getExtensions().at("y") ==
(RISCVISAUtils::ExtensionVersion{0, 910}));
std::string RV32YMStr = InfoRV32YM.toString();
- EXPECT_EQ(RV32YMStr, "rv32i2p1_y0p910_m2p0_zmmul1p0");
+ EXPECT_EQ(RV32YMStr, "rv32y0p910_m2p0_zmmul1p0");
auto RoundTripArch = RISCVISAInfo::parseArchString(RV32YMStr, true);
ASSERT_THAT_EXPECTED(RoundTripArch, Succeeded());
EXPECT_EQ((*RoundTripArch)->toString(), RV32YMStr);
@@ -279,23 +287,19 @@ TEST(ParseArchString, AcceptsRVYBaseISA) {
EXPECT_EQ((*RoundTripNorm)->toString(), RV32YMStr);
// We can also parse it without version if we disable the version check.
- for (StringRef Input : {"rv32y", "rv32iy", "rv32ey"}) {
- auto MaybeNoVal = RISCVISAInfo::parseArchString(Input, true, false);
- ASSERT_THAT_EXPECTED(MaybeNoVal, Succeeded());
- EXPECT_EQ((*MaybeNoVal)->getExtensions().size(), 2UL);
- }
-
- for (StringRef Input : {"rv64y0p910", "rv64iy0p910", "rv64i2p1_y0p910"}) {
- auto MaybeRV64Y = RISCVISAInfo::parseArchString(Input, true);
- ASSERT_THAT_EXPECTED(MaybeRV64Y, Succeeded());
- RISCVISAInfo &InfoRV64Y = **MaybeRV64Y;
- const auto &ExtsRV64Y = InfoRV64Y.getExtensions();
- EXPECT_EQ(ExtsRV64Y.size(), 2UL); // i, y
- EXPECT_TRUE(ExtsRV64Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
- EXPECT_TRUE(ExtsRV64Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
- EXPECT_EQ(InfoRV64Y.getXLen(), 64U);
- EXPECT_EQ(InfoRV64Y.toString(), "rv64i2p1_y0p910");
- }
+ auto MaybeNoVal = RISCVISAInfo::parseArchString("rv32y", true, false);
+ ASSERT_THAT_EXPECTED(MaybeNoVal, Succeeded());
+ EXPECT_EQ((*MaybeNoVal)->getExtensions().size(), 2UL);
+
+ auto MaybeRV64Y = RISCVISAInfo::parseArchString("rv64y0p910", true);
+ ASSERT_THAT_EXPECTED(MaybeRV64Y, Succeeded());
+ RISCVISAInfo &InfoRV64Y = **MaybeRV64Y;
+ const auto &ExtsRV64Y = InfoRV64Y.getExtensions();
+ EXPECT_EQ(ExtsRV64Y.size(), 2UL); // i, y
+ EXPECT_TRUE(ExtsRV64Y.at("i") == (RISCVISAUtils::ExtensionVersion{2, 1}));
+ EXPECT_TRUE(ExtsRV64Y.at("y") == (RISCVISAUtils::ExtensionVersion{0, 910}));
+ EXPECT_EQ(InfoRV64Y.getXLen(), 64U);
+ EXPECT_EQ(InfoRV64Y.toString(), "rv64y0p910");
}
TEST(ParseArchString, RejectsUnsupportedBaseISA) {
More information about the llvm-commits
mailing list