CXXRTL: workaround LLVM regression in LoopRotatePass (fixes #4419)

Problem: LoopRotatePass applies a transformation to each loop which
has O(function size) cost due to invalidating some bookkeeping. CXXRTL
output is one huge function with thousands of loops. Most of these loops
are 1-trip but that doesn't help because loop elimination is after loop
rotation.

Fix is to add an `if` (with constexpr condition) for the common 1-word
case, to cause the loop to be DCE'd long before LoopRotatePass. This
does not seem to affect performance (at -O2) but reduces compile time
for Hazard by around 7.5x. Do this for the worst offenders: trunc, zext,
sext, rtrunc, rzext, which are used everywhere in slice and concat.

Arguably an upstream LLVM bug but it has been present since v18 at
least, so affects a lot of users.
This commit is contained in:
Luke Wren 2026-08-14 14:23:36 +01:00
parent 4716f4410f
commit 2b96cc5d38
1 changed files with 48 additions and 23 deletions

View File

@ -198,9 +198,14 @@ struct value : public expr_base<value<Bits>> {
value<NewBits> trunc() const { value<NewBits> trunc() const {
static_assert(NewBits <= Bits, "trunc() may not increase width"); static_assert(NewBits <= Bits, "trunc() may not increase width");
value<NewBits> result; value<NewBits> result;
for (size_t n = 0; n < result.chunks; n++) if (chunks == 1 && result.chunks == 1) {
result.data[n] = data[n]; // DCE loop as early as possible in common case
result.data[result.chunks - 1] &= result.msb_mask; result.data[0] = data[0] & result.msb_mask;
} else {
for (size_t n = 0; n < result.chunks; n++)
result.data[n] = data[n];
result.data[result.chunks - 1] &= result.msb_mask;
}
return result; return result;
} }
@ -209,8 +214,13 @@ struct value : public expr_base<value<Bits>> {
value<NewBits> zext() const { value<NewBits> zext() const {
static_assert(NewBits >= Bits, "zext() may not decrease width"); static_assert(NewBits >= Bits, "zext() may not decrease width");
value<NewBits> result; value<NewBits> result;
for (size_t n = 0; n < chunks; n++) if (chunks == 1 && result.chunks == 1) {
result.data[n] = data[n]; // DCE loop as early as possible in common case
result.data[0] = data[0];
} else {
for (size_t n = 0; n < chunks; n++)
result.data[n] = data[n];
}
return result; return result;
} }
@ -219,8 +229,13 @@ struct value : public expr_base<value<Bits>> {
value<NewBits> sext() const { value<NewBits> sext() const {
static_assert(NewBits >= Bits, "sext() may not decrease width"); static_assert(NewBits >= Bits, "sext() may not decrease width");
value<NewBits> result; value<NewBits> result;
for (size_t n = 0; n < chunks; n++) if (chunks == 1 && result.chunks == 1) {
result.data[n] = data[n]; // DCE loop as early as possible in common case
result.data[0] = data[0];
} else {
for (size_t n = 0; n < chunks; n++)
result.data[n] = data[n];
}
if (is_neg()) { if (is_neg()) {
result.data[chunks - 1] |= ~msb_mask; result.data[chunks - 1] |= ~msb_mask;
for (size_t n = chunks; n < result.chunks; n++) for (size_t n = chunks; n < result.chunks; n++)
@ -237,15 +252,20 @@ struct value : public expr_base<value<Bits>> {
value<NewBits> result; value<NewBits> result;
constexpr size_t shift_chunks = (Bits - NewBits) / chunk::bits; constexpr size_t shift_chunks = (Bits - NewBits) / chunk::bits;
constexpr size_t shift_bits = (Bits - NewBits) % chunk::bits; constexpr size_t shift_bits = (Bits - NewBits) % chunk::bits;
chunk::type carry = 0; if (chunks == 1 && result.chunks == 1) {
if (shift_chunks + result.chunks < chunks) { // DCE loop as early as possible in common case
carry = (shift_bits == 0) ? 0 result.data[0] = data[0] >> shift_bits;
: data[shift_chunks + result.chunks] << (chunk::bits - shift_bits); } else {
} chunk::type carry = 0;
for (size_t n = result.chunks; n > 0; n--) { if (shift_chunks + result.chunks < chunks) {
result.data[n - 1] = carry | (data[shift_chunks + n - 1] >> shift_bits); carry = (shift_bits == 0) ? 0
carry = (shift_bits == 0) ? 0 : data[shift_chunks + result.chunks] << (chunk::bits - shift_bits);
: data[shift_chunks + n - 1] << (chunk::bits - shift_bits); }
for (size_t n = result.chunks; n > 0; n--) {
result.data[n - 1] = carry | (data[shift_chunks + n - 1] >> shift_bits);
carry = (shift_bits == 0) ? 0
: data[shift_chunks + n - 1] << (chunk::bits - shift_bits);
}
} }
return result; return result;
} }
@ -257,14 +277,19 @@ struct value : public expr_base<value<Bits>> {
value<NewBits> result; value<NewBits> result;
constexpr size_t shift_chunks = (NewBits - Bits) / chunk::bits; constexpr size_t shift_chunks = (NewBits - Bits) / chunk::bits;
constexpr size_t shift_bits = (NewBits - Bits) % chunk::bits; constexpr size_t shift_bits = (NewBits - Bits) % chunk::bits;
chunk::type carry = 0; if (chunks == 1 && result.chunks == 1) {
for (size_t n = 0; n < chunks; n++) { // DCE loop as early as possible in common case
result.data[shift_chunks + n] = (data[n] << shift_bits) | carry; result.data[0] = data[0] << shift_bits;
carry = (shift_bits == 0) ? 0 } else {
: data[n] >> (chunk::bits - shift_bits); chunk::type carry = 0;
for (size_t n = 0; n < chunks; n++) {
result.data[shift_chunks + n] = (data[n] << shift_bits) | carry;
carry = (shift_bits == 0) ? 0
: data[n] >> (chunk::bits - shift_bits);
}
if (shift_chunks + chunks < result.chunks)
result.data[shift_chunks + chunks] = carry;
} }
if (shift_chunks + chunks < result.chunks)
result.data[shift_chunks + chunks] = carry;
return result; return result;
} }