From fb71c721017793ad7ce5f21760e70486af4c8990 Mon Sep 17 00:00:00 2001 From: osmanyasar05 Date: Thu, 2 Jul 2026 11:40:14 +0100 Subject: [PATCH 1/2] rewrites --- Veir/Passes/RISCVCombines/Combine.lean | 448 ++++++++++++++++++++++++- 1 file changed, 447 insertions(+), 1 deletion(-) diff --git a/Veir/Passes/RISCVCombines/Combine.lean b/Veir/Passes/RISCVCombines/Combine.lean index 09027e384..a4cff50a7 100644 --- a/Veir/Passes/RISCVCombines/Combine.lean +++ b/Veir/Passes/RISCVCombines/Combine.lean @@ -27,9 +27,455 @@ def right_identity_zero_add (rewriter: PatternRewriter OpCode) (op: OperationPtr let rewriter := rewriter.replaceValue (op.getResult 0) lhs sorry sorry sorry rewriter.eraseOp op sorry sorry sorry +/-! ### select_same_val : (c ? x : x) → x -/ + +set_option warn.sorry false in +def select_same_val_self (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (_c, tval, fval) := matchSelect op rewriter.ctx | return rewriter + if tval != fval then return rewriter + let rewriter := rewriter.replaceValue (op.getResult 0) tval sorry sorry sorry + rewriter.eraseOp op sorry sorry sorry + +/-! ### select_constant_cmp : (1 ? x : y) → x , (0 ? x : y) → y -/ + +set_option warn.sorry false in +def select_constant_cmp_true (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (cond, tval, _fval) := matchSelect op rewriter.ctx | return rewriter + let some cst := matchConstantIntVal cond rewriter.ctx | return rewriter + if cst.value ≠ 1 then return rewriter + let rewriter := rewriter.replaceValue (op.getResult 0) tval sorry sorry sorry + rewriter.eraseOp op sorry sorry sorry + +set_option warn.sorry false in +def select_constant_cmp_false (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (cond, _tval, fval) := matchSelect op rewriter.ctx | return rewriter + let some cst := matchConstantIntVal cond rewriter.ctx | return rewriter + if cst.value ≠ 0 then return rewriter + let rewriter := rewriter.replaceValue (op.getResult 0) fval sorry sorry sorry + rewriter.eraseOp op sorry sorry sorry + +/-! ### hoist_logic_op_with_same_opcode_hands-/ + +-- (sext X) & (sext Y) → sext (X & Y) +set_option warn.sorry false in +def AndSextSext (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchSext dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchSext dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (sext X) | (sext Y) → sext (X | Y) +set_option warn.sorry false in +def OrSextSext (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchSext dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchSext dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (sext X) ^ (sext Y) → sext (X ^ Y) +set_option warn.sorry false in +def XorSextSext (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchSext dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchSext dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (zext X) & (zext Y) → zext (X & Y) +set_option warn.sorry false in +def AndZextZext (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchZext dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchZext dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (zext X) | (zext Y) → zext (X | Y) +set_option warn.sorry false in +def OrZextZext (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchZext dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchZext dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (zext X) ^ (zext Y) → zext (X ^ Y) +set_option warn.sorry false in +def XorZextZext (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchZext dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchZext dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (trunc X) & (trunc Y) → trunc (X & Y) +set_option warn.sorry false in +def AndTruncTrunc (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchTrunc dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchTrunc dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (trunc X) | (trunc Y) → trunc (X | Y) +set_option warn.sorry false in +def OrTruncTrunc (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchTrunc dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchTrunc dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (trunc X) ^ (trunc Y) → trunc (X ^ Y) +set_option warn.sorry false in +def XorTruncTrunc (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, _xp) := matchTrunc dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, yp) := matchTrunc dY rewriter.ctx | return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + #[] #[] yp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X << Z) & (Y << Z) → (X & Y) << Z +set_option warn.sorry false in +def AndShlShl (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchShl dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchShl dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .shl) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X << Z) | (Y << Z) → (X | Y) << Z +set_option warn.sorry false in +def OrShlShl (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchShl dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchShl dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .shl) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X << Z) ^ (Y << Z) → (X ^ Y) << Z +set_option warn.sorry false in +def XorShlShl (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchShl dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchShl dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .shl) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X >> Z) & (Y >> Z) → (X & Y) >> Z (logical) +set_option warn.sorry false in +def AndLshrLshr (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchLshr dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchLshr dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .lshr) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X >> Z) | (Y >> Z) → (X | Y) >> Z (logical) +set_option warn.sorry false in +def OrLshrLshr (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchLshr dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchLshr dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .lshr) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X >> Z) ^ (Y >> Z) → (X ^ Y) >> Z (logical) +set_option warn.sorry false in +def XorLshrLshr (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchLshr dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchLshr dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .lshr) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X >> Z) & (Y >> Z) → (X & Y) >> Z (arithmetic) +set_option warn.sorry false in +def AndAshrAshr (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchAshr dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchAshr dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .ashr) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X >> Z) | (Y >> Z) → (X | Y) >> Z (arithmetic) +set_option warn.sorry false in +def OrAshrAshr (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchAshr dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchAshr dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .ashr) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X >> Z) ^ (Y >> Z) → (X ^ Y) >> Z (arithmetic) +set_option warn.sorry false in +def XorAshrAshr (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _p0) := matchAshr dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, p1) := matchAshr dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .ashr) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] p1 (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X & Z) & (Y & Z) → (X & Y) & Z +set_option warn.sorry false in +def AndAndAnd (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, _) := matchAnd op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _) := matchAnd dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, _) := matchAnd dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X & Z) | (Y & Z) → (X | Y) & Z +set_option warn.sorry false in +def OrAndAnd (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, oprops) := matchOr op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _) := matchAnd dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, _) := matchAnd dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] oprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- (X & Z) ^ (Y & Z) → (X ^ Y) & Z +set_option warn.sorry false in +def XorAndAnd (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (v0, v1, xprops) := matchXor op rewriter.ctx | return rewriter + let some dX := getDefiningOp v0 rewriter.ctx | return rewriter + let some (x, z0, _) := matchAnd dX rewriter.ctx | return rewriter + let some dY := getDefiningOp v1 rewriter.ctx | return rewriter + let some (y, z1, _) := matchAnd dY rewriter.ctx | return rewriter + if z0 != z1 then return rewriter + let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] + #[] #[] xprops (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0), z0] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +/-! ### sub_add_reg -/ + +-- (x + y) - y → x +set_option warn.sorry false in +def sub_add_reg_x_add_y_sub_y (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (s0, s1, _sp) := matchSub op rewriter.ctx | return rewriter + let some dAdd := getDefiningOp s0 rewriter.ctx | return rewriter + let some (x, y, _ap) := matchAdd dAdd rewriter.ctx | return rewriter + if y != s1 then return rewriter + let rewriter := rewriter.replaceValue (op.getResult 0) x sorry sorry sorry + rewriter.eraseOp op sorry sorry sorry + +-- (x + y) - x → y +set_option warn.sorry false in +def sub_add_reg_x_add_y_sub_x (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (s0, s1, _sp) := matchSub op rewriter.ctx | return rewriter + let some dAdd := getDefiningOp s0 rewriter.ctx | return rewriter + let some (x, y, _ap) := matchAdd dAdd rewriter.ctx | return rewriter + if x != s1 then return rewriter + let rewriter := rewriter.replaceValue (op.getResult 0) y sorry sorry sorry + rewriter.eraseOp op sorry sorry sorry + +-- x - (y + x) → 0 - y +set_option warn.sorry false in +def sub_add_reg_x_sub_y_add_x (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (s0, s1, sp) := matchSub op rewriter.ctx | return rewriter + let some dAdd := getDefiningOp s1 rewriter.ctx | return rewriter + let some (y, x, _ap) := matchAdd dAdd rewriter.ctx | return rewriter + if x != s0 then return rewriter + let .integerType yty := (y.getType! rewriter.ctx.raw).val | return rewriter + let z := LLVMConstantProperties.mk (.integer (IntegerAttr.mk (0) yty)) + let (rewriter, c0) ← rewriter.createOp (.llvm .mlir__constant) #[y.getType! rewriter.ctx.raw] #[] + #[] #[] z (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .sub) #[s0.getType! rewriter.ctx.raw] #[(c0.getResult 0), y] + #[] #[] sp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +-- x - (x + y) → 0 - y +set_option warn.sorry false in +def sub_add_reg_x_sub_x_add_y (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (s0, s1, sp) := matchSub op rewriter.ctx | return rewriter + let some dAdd := getDefiningOp s1 rewriter.ctx | return rewriter + let some (x, y, _ap) := matchAdd dAdd rewriter.ctx | return rewriter + if x != s0 then return rewriter + let .integerType yty := (y.getType! rewriter.ctx.raw).val | return rewriter + let z := LLVMConstantProperties.mk (.integer (IntegerAttr.mk (0) yty)) + let (rewriter, c0) ← rewriter.createOp (.llvm .mlir__constant) #[y.getType! rewriter.ctx.raw] #[] + #[] #[] z (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .sub) #[s0.getType! rewriter.ctx.raw] #[(c0.getResult 0), y] + #[] #[] sp (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + +/-! ### xor_of_and_with_same_reg : (xor (and x, y), y) → (and (not x), y) -/ + +set_option warn.sorry false in +def xor_of_and_with_same_reg (rewriter: PatternRewriter OpCode) (op: OperationPtr) + (opInBounds : op.InBounds rewriter.ctx.raw) : Option (PatternRewriter OpCode) := do + let some (andVal, yval, _xp) := matchXor op rewriter.ctx | return rewriter + let some dA := getDefiningOp andVal rewriter.ctx | return rewriter + let some (x, y2, _) := matchAnd dA rewriter.ctx | return rewriter + if y2 != yval then return rewriter + let .integerType xty := (x.getType! rewriter.ctx.raw).val | return rewriter + let m1 := LLVMConstantProperties.mk (.integer (IntegerAttr.mk (-1) xty)) + let (rewriter, c1) ← rewriter.createOp (.llvm .mlir__constant) #[x.getType! rewriter.ctx.raw] #[] + #[] #[] m1 (some $ .before op) sorry sorry sorry sorry + let (rewriter, notx) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, (c1.getResult 0)] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + let (rewriter, newOp) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[(notx.getResult 0), yval] + #[] #[] () (some $ .before op) sorry sorry sorry sorry + rewriter.replaceOp op newOp sorry sorry sorry sorry sorry + + def Combine.impl (ctx : WfIRContext OpCode) (op : OperationPtr) (_ : op.InBounds ctx.raw) : ExceptT String IO (WfIRContext OpCode) := do - let patterns : Array (RewritePattern OpCode) := #[right_identity_zero_add] + let patterns : Array (RewritePattern OpCode) := #[right_identity_zero_add, + select_same_val_self, select_constant_cmp_true, select_constant_cmp_false, + AndSextSext, OrSextSext, XorSextSext, + AndZextZext, OrZextZext, XorZextZext, + AndTruncTrunc, OrTruncTrunc, XorTruncTrunc, + AndShlShl, OrShlShl, XorShlShl, + AndLshrLshr, OrLshrLshr, XorLshrLshr, + AndAshrAshr, OrAshrAshr, XorAshrAshr, + AndAndAnd, OrAndAnd, XorAndAnd, + sub_add_reg_x_add_y_sub_y, sub_add_reg_x_add_y_sub_x, + sub_add_reg_x_sub_y_add_x, sub_add_reg_x_sub_x_add_y, + xor_of_and_with_same_reg] let pattern := RewritePattern.GreedyRewritePattern patterns match RewritePattern.applyInContext pattern ctx with | none => throw "Error while applying pattern rewrites" From 85771ddca224b72ae9f950a0b110deaae255e75a Mon Sep 17 00:00:00 2001 From: osmanyasar05 Date: Tue, 7 Jul 2026 15:04:06 +0100 Subject: [PATCH 2/2] add tests --- Test/Passes/RISCVCombines/AndAndAnd.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/AndAshrAshr.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/AndLshrLshr.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/AndSextSext.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/AndShlShl.mlir | 37 +++++++++++++++++++ Test/Passes/RISCVCombines/AndTruncTrunc.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/AndZextZext.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/OrAndAnd.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/OrAshrAshr.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/OrLshrLshr.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/OrSextSext.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/OrShlShl.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/OrTruncTrunc.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/OrZextZext.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/XorAndAnd.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/XorAshrAshr.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/XorLshrLshr.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/XorSextSext.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/XorShlShl.mlir | 36 ++++++++++++++++++ Test/Passes/RISCVCombines/XorTruncTrunc.mlir | 35 ++++++++++++++++++ Test/Passes/RISCVCombines/XorZextZext.mlir | 35 ++++++++++++++++++ .../select_constant_cmp_false.mlir | 31 ++++++++++++++++ .../select_constant_cmp_true.mlir | 30 +++++++++++++++ .../RISCVCombines/select_same_val_self.mlir | 30 +++++++++++++++ .../sub_add_reg_x_add_y_sub_x.mlir | 32 ++++++++++++++++ .../sub_add_reg_x_add_y_sub_y.mlir | 32 ++++++++++++++++ .../sub_add_reg_x_sub_x_add_y.mlir | 33 +++++++++++++++++ .../sub_add_reg_x_sub_y_add_x.mlir | 33 +++++++++++++++++ .../xor_of_and_with_same_reg.mlir | 35 ++++++++++++++++++ Veir/Passes/RISCVCombines/Combine.lean | 18 ++++----- 30 files changed, 1013 insertions(+), 9 deletions(-) create mode 100644 Test/Passes/RISCVCombines/AndAndAnd.mlir create mode 100644 Test/Passes/RISCVCombines/AndAshrAshr.mlir create mode 100644 Test/Passes/RISCVCombines/AndLshrLshr.mlir create mode 100644 Test/Passes/RISCVCombines/AndSextSext.mlir create mode 100644 Test/Passes/RISCVCombines/AndShlShl.mlir create mode 100644 Test/Passes/RISCVCombines/AndTruncTrunc.mlir create mode 100644 Test/Passes/RISCVCombines/AndZextZext.mlir create mode 100644 Test/Passes/RISCVCombines/OrAndAnd.mlir create mode 100644 Test/Passes/RISCVCombines/OrAshrAshr.mlir create mode 100644 Test/Passes/RISCVCombines/OrLshrLshr.mlir create mode 100644 Test/Passes/RISCVCombines/OrSextSext.mlir create mode 100644 Test/Passes/RISCVCombines/OrShlShl.mlir create mode 100644 Test/Passes/RISCVCombines/OrTruncTrunc.mlir create mode 100644 Test/Passes/RISCVCombines/OrZextZext.mlir create mode 100644 Test/Passes/RISCVCombines/XorAndAnd.mlir create mode 100644 Test/Passes/RISCVCombines/XorAshrAshr.mlir create mode 100644 Test/Passes/RISCVCombines/XorLshrLshr.mlir create mode 100644 Test/Passes/RISCVCombines/XorSextSext.mlir create mode 100644 Test/Passes/RISCVCombines/XorShlShl.mlir create mode 100644 Test/Passes/RISCVCombines/XorTruncTrunc.mlir create mode 100644 Test/Passes/RISCVCombines/XorZextZext.mlir create mode 100644 Test/Passes/RISCVCombines/select_constant_cmp_false.mlir create mode 100644 Test/Passes/RISCVCombines/select_constant_cmp_true.mlir create mode 100644 Test/Passes/RISCVCombines/select_same_val_self.mlir create mode 100644 Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_x.mlir create mode 100644 Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_y.mlir create mode 100644 Test/Passes/RISCVCombines/sub_add_reg_x_sub_x_add_y.mlir create mode 100644 Test/Passes/RISCVCombines/sub_add_reg_x_sub_y_add_x.mlir create mode 100644 Test/Passes/RISCVCombines/xor_of_and_with_same_reg.mlir diff --git a/Test/Passes/RISCVCombines/AndAndAnd.mlir b/Test/Passes/RISCVCombines/AndAndAnd.mlir new file mode 100644 index 000000000..59dea53fa --- /dev/null +++ b/Test/Passes/RISCVCombines/AndAndAnd.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `and` through a shared masking `and`: `(X & Z) & (Y & Z)` equals `(X & Y) & Z`. Only fires when both inner `and`s share the same +// masking operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %ax = "llvm.and"(%x, %z) : (i64, i64) -> i64 + %ay = "llvm.and"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.and"(%ax, %ay) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct masks, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %ax = "llvm.and"(%x, %z0) : (i64, i64) -> i64 + %ay = "llvm.and"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.and"(%ax, %ay) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `and` now feeds a single masking `and`: (X and Y) & Z. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[MASK:.*]] = "llvm.and"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[MASK]]) : (i64) -> () + +// Distinct masks: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NAX:.*]] = "llvm.and"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NAY:.*]] = "llvm.and"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NAX]], %[[NAY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/AndAshrAshr.mlir b/Test/Passes/RISCVCombines/AndAshrAshr.mlir new file mode 100644 index 000000000..8f72ff39c --- /dev/null +++ b/Test/Passes/RISCVCombines/AndAshrAshr.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `and` through a shared arithmetic right shift: `(X >> Z) & (Y >> Z)` equals `(X & Y) >> Z` (the replicated sign bits agree bit-by-bit). Only fires when both arithmetic right shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.ashr"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.ashr"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.and"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.ashr"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.ashr"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.and"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `and` now feeds a single `ashr`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.ashr"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.ashr"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.ashr"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/AndLshrLshr.mlir b/Test/Passes/RISCVCombines/AndLshrLshr.mlir new file mode 100644 index 000000000..58aee30dd --- /dev/null +++ b/Test/Passes/RISCVCombines/AndLshrLshr.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `and` through a shared logical right shift: `(X >> Z) & (Y >> Z)` equals `(X & Y) >> Z`. Only fires when both logical right shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.lshr"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.lshr"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.and"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.lshr"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.lshr"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.and"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `and` now feeds a single `lshr`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.lshr"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.lshr"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.lshr"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/AndSextSext.mlir b/Test/Passes/RISCVCombines/AndSextSext.mlir new file mode 100644 index 000000000..8dcceee8d --- /dev/null +++ b/Test/Passes/RISCVCombines/AndSextSext.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A sign extension distributes over `and`: sign-extending the inputs then anding is the same as anding then sign-extending, because sign extension replicates the top bit uniformly. The two casts are hoisted through +// the bitwise op: `(sext X) and (sext Y) -> sext (X and Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i32, i32) -> i64}> ({ + ^bb0(%x: i32, %y: i32): + %ex = "llvm.sext"(%x) : (i32) -> i64 + %ey = "llvm.sext"(%y) : (i32) -> i64 + %r = "llvm.and"(%ex, %ey) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: only one operand is a `sext`, so nothing is hoisted. + "func.func"() <{function_type = (i32, i64) -> i64}> ({ + ^bb0(%x: i32, %y: i64): + %ex = "llvm.sext"(%x) : (i32) -> i64 + %r = "llvm.and"(%ex, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `sext` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i32, %[[Y:.*]] : i32): +// CHECK: %[[OP:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i32, i32) -> i32 +// CHECK-NEXT: %[[E:.*]] = "llvm.sext"(%[[OP]]) : (i32) -> i64 +// CHECK: "func.return"(%[[E]]) : (i64) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i32, %[[NY:.*]] : i64): +// CHECK: %[[NEX:.*]] = "llvm.sext"(%[[NX]]) : (i32) -> i64 +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NEX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/AndShlShl.mlir b/Test/Passes/RISCVCombines/AndShlShl.mlir new file mode 100644 index 000000000..0827c0ba8 --- /dev/null +++ b/Test/Passes/RISCVCombines/AndShlShl.mlir @@ -0,0 +1,37 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `and` through a shared left shift: `(X << Z) & (Y << Z)` equals +// `(X & Y) << Z`, since shifting in identical zero bits commutes with a bitwise +// `and`. Only fires when both shift amounts are the same value `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.shl"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.shl"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.and"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct shift amounts, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.shl"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.shl"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.and"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `and` now feeds a single shift: (X & Y) << Z. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[AND:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SHL:.*]] = "llvm.shl"(%[[AND]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SHL]]) : (i64) -> () + +// Distinct shift amounts: the two shifts and the outer `and` are untouched. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.shl"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.shl"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/AndTruncTrunc.mlir b/Test/Passes/RISCVCombines/AndTruncTrunc.mlir new file mode 100644 index 000000000..c9ab80ec7 --- /dev/null +++ b/Test/Passes/RISCVCombines/AndTruncTrunc.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A truncation distributes over `and`: truncating the inputs then anding keeps exactly the low bits that anding-then-truncating would. The two casts are hoisted through +// the bitwise op: `(trunc X) and (trunc Y) -> trunc (X and Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i32}> ({ + ^bb0(%x: i64, %y: i64): + %ex = "llvm.trunc"(%x) : (i64) -> i32 + %ey = "llvm.trunc"(%y) : (i64) -> i32 + %r = "llvm.and"(%ex, %ey) : (i32, i32) -> i32 + "func.return"(%r) : (i32) -> () + }) : () -> () + + // Negative case: only one operand is a `trunc`, so nothing is hoisted. + "func.func"() <{function_type = (i64, i32) -> i32}> ({ + ^bb0(%x: i64, %y: i32): + %ex = "llvm.trunc"(%x) : (i64) -> i32 + %r = "llvm.and"(%ex, %y) : (i32, i32) -> i32 + "func.return"(%r) : (i32) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `trunc` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64): +// CHECK: %[[OP:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[E:.*]] = "llvm.trunc"(%[[OP]]) : (i64) -> i32 +// CHECK: "func.return"(%[[E]]) : (i32) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i32): +// CHECK: %[[NEX:.*]] = "llvm.trunc"(%[[NX]]) : (i64) -> i32 +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NEX]], %[[NY]]) : (i32, i32) -> i32 +// CHECK: "func.return"(%[[NR]]) : (i32) -> () diff --git a/Test/Passes/RISCVCombines/AndZextZext.mlir b/Test/Passes/RISCVCombines/AndZextZext.mlir new file mode 100644 index 000000000..71bcd350e --- /dev/null +++ b/Test/Passes/RISCVCombines/AndZextZext.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A zero extension distributes over `and`: zero-extending the inputs then anding is the same as anding then zero-extending, because the added high bits are all zero. The two casts are hoisted through +// the bitwise op: `(zext X) and (zext Y) -> zext (X and Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i32, i32) -> i64}> ({ + ^bb0(%x: i32, %y: i32): + %ex = "llvm.zext"(%x) : (i32) -> i64 + %ey = "llvm.zext"(%y) : (i32) -> i64 + %r = "llvm.and"(%ex, %ey) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: only one operand is a `zext`, so nothing is hoisted. + "func.func"() <{function_type = (i32, i64) -> i64}> ({ + ^bb0(%x: i32, %y: i64): + %ex = "llvm.zext"(%x) : (i32) -> i64 + %r = "llvm.and"(%ex, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `zext` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i32, %[[Y:.*]] : i32): +// CHECK: %[[OP:.*]] = "llvm.and"(%[[X]], %[[Y]]) : (i32, i32) -> i32 +// CHECK-NEXT: %[[E:.*]] = "llvm.zext"(%[[OP]]) : (i32) -> i64 +// CHECK: "func.return"(%[[E]]) : (i64) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i32, %[[NY:.*]] : i64): +// CHECK: %[[NEX:.*]] = "llvm.zext"(%[[NX]]) : (i32) -> i64 +// CHECK: %[[NR:.*]] = "llvm.and"(%[[NEX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/OrAndAnd.mlir b/Test/Passes/RISCVCombines/OrAndAnd.mlir new file mode 100644 index 000000000..4b3b46cf4 --- /dev/null +++ b/Test/Passes/RISCVCombines/OrAndAnd.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `or` through a shared masking `and`: `(X & Z) | (Y & Z)` equals `(X | Y) & Z`. Only fires when both inner `and`s share the same +// masking operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %ax = "llvm.and"(%x, %z) : (i64, i64) -> i64 + %ay = "llvm.and"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.or"(%ax, %ay) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct masks, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %ax = "llvm.and"(%x, %z0) : (i64, i64) -> i64 + %ay = "llvm.and"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.or"(%ax, %ay) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `or` now feeds a single masking `and`: (X or Y) & Z. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[MASK:.*]] = "llvm.and"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[MASK]]) : (i64) -> () + +// Distinct masks: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NAX:.*]] = "llvm.and"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NAY:.*]] = "llvm.and"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NAX]], %[[NAY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/OrAshrAshr.mlir b/Test/Passes/RISCVCombines/OrAshrAshr.mlir new file mode 100644 index 000000000..ee18ec4e8 --- /dev/null +++ b/Test/Passes/RISCVCombines/OrAshrAshr.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `or` through a shared arithmetic right shift: `(X >> Z) | (Y >> Z)` equals `(X | Y) >> Z` (the replicated sign bits agree bit-by-bit). Only fires when both arithmetic right shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.ashr"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.ashr"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.or"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.ashr"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.ashr"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.or"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `or` now feeds a single `ashr`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.ashr"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.ashr"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.ashr"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/OrLshrLshr.mlir b/Test/Passes/RISCVCombines/OrLshrLshr.mlir new file mode 100644 index 000000000..9879f3d27 --- /dev/null +++ b/Test/Passes/RISCVCombines/OrLshrLshr.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `or` through a shared logical right shift: `(X >> Z) | (Y >> Z)` equals `(X | Y) >> Z`. Only fires when both logical right shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.lshr"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.lshr"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.or"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.lshr"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.lshr"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.or"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `or` now feeds a single `lshr`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.lshr"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.lshr"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.lshr"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/OrSextSext.mlir b/Test/Passes/RISCVCombines/OrSextSext.mlir new file mode 100644 index 000000000..d1521e26b --- /dev/null +++ b/Test/Passes/RISCVCombines/OrSextSext.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A sign extension distributes over `or`: sign-extending the inputs then oring is the same as oring then sign-extending, because sign extension replicates the top bit uniformly. The two casts are hoisted through +// the bitwise op: `(sext X) or (sext Y) -> sext (X or Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i32, i32) -> i64}> ({ + ^bb0(%x: i32, %y: i32): + %ex = "llvm.sext"(%x) : (i32) -> i64 + %ey = "llvm.sext"(%y) : (i32) -> i64 + %r = "llvm.or"(%ex, %ey) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: only one operand is a `sext`, so nothing is hoisted. + "func.func"() <{function_type = (i32, i64) -> i64}> ({ + ^bb0(%x: i32, %y: i64): + %ex = "llvm.sext"(%x) : (i32) -> i64 + %r = "llvm.or"(%ex, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `sext` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i32, %[[Y:.*]] : i32): +// CHECK: %[[OP:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i32, i32) -> i32 +// CHECK-NEXT: %[[E:.*]] = "llvm.sext"(%[[OP]]) : (i32) -> i64 +// CHECK: "func.return"(%[[E]]) : (i64) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i32, %[[NY:.*]] : i64): +// CHECK: %[[NEX:.*]] = "llvm.sext"(%[[NX]]) : (i32) -> i64 +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NEX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/OrShlShl.mlir b/Test/Passes/RISCVCombines/OrShlShl.mlir new file mode 100644 index 000000000..09d1a393d --- /dev/null +++ b/Test/Passes/RISCVCombines/OrShlShl.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `or` through a shared left shift: `(X << Z) | (Y << Z)` equals `(X | Y) << Z`. Only fires when both left shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.shl"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.shl"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.or"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.shl"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.shl"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.or"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `or` now feeds a single `shl`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.shl"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.shl"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.shl"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/OrTruncTrunc.mlir b/Test/Passes/RISCVCombines/OrTruncTrunc.mlir new file mode 100644 index 000000000..8f0a1b6a2 --- /dev/null +++ b/Test/Passes/RISCVCombines/OrTruncTrunc.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A truncation distributes over `or`: truncating the inputs then oring keeps exactly the low bits that oring-then-truncating would. The two casts are hoisted through +// the bitwise op: `(trunc X) or (trunc Y) -> trunc (X or Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i32}> ({ + ^bb0(%x: i64, %y: i64): + %ex = "llvm.trunc"(%x) : (i64) -> i32 + %ey = "llvm.trunc"(%y) : (i64) -> i32 + %r = "llvm.or"(%ex, %ey) : (i32, i32) -> i32 + "func.return"(%r) : (i32) -> () + }) : () -> () + + // Negative case: only one operand is a `trunc`, so nothing is hoisted. + "func.func"() <{function_type = (i64, i32) -> i32}> ({ + ^bb0(%x: i64, %y: i32): + %ex = "llvm.trunc"(%x) : (i64) -> i32 + %r = "llvm.or"(%ex, %y) : (i32, i32) -> i32 + "func.return"(%r) : (i32) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `trunc` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64): +// CHECK: %[[OP:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[E:.*]] = "llvm.trunc"(%[[OP]]) : (i64) -> i32 +// CHECK: "func.return"(%[[E]]) : (i32) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i32): +// CHECK: %[[NEX:.*]] = "llvm.trunc"(%[[NX]]) : (i64) -> i32 +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NEX]], %[[NY]]) : (i32, i32) -> i32 +// CHECK: "func.return"(%[[NR]]) : (i32) -> () diff --git a/Test/Passes/RISCVCombines/OrZextZext.mlir b/Test/Passes/RISCVCombines/OrZextZext.mlir new file mode 100644 index 000000000..a86514275 --- /dev/null +++ b/Test/Passes/RISCVCombines/OrZextZext.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A zero extension distributes over `or`: zero-extending the inputs then oring is the same as oring then zero-extending, because the added high bits are all zero. The two casts are hoisted through +// the bitwise op: `(zext X) or (zext Y) -> zext (X or Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i32, i32) -> i64}> ({ + ^bb0(%x: i32, %y: i32): + %ex = "llvm.zext"(%x) : (i32) -> i64 + %ey = "llvm.zext"(%y) : (i32) -> i64 + %r = "llvm.or"(%ex, %ey) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: only one operand is a `zext`, so nothing is hoisted. + "func.func"() <{function_type = (i32, i64) -> i64}> ({ + ^bb0(%x: i32, %y: i64): + %ex = "llvm.zext"(%x) : (i32) -> i64 + %r = "llvm.or"(%ex, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `zext` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i32, %[[Y:.*]] : i32): +// CHECK: %[[OP:.*]] = "llvm.or"(%[[X]], %[[Y]]) : (i32, i32) -> i32 +// CHECK-NEXT: %[[E:.*]] = "llvm.zext"(%[[OP]]) : (i32) -> i64 +// CHECK: "func.return"(%[[E]]) : (i64) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i32, %[[NY:.*]] : i64): +// CHECK: %[[NEX:.*]] = "llvm.zext"(%[[NX]]) : (i32) -> i64 +// CHECK: %[[NR:.*]] = "llvm.or"(%[[NEX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/XorAndAnd.mlir b/Test/Passes/RISCVCombines/XorAndAnd.mlir new file mode 100644 index 000000000..493042271 --- /dev/null +++ b/Test/Passes/RISCVCombines/XorAndAnd.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `xor` through a shared masking `and`: `(X & Z) ^ (Y & Z)` equals `(X ^ Y) & Z`. Only fires when both inner `and`s share the same +// masking operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %ax = "llvm.and"(%x, %z) : (i64, i64) -> i64 + %ay = "llvm.and"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.xor"(%ax, %ay) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct masks, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %ax = "llvm.and"(%x, %z0) : (i64, i64) -> i64 + %ay = "llvm.and"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.xor"(%ax, %ay) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `xor` now feeds a single masking `and`: (X xor Y) & Z. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[MASK:.*]] = "llvm.and"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[MASK]]) : (i64) -> () + +// Distinct masks: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NAX:.*]] = "llvm.and"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NAY:.*]] = "llvm.and"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NAX]], %[[NAY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/XorAshrAshr.mlir b/Test/Passes/RISCVCombines/XorAshrAshr.mlir new file mode 100644 index 000000000..7de216d59 --- /dev/null +++ b/Test/Passes/RISCVCombines/XorAshrAshr.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `xor` through a shared arithmetic right shift: `(X >> Z) ^ (Y >> Z)` equals `(X ^ Y) >> Z` (the replicated sign bits cancel bit-by-bit). Only fires when both arithmetic right shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.ashr"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.ashr"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.xor"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.ashr"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.ashr"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.xor"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `xor` now feeds a single `ashr`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.ashr"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.ashr"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.ashr"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/XorLshrLshr.mlir b/Test/Passes/RISCVCombines/XorLshrLshr.mlir new file mode 100644 index 000000000..55b7fbc99 --- /dev/null +++ b/Test/Passes/RISCVCombines/XorLshrLshr.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `xor` through a shared logical right shift: `(X >> Z) ^ (Y >> Z)` equals `(X ^ Y) >> Z`. Only fires when both logical right shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.lshr"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.lshr"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.xor"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.lshr"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.lshr"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.xor"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `xor` now feeds a single `lshr`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.lshr"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.lshr"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.lshr"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/XorSextSext.mlir b/Test/Passes/RISCVCombines/XorSextSext.mlir new file mode 100644 index 000000000..6be2c7900 --- /dev/null +++ b/Test/Passes/RISCVCombines/XorSextSext.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A sign extension distributes over `xor`: sign-extending the inputs then xoring is the same as xoring then sign-extending, because sign extension replicates the top bit uniformly. The two casts are hoisted through +// the bitwise op: `(sext X) xor (sext Y) -> sext (X xor Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i32, i32) -> i64}> ({ + ^bb0(%x: i32, %y: i32): + %ex = "llvm.sext"(%x) : (i32) -> i64 + %ey = "llvm.sext"(%y) : (i32) -> i64 + %r = "llvm.xor"(%ex, %ey) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: only one operand is a `sext`, so nothing is hoisted. + "func.func"() <{function_type = (i32, i64) -> i64}> ({ + ^bb0(%x: i32, %y: i64): + %ex = "llvm.sext"(%x) : (i32) -> i64 + %r = "llvm.xor"(%ex, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `sext` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i32, %[[Y:.*]] : i32): +// CHECK: %[[OP:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i32, i32) -> i32 +// CHECK-NEXT: %[[E:.*]] = "llvm.sext"(%[[OP]]) : (i32) -> i64 +// CHECK: "func.return"(%[[E]]) : (i64) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i32, %[[NY:.*]] : i64): +// CHECK: %[[NEX:.*]] = "llvm.sext"(%[[NX]]) : (i32) -> i64 +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NEX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/XorShlShl.mlir b/Test/Passes/RISCVCombines/XorShlShl.mlir new file mode 100644 index 000000000..0a09c117a --- /dev/null +++ b/Test/Passes/RISCVCombines/XorShlShl.mlir @@ -0,0 +1,36 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// Distributing `xor` through a shared left shift: `(X << Z) ^ (Y << Z)` equals `(X ^ Y) << Z`. Only fires when both left shifts share the same +// second operand `Z`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z: i64): + %sx = "llvm.shl"(%x, %z) : (i64, i64) -> i64 + %sy = "llvm.shl"(%y, %z) : (i64, i64) -> i64 + %r = "llvm.xor"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct second operands, so the rule must not fire. + "func.func"() <{function_type = (i64, i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %z0: i64, %z1: i64): + %sx = "llvm.shl"(%x, %z0) : (i64, i64) -> i64 + %sy = "llvm.shl"(%y, %z1) : (i64, i64) -> i64 + %r = "llvm.xor"(%sx, %sy) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `xor` now feeds a single `shl`. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64, %[[Z:.*]] : i64): +// CHECK: %[[OUT:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[SH:.*]] = "llvm.shl"(%[[OUT]], %[[Z]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[SH]]) : (i64) -> () + +// Distinct second operands: nothing is combined. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NZ0:.*]] : i64, %[[NZ1:.*]] : i64): +// CHECK: %[[NSX:.*]] = "llvm.shl"(%[[NX]], %[[NZ0]]) +// CHECK: %[[NSY:.*]] = "llvm.shl"(%[[NY]], %[[NZ1]]) +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NSX]], %[[NSY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/XorTruncTrunc.mlir b/Test/Passes/RISCVCombines/XorTruncTrunc.mlir new file mode 100644 index 000000000..7c9a22d64 --- /dev/null +++ b/Test/Passes/RISCVCombines/XorTruncTrunc.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A truncation distributes over `xor`: truncating the inputs then xoring keeps exactly the low bits that xoring-then-truncating would. The two casts are hoisted through +// the bitwise op: `(trunc X) xor (trunc Y) -> trunc (X xor Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i32}> ({ + ^bb0(%x: i64, %y: i64): + %ex = "llvm.trunc"(%x) : (i64) -> i32 + %ey = "llvm.trunc"(%y) : (i64) -> i32 + %r = "llvm.xor"(%ex, %ey) : (i32, i32) -> i32 + "func.return"(%r) : (i32) -> () + }) : () -> () + + // Negative case: only one operand is a `trunc`, so nothing is hoisted. + "func.func"() <{function_type = (i64, i32) -> i32}> ({ + ^bb0(%x: i64, %y: i32): + %ex = "llvm.trunc"(%x) : (i64) -> i32 + %r = "llvm.xor"(%ex, %y) : (i32, i32) -> i32 + "func.return"(%r) : (i32) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `trunc` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64): +// CHECK: %[[OP:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[E:.*]] = "llvm.trunc"(%[[OP]]) : (i64) -> i32 +// CHECK: "func.return"(%[[E]]) : (i32) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i32): +// CHECK: %[[NEX:.*]] = "llvm.trunc"(%[[NX]]) : (i64) -> i32 +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NEX]], %[[NY]]) : (i32, i32) -> i32 +// CHECK: "func.return"(%[[NR]]) : (i32) -> () diff --git a/Test/Passes/RISCVCombines/XorZextZext.mlir b/Test/Passes/RISCVCombines/XorZextZext.mlir new file mode 100644 index 000000000..56de49078 --- /dev/null +++ b/Test/Passes/RISCVCombines/XorZextZext.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A zero extension distributes over `xor`: zero-extending the inputs then xoring is the same as xoring then zero-extending, because the added high bits are all zero. The two casts are hoisted through +// the bitwise op: `(zext X) xor (zext Y) -> zext (X xor Y)`, doing the +// bitwise op on the narrow operands and casting once. + +"builtin.module"() ({ + "func.func"() <{function_type = (i32, i32) -> i64}> ({ + ^bb0(%x: i32, %y: i32): + %ex = "llvm.zext"(%x) : (i32) -> i64 + %ey = "llvm.zext"(%y) : (i32) -> i64 + %r = "llvm.xor"(%ex, %ey) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: only one operand is a `zext`, so nothing is hoisted. + "func.func"() <{function_type = (i32, i64) -> i64}> ({ + ^bb0(%x: i32, %y: i64): + %ex = "llvm.zext"(%x) : (i32) -> i64 + %r = "llvm.xor"(%ex, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The bitwise op runs on the narrow operands, then a single `zext` widens. +// CHECK: ^{{.*}}(%[[X:.*]] : i32, %[[Y:.*]] : i32): +// CHECK: %[[OP:.*]] = "llvm.xor"(%[[X]], %[[Y]]) : (i32, i32) -> i32 +// CHECK-NEXT: %[[E:.*]] = "llvm.zext"(%[[OP]]) : (i32) -> i64 +// CHECK: "func.return"(%[[E]]) : (i64) -> () + +// Single cast: the pattern does not fire. +// CHECK: ^{{.*}}(%[[NX:.*]] : i32, %[[NY:.*]] : i64): +// CHECK: %[[NEX:.*]] = "llvm.zext"(%[[NX]]) : (i32) -> i64 +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NEX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/select_constant_cmp_false.mlir b/Test/Passes/RISCVCombines/select_constant_cmp_false.mlir new file mode 100644 index 000000000..63d877b46 --- /dev/null +++ b/Test/Passes/RISCVCombines/select_constant_cmp_false.mlir @@ -0,0 +1,31 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A `select` with a constant-`0` condition always picks the false operand: +// `(0 ? x : y) -> y`. The `select` is erased and its uses forwarded to `y`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%t: i64, %f: i64): + %c = "llvm.mlir.constant"() <{value = 0 : i1}> : () -> i1 + %r = "llvm.select"(%c, %t, %f) : (i1, i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: a constant-`1` condition is handled by the `true` rule, not + // this one -- here it must not fold to the false operand. + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%t: i64, %f: i64): + %c = "llvm.mlir.constant"() <{value = 1 : i1}> : () -> i1 + %r = "llvm.select"(%c, %t, %f) : (i1, i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `select` is gone; the false operand is returned directly. +// CHECK: ^{{.*}}(%{{.*}} : i64, %[[F:.*]] : i64): +// CHECK-NOT: "llvm.select" +// CHECK: "func.return"(%[[F]]) : (i64) -> () + +// The constant-`1` case folds to the true operand, not the false one. +// CHECK: ^{{.*}}(%[[T:.*]] : i64, %{{.*}} : i64): +// CHECK: "func.return"(%[[T]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/select_constant_cmp_true.mlir b/Test/Passes/RISCVCombines/select_constant_cmp_true.mlir new file mode 100644 index 000000000..b32614d8a --- /dev/null +++ b/Test/Passes/RISCVCombines/select_constant_cmp_true.mlir @@ -0,0 +1,30 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A `select` with a constant-`1` condition always picks the true operand: +// `(1 ? x : y) -> x`. The `select` is erased and its uses forwarded to `x`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%t: i64, %f: i64): + %c = "llvm.mlir.constant"() <{value = 1 : i1}> : () -> i1 + %r = "llvm.select"(%c, %t, %f) : (i1, i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: a non-constant condition leaves the `select` in place. + "func.func"() <{function_type = (i1, i64, i64) -> i64}> ({ + ^bb0(%c: i1, %t: i64, %f: i64): + %r = "llvm.select"(%c, %t, %f) : (i1, i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `select` is gone; the true operand is returned directly. +// CHECK: ^{{.*}}(%[[T:.*]] : i64, %{{.*}} : i64): +// CHECK-NOT: "llvm.select" +// CHECK: "func.return"(%[[T]]) : (i64) -> () + +// The non-constant-condition `select` survives. +// CHECK: ^{{.*}}(%[[C:.*]] : i1, %[[NT:.*]] : i64, %[[NF:.*]] : i64): +// CHECK: %[[SEL:.*]] = "llvm.select"(%[[C]], %[[NT]], %[[NF]]) +// CHECK: "func.return"(%[[SEL]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/select_same_val_self.mlir b/Test/Passes/RISCVCombines/select_same_val_self.mlir new file mode 100644 index 000000000..8dc5243c8 --- /dev/null +++ b/Test/Passes/RISCVCombines/select_same_val_self.mlir @@ -0,0 +1,30 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// A `select` whose true and false operands are the same value `x` always yields +// `x`, regardless of the condition: `(c ? x : x) -> x`. The `select` (and the +// dead condition) can be dropped. + +"builtin.module"() ({ + "func.func"() <{function_type = (i1, i64) -> i64}> ({ + ^bb0(%c: i1, %x: i64): + %r = "llvm.select"(%c, %x, %x) : (i1, i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: distinct arms, so the `select` must stay. + "func.func"() <{function_type = (i1, i64, i64) -> i64}> ({ + ^bb0(%c: i1, %x: i64, %y: i64): + %r = "llvm.select"(%c, %x, %y) : (i1, i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `select` is gone; the function returns its operand directly. +// CHECK: ^{{.*}}(%{{.*}} : i1, %[[X:.*]] : i64): +// CHECK-NOT: "llvm.select" +// CHECK: "func.return"(%[[X]]) : (i64) -> () + +// The distinct-arm `select` survives. +// CHECK: ^{{.*}}(%[[C:.*]] : i1, %[[NX:.*]] : i64, %[[NY:.*]] : i64): +// CHECK: %[[SEL:.*]] = "llvm.select"(%[[C]], %[[NX]], %[[NY]]) +// CHECK: "func.return"(%[[SEL]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_x.mlir b/Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_x.mlir new file mode 100644 index 000000000..04747bdde --- /dev/null +++ b/Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_x.mlir @@ -0,0 +1,32 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// `(x + y) - x` cancels the `+ x`/`- x` pair and simplifies to `y`. The `sub` +// is erased and its uses forwarded to `y`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64): + %add = "llvm.add"(%x, %y) : (i64, i64) -> i64 + %s = "llvm.sub"(%add, %x) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () + + // Negative case: the subtrahend is not the `add`'s first operand, so no fold. + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %w: i64): + %add = "llvm.add"(%x, %y) : (i64, i64) -> i64 + %s = "llvm.sub"(%add, %w) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `sub` is gone; `y` is returned directly (the dead `add` may remain). +// CHECK: ^{{.*}}(%{{.*}} : i64, %[[Y:.*]] : i64): +// CHECK-NOT: "llvm.sub" +// CHECK: "func.return"(%[[Y]]) : (i64) -> () + +// Unrelated subtrahend: the `sub` survives. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NW:.*]] : i64): +// CHECK: %[[NADD:.*]] = "llvm.add"(%[[NX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: %[[NSUB:.*]] = "llvm.sub"(%[[NADD]], %[[NW]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NSUB]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_y.mlir b/Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_y.mlir new file mode 100644 index 000000000..ee04da274 --- /dev/null +++ b/Test/Passes/RISCVCombines/sub_add_reg_x_add_y_sub_y.mlir @@ -0,0 +1,32 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// `(x + y) - y` cancels the `+ y`/`- y` pair and simplifies to `x`. The `sub` +// is erased and its uses forwarded to `x`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64): + %add = "llvm.add"(%x, %y) : (i64, i64) -> i64 + %s = "llvm.sub"(%add, %y) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () + + // Negative case: the subtrahend is not the `add`'s second operand, so no fold. + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %w: i64): + %add = "llvm.add"(%x, %y) : (i64, i64) -> i64 + %s = "llvm.sub"(%add, %w) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () +}) : () -> () + +// The `sub` is gone; `x` is returned directly (the dead `add` may remain). +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %{{.*}} : i64): +// CHECK-NOT: "llvm.sub" +// CHECK: "func.return"(%[[X]]) : (i64) -> () + +// Unrelated subtrahend: the `sub` survives. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NW:.*]] : i64): +// CHECK: %[[NADD:.*]] = "llvm.add"(%[[NX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: %[[NSUB:.*]] = "llvm.sub"(%[[NADD]], %[[NW]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NSUB]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/sub_add_reg_x_sub_x_add_y.mlir b/Test/Passes/RISCVCombines/sub_add_reg_x_sub_x_add_y.mlir new file mode 100644 index 000000000..de1441743 --- /dev/null +++ b/Test/Passes/RISCVCombines/sub_add_reg_x_sub_x_add_y.mlir @@ -0,0 +1,33 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// `x - (x + y)` cancels the two `x` terms, leaving `-y`, which is materialized +// as `0 - y`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64): + %add = "llvm.add"(%x, %y) : (i64, i64) -> i64 + %s = "llvm.sub"(%x, %add) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () + + // Negative case: the minuend is not the `add`'s first operand, so no fold. + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %w: i64): + %add = "llvm.add"(%x, %y) : (i64, i64) -> i64 + %s = "llvm.sub"(%w, %add) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () +}) : () -> () + +// The result is `0 - y`. +// CHECK: ^{{.*}}(%{{.*}} : i64, %[[Y:.*]] : i64): +// CHECK: %[[ZERO:.*]] = "llvm.mlir.constant"() <{"value" = 0 : i64}> : () -> i64 +// CHECK-NEXT: %[[NEG:.*]] = "llvm.sub"(%[[ZERO]], %[[Y]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NEG]]) : (i64) -> () + +// Unrelated minuend: the original `sub` survives. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NW:.*]] : i64): +// CHECK: %[[NADD:.*]] = "llvm.add"(%[[NX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: %[[NSUB:.*]] = "llvm.sub"(%[[NW]], %[[NADD]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NSUB]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/sub_add_reg_x_sub_y_add_x.mlir b/Test/Passes/RISCVCombines/sub_add_reg_x_sub_y_add_x.mlir new file mode 100644 index 000000000..43bb09e81 --- /dev/null +++ b/Test/Passes/RISCVCombines/sub_add_reg_x_sub_y_add_x.mlir @@ -0,0 +1,33 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// `x - (y + x)` cancels the two `x` terms, leaving `-y`, which is materialized +// as `0 - y`. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64): + %add = "llvm.add"(%y, %x) : (i64, i64) -> i64 + %s = "llvm.sub"(%x, %add) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () + + // Negative case: the minuend is not the `add`'s second operand, so no fold. + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %w: i64): + %add = "llvm.add"(%y, %x) : (i64, i64) -> i64 + %s = "llvm.sub"(%w, %add) : (i64, i64) -> i64 + "func.return"(%s) : (i64) -> () + }) : () -> () +}) : () -> () + +// The result is `0 - y`. +// CHECK: ^{{.*}}(%{{.*}} : i64, %[[Y:.*]] : i64): +// CHECK: %[[ZERO:.*]] = "llvm.mlir.constant"() <{"value" = 0 : i64}> : () -> i64 +// CHECK-NEXT: %[[NEG:.*]] = "llvm.sub"(%[[ZERO]], %[[Y]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NEG]]) : (i64) -> () + +// Unrelated minuend: the original `sub` survives. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NW:.*]] : i64): +// CHECK: %[[NADD:.*]] = "llvm.add"(%[[NY]], %[[NX]]) : (i64, i64) -> i64 +// CHECK: %[[NSUB:.*]] = "llvm.sub"(%[[NW]], %[[NADD]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NSUB]]) : (i64) -> () diff --git a/Test/Passes/RISCVCombines/xor_of_and_with_same_reg.mlir b/Test/Passes/RISCVCombines/xor_of_and_with_same_reg.mlir new file mode 100644 index 000000000..776f899f2 --- /dev/null +++ b/Test/Passes/RISCVCombines/xor_of_and_with_same_reg.mlir @@ -0,0 +1,35 @@ +// RUN: veir-opt %s -p=riscv-combine | filecheck %s + +// `(x & y) ^ y` clears the bits of `y` that are set in `x` and keeps the rest, +// i.e. `(~x) & y`. It rewrites to `and (xor x, -1), y` -- an `andn`-shaped form. + +"builtin.module"() ({ + "func.func"() <{function_type = (i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64): + %a = "llvm.and"(%x, %y) : (i64, i64) -> i64 + %r = "llvm.xor"(%a, %y) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () + + // Negative case: the `xor`'s second operand is not the `and`'s operand, so no + // fold. + "func.func"() <{function_type = (i64, i64, i64) -> i64}> ({ + ^bb0(%x: i64, %y: i64, %w: i64): + %a = "llvm.and"(%x, %y) : (i64, i64) -> i64 + %r = "llvm.xor"(%a, %w) : (i64, i64) -> i64 + "func.return"(%r) : (i64) -> () + }) : () -> () +}) : () -> () + +// Rewritten to (x ^ -1) & y. +// CHECK: ^{{.*}}(%[[X:.*]] : i64, %[[Y:.*]] : i64): +// CHECK: %[[M1:.*]] = "llvm.mlir.constant"() <{"value" = -1 : i64}> : () -> i64 +// CHECK-NEXT: %[[NOTX:.*]] = "llvm.xor"(%[[X]], %[[M1]]) : (i64, i64) -> i64 +// CHECK-NEXT: %[[RES:.*]] = "llvm.and"(%[[NOTX]], %[[Y]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[RES]]) : (i64) -> () + +// Unrelated `xor` operand: the original ops survive. +// CHECK: ^{{.*}}(%[[NX:.*]] : i64, %[[NY:.*]] : i64, %[[NW:.*]] : i64): +// CHECK: %[[NA:.*]] = "llvm.and"(%[[NX]], %[[NY]]) : (i64, i64) -> i64 +// CHECK: %[[NR:.*]] = "llvm.xor"(%[[NA]], %[[NW]]) : (i64, i64) -> i64 +// CHECK: "func.return"(%[[NR]]) : (i64) -> () diff --git a/Veir/Passes/RISCVCombines/Combine.lean b/Veir/Passes/RISCVCombines/Combine.lean index 13cf244fe..094e6cc1c 100644 --- a/Veir/Passes/RISCVCombines/Combine.lean +++ b/Veir/Passes/RISCVCombines/Combine.lean @@ -70,7 +70,7 @@ def AndSextSext (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchSext dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] () (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -85,7 +85,7 @@ def OrSextSext (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchSext dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] oprops (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -100,7 +100,7 @@ def XorSextSext (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchSext dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] xprops (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .sext) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -115,7 +115,7 @@ def AndZextZext (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchZext dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] () (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -130,7 +130,7 @@ def OrZextZext (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchZext dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] oprops (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -145,7 +145,7 @@ def XorZextZext (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchZext dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] xprops (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .zext) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -160,7 +160,7 @@ def AndTruncTrunc (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchTrunc dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .and) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] () (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -175,7 +175,7 @@ def OrTruncTrunc (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchTrunc dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .or) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] oprops (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry @@ -190,7 +190,7 @@ def XorTruncTrunc (rewriter: PatternRewriter OpCode) (op: OperationPtr) let some (y, yp) := matchTrunc dY rewriter.ctx | return rewriter let (rewriter, inner) ← rewriter.createOp (.llvm .xor) #[x.getType! rewriter.ctx.raw] #[x, y] #[] #[] xprops (some $ .before op) sorry sorry sorry sorry - let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[x.getType! rewriter.ctx.raw] #[(inner.getResult 0)] + let (rewriter, newOp) ← rewriter.createOp (.llvm .trunc) #[(op.getResult 0 : ValuePtr).getType! rewriter.ctx.raw] #[(inner.getResult 0)] #[] #[] yp (some $ .before op) sorry sorry sorry sorry rewriter.replaceOp op newOp sorry sorry sorry sorry sorry