Skip to content

[comb] New Canonicalization ~sext(x) = sext(~x)#9637

Merged
cowardsa merged 9 commits intollvm:mainfrom
cowardsa:not_sext
Feb 11, 2026
Merged

[comb] New Canonicalization ~sext(x) = sext(~x)#9637
cowardsa merged 9 commits intollvm:mainfrom
cowardsa:not_sext

Conversation

@cowardsa
Copy link
Contributor

@cowardsa cowardsa commented Feb 9, 2026

New canonicalization pattern pushing logical negation over sign-extension:

~sext(x) = sext(~x)

This represents a canonicalization pattern in that it allows for optimisations as used in synthesis flows that need to identify sign-extended operators to simplify the logic.
Appreciate that it could be debatable whether this is a canonicalization so open to suggestions or discussion?

An example is included in the datapath tests where moving the negation allows for a reduction in the logic required to implement arithmetic operators.

@cowardsa cowardsa requested a review from darthscsi as a code owner February 9, 2026 18:30
Copy link
Member

@seldridge seldridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, having this canonicalizer makes sense to me. It should be cheaper to invert fewer bits. Sign extension (static extract + replication) is viewed as basically free whereas inversion occurs a gate and power cost.

// Match the base unextended value against the sub-matcher
return lhs.match(op->getOperand(1));
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be written using an ODS pattern as opposed to open coded here?

(This is a usual ask as it's viewed that the ODS patterns are easier to maintain. I do not see existing comb canonicalizations, though. Examples from FIRRTL DIalect: https://github.com/llvm/circt/blob/main/include/circt/Dialect/FIRRTL/FIRRTLCanonicalization.td)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's trivial to write ODS pattern for this pattern at Comb mainly because there is no sign type/implicit width extension op like FIRRTL. The ODS would looks like this:

let n = width of x in
let m = width of sign-extended expression in
(xor (concat (replicate (extract(x, n - 1 , 1)), m - n + 1), extract(x, 0, n -2)), -1)
=> 
let flip_x = xor(x, -1) in
(concat (replicate (extract(flip_x, n -1 , 1)), m - n + 1), extract(flip_x, 0, n -2))

IMO it's a bit more maintainable in C++ with a proper helper function :) If we could define and use SExt pattern as ODS's dag object in source pattern without adding a new operation, it would be easier but not sure it's that's possible today 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the context and sketching it out. Yeah, this looks a bit tricky in ODS.

if (op.isBinaryNot()) {
Value base;
// Check for sext of the inverted value
if (matchPattern(op.getOperand(0), m_Sext(m_Any(&base)))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (matchPattern(op.getOperand(0), m_Sext(m_Any(&base)))) {
Value base;
if (matchPattern(op.getResult(), m_Complement(m_Sext(m_Any(&base))))) {

Nesting m_Complement would work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uenoku - didn't really understand how this works?

As written m_Complement does not compose with itself or m_Sext - as you get an error in converting Value to Operation?

Think I would need to refactor the m_Complement matcher to allow for this composition (maybe a separate PR?_ - for example this current style matches existing Xor Folds:

  // xor(xor(x,1),1) -> x
  // but not self loop
  if (isBinaryNot()) {
    Value subExpr;
    if (matchPattern(getOperand(0), m_Complement(m_Any(&subExpr))) &&
        subExpr != getResult())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it look we need to use matchOperandOrValueAtIndex instead of getOperand in m_Complement. I can follow up in another PR so it looks good as is. Thank you for trying it!

return xorOp && xorOp.isBinaryNot() && 
            mlir::detail::matchOperandOrValueAtIndex(op, 0, lhs);

@cowardsa cowardsa merged commit afcefdc into llvm:main Feb 11, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants