From 2b96cc5d381fd10ea5fb9197edfa301d850554d2 Mon Sep 17 00:00:00 2001 From: Luke Wren Date: Fri, 14 Aug 2026 14:23:36 +0100 Subject: [PATCH] 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. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 71 +++++++++++++++++-------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 521cce4b0..511648cca 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -198,9 +198,14 @@ struct value : public expr_base> { value trunc() const { static_assert(NewBits <= Bits, "trunc() may not increase width"); value result; - for (size_t n = 0; n < result.chunks; n++) - result.data[n] = data[n]; - result.data[result.chunks - 1] &= result.msb_mask; + if (chunks == 1 && result.chunks == 1) { + // DCE loop as early as possible in common case + 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; } @@ -209,8 +214,13 @@ struct value : public expr_base> { value zext() const { static_assert(NewBits >= Bits, "zext() may not decrease width"); value result; - for (size_t n = 0; n < chunks; n++) - result.data[n] = data[n]; + if (chunks == 1 && result.chunks == 1) { + // 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; } @@ -219,8 +229,13 @@ struct value : public expr_base> { value sext() const { static_assert(NewBits >= Bits, "sext() may not decrease width"); value result; - for (size_t n = 0; n < chunks; n++) - result.data[n] = data[n]; + if (chunks == 1 && result.chunks == 1) { + // 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()) { result.data[chunks - 1] |= ~msb_mask; for (size_t n = chunks; n < result.chunks; n++) @@ -237,15 +252,20 @@ struct value : public expr_base> { value result; constexpr size_t shift_chunks = (Bits - NewBits) / chunk::bits; constexpr size_t shift_bits = (Bits - NewBits) % chunk::bits; - chunk::type carry = 0; - if (shift_chunks + result.chunks < chunks) { - carry = (shift_bits == 0) ? 0 - : data[shift_chunks + result.chunks] << (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); + if (chunks == 1 && result.chunks == 1) { + // DCE loop as early as possible in common case + result.data[0] = data[0] >> shift_bits; + } else { + chunk::type carry = 0; + if (shift_chunks + result.chunks < chunks) { + carry = (shift_bits == 0) ? 0 + : data[shift_chunks + result.chunks] << (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; } @@ -257,14 +277,19 @@ struct value : public expr_base> { value result; constexpr size_t shift_chunks = (NewBits - Bits) / chunk::bits; constexpr size_t shift_bits = (NewBits - Bits) % chunk::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 (chunks == 1 && result.chunks == 1) { + // DCE loop as early as possible in common case + result.data[0] = data[0] << shift_bits; + } else { + 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; }