CT_00_036 -- Improve bset/bclr/binv/bext with variable bit offset (GCC)

CT_00_036 -- Improve bset/bclr/binv/bext with variable bit offset (GCC)

While significant progress has been made on improving GCC's ability to generate bset/bclr/binv more work remains to be done.

 

The RTL phase of the compiler can discover many cases where bit manipulation of a SI value on rv64 is safe by looking for idioms like masking of the bit position, sign/zero extensions, etc.  The cases we want to try and handle next would utilize range information available from the Ranger API at gimple→rtl expansion to know that a particular bit manipulation won't change theSI sign bit.  Issues:

  1. Ranger probably can't be called after TER.   So we're probably stuck relying on global data computed by Ranger and recorded prior to TER.

  2. Does TER ever make transformations that destroy that global range data?

  3. We'll need to rewrite the SI operations in DI, that way we don't have to carry around range information in RTL.  We'll need to be careful to ensure this does not introduce unnecessary sign or zero extensions.

 

Another testcase to consider that likely doesn't use range data:

void foo(unsigned char *data, unsigned int lo_bit) { unsigned int mask = ((1UL << 1) - 1) << lo_bit; *data = (*data & ~mask) | ((1 << lo_bit) & mask); }

 

In this test

typedef struct SHA { unsigned char block[128]; unsigned int blockcnt; } SHA; #define BITSET(s, pos) s[(pos) >> 3] & (char) (0x01 << (7 - (pos) % 8)) #define SETBIT(s, pos) s[(pos) >> 3] |= (char) (0x01 << (7 - (pos) % 8)) #define CLRBIT(s, pos) s[(pos) >> 3] &= (char) ~(0x01 << (7 - (pos) % 8)) #define ULNG unsigned long void shabits(char *bitstr, long bitcnt, SHA *s) { ULNG i; for (i = 0UL; i < bitcnt; i++) { if (BITSET(bitstr, i)) SETBIT(s->block, s->blockcnt); else CLRBIT(s->block, s->blockcnt); } }

 

We fail miserably at using the Zbs extensions.  The root issue seems to be failure to utilize the REG_EQUAL notes which carry enough information to discover that we're shifting a constant value (1) by a variable amount.  That's critical to being able to utilize the Zbs extensions.  This may be fairly hard to fix.

 

typedef struct SHA { unsigned char block[128]; unsigned int blockcnt; } SHA; #define BITSET(s, pos) s[(pos) >> 3] & (char) (0x01 << (7 - (pos) % 8)) #define SETBIT(s, pos) s[(pos) >> 3] |= (char) (0x01 << (7 - (pos) % 8)) #define CLRBIT(s, pos) s[(pos) >> 3] &= (char) ~(0x01 << (7 - (pos) % 8)) #define ULNG unsigned long void shabits(char *bitstr, long bitcnt, SHA *s, ULNG i) { if (BITSET(bitstr, i)) SETBIT(s->block, s->blockcnt); else CLRBIT(s->block, s->blockcnt); } The key to unlocking various optimizations in this code is: Trying 22 -> 24: 22: r167:DI=sign_extend(0x1<<r163:DI#0) REG_DEAD r163:DI REG_EQUAL sign_extend(0x1<<r163:DI#0) 24: r151:DI=zero_extend(r167:DI#0) REG_DEAD r167:DI Failed to match this instruction: (set (reg:DI 151 [ _40 ]) (zero_extend:DI (subreg:QI (ashift:DI (const_int 1 [0x1]) (subreg:QI (reg:DI 163) 0)) 0))) We can't safely match because the shift count might be > 8 and we're doing the shift in DImode. So we could get something like 0x1000 as the result. The zero extension from QI to DI would then lop off the upper bits resulting in 0x0. If we tried to use naively use bset we'd get 0x1000. Latest thinking (March 2025) is that we'd really like ext-dce to change the extension to a subreg (or push the subreg to the operands). That should be enough to then allow combine to realize some bits are not live and ultimately improve our ability to utilize Zbs extensions for some of this stuff.

 

  • Operations on sub-word types (chars).   We should recognize (1 << n) as a QI or HI type as a simple bset.

  • In the same testcase there's an additional idioms for bset/binv and bclr that are used by late-combine that will allow removal of a bset

  • And in the same testcase there's a complex bext we could discover

  • And there's a redundant li.  This is an artifact of splitting very late in the pipeline and not having a good pass to clean things up

 

Failure to define SHIFT_COUNT_TRUNCATED implies that backend needs to handle extensions, masking, etc in its bit manipulation patterns.  Multiple missed cases with bext have been identified.

 

Ventana has other missed bitmanip cases in our internal bug tracker.  If anyone is interested in those testcases, just reach out to Jeff.

 

Stakeholders/Partners

RISE:

Ventana: Jeff Law – general oversight / guidance & implementation

External:

 

Dependencies

 

 

Status

Development

NOT STARTED

 

Development Timeline

1H2025

 

Upstreaming

NOT STARTED

 

 

Upstream Version

gcc-16 (target)

(Spring 2026)

 

 

 

 

Contacts

Jeff Law (Ventana)

 

Dependencies

None

 

 

Updates

Jul 16, 2025 

  • Several improvements have been made for bext detection.  While those cases weren't initially part of this work item they are definitely useful to tackle.

  • Several improvements have been made for cases where we can determine a bset/binv/bclr is not going to change the SI sign bit. 

  • But overall no progress on the specific issues in this work item.  Moving to 2H2025.

Nov 7, 2024 

  • Moved to 1H2025

Jul 9, 2024 

  • Additional case from perlbench noted.

Jun 19, 2024 

  • Project noted for 2H2024