[llvm] [InstCombine] Canonicalize complex boolean expressions into ~((y | z) ^ x) via 3-input truth table (PR #149530)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 22 09:38:38 PDT 2025


================
@@ -0,0 +1,375 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+; Tests for GitHub issue #97044 - Boolean expression canonicalization
+define i32 @test0_4way_or(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test0_4way_or(
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT:    [[OR13:%.*]] = xor i32 [[TMP2]], -1
+; CHECK-NEXT:    ret i32 [[OR13]]
+;
+  %not = xor i32 %z, -1
+  %and = and i32 %y, %not
+  %and1 = and i32 %and, %x
+  %not2 = xor i32 %y, -1
+  %and3 = and i32 %x, %not2
+  %and4 = and i32 %and3, %z
+  %or = or i32 %and1, %and4
+  %not5 = xor i32 %x, -1
+  %not6 = xor i32 %y, -1
+  %and7 = and i32 %not5, %not6
+  %not8 = xor i32 %z, -1
+  %and9 = and i32 %and7, %not8
+  %or10 = or i32 %or, %and9
+  %and11 = and i32 %x, %y
+  %and12 = and i32 %and11, %z
+  %or13 = or i32 %or10, %and12
+  ret i32 %or13
+}
+define i32 @test1_xor_pattern(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test1_xor_pattern(
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[TMP2]], -1
+; CHECK-NEXT:    ret i32 [[XOR]]
+;
+  %not = xor i32 %z, -1
+  %and = and i32 %x, %y
+  %not1 = xor i32 %x, -1
+  %not2 = xor i32 %y, -1
+  %and3 = and i32 %not1, %not2
+  %or = or i32 %and, %and3
+  %and4 = and i32 %not, %or
+  %and5 = and i32 %x, %y
+  %and6 = and i32 %x, %not2
+  %or7 = or i32 %and5, %and6
+  %and8 = and i32 %z, %or7
+  %xor = xor i32 %and4, %and8
+  ret i32 %xor
+}
+define i32 @test2_nested_xor(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test2_nested_xor(
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT:    [[TMP3:%.*]] = xor i32 [[TMP2]], [[Y]]
+; CHECK-NEXT:    ret i32 [[TMP3]]
+;
+  %and = and i32 %x, %y
+  %not = xor i32 %x, -1
+  %not1 = xor i32 %y, -1
+  %and2 = and i32 %not, %not1
+  %or = or i32 %and, %and2
+  %and3 = and i32 %x, %y
+  %not4 = xor i32 %y, -1
+  %and5 = and i32 %x, %not4
+  %or6 = or i32 %and3, %and5
+  %xor = xor i32 %or, %or6
+  %not7 = xor i32 %y, -1
+  %and8 = and i32 %z, %not7
+  %and9 = and i32 %xor, %and8
+  %xor10 = xor i32 %or, %and9
+  %xor11 = xor i32 %xor10, %y
+  %xor12 = xor i32 %xor11, -1
+  ret i32 %xor12
+}
+define i32 @test3_already_optimal(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test3_already_optimal(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[OR]], [[X:%.*]]
+; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[XOR]], -1
+; CHECK-NEXT:    ret i32 [[NOT]]
+;
+  %or = or i32 %y, %z
+  %xor = xor i32 %or, %x
+  %not = xor i32 %xor, -1
+  ret i32 %not
+}
+
+define i32 @test_add_as_leaf(i32 %x, i32 %y, i32 %c) {
+; CHECK-LABEL: @test_add_as_leaf(
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[C:%.*]]
+; CHECK-NEXT:    [[NOT3:%.*]] = xor i32 [[X]], -1
+; CHECK-NEXT:    [[AND4:%.*]] = and i32 [[ADD]], [[NOT3]]
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[Y:%.*]], [[AND4]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[TMP1]], [[ADD]]
+; CHECK-NEXT:    ret i32 [[XOR]]
+;
+  %add = add i32 %x, %c
+  %not1 = xor i32 %add, -1
+  %and1 = and i32 %not1, %y
+  %not2 = xor i32 %y, -1
+  %and2 = and i32 %add, %not2
+  %or = or i32 %and1, %and2
+  %and3 = and i32 %x, %y
+  %not3 = xor i32 %x, -1
+  %and4 = and i32 %not3, %add
+  %xor = xor i32 %or, %and4
+  ret i32 %xor
+}
+
+define i32 @test_sub_as_leaf(i32 %a, i32 %b, i32 %offset) {
+; CHECK-LABEL: @test_sub_as_leaf(
+; CHECK-NEXT:    [[SUB:%.*]] = sub i32 [[A:%.*]], [[OFFSET:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[B:%.*]], [[SUB]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[A]]
+; CHECK-NEXT:    [[RESULT:%.*]] = xor i32 [[TMP2]], -1
+; CHECK-NEXT:    ret i32 [[RESULT]]
+;
+  %sub = sub i32 %a, %offset
+  %not1 = xor i32 %sub, -1
+  %and1 = and i32 %not1, %b
+  %and2 = and i32 %and1, %a
+  %not2 = xor i32 %b, -1
+  %and3 = and i32 %a, %not2
+  %and4 = and i32 %and3, %sub
+  %or = or i32 %and2, %and4
+  %not3 = xor i32 %a, -1
+  %not4 = xor i32 %b, -1
+  %and5 = and i32 %not3, %not4
+  %not5 = xor i32 %sub, -1
+  %and6 = and i32 %and5, %not5
+  %or2 = or i32 %or, %and6
+  %and7 = and i32 %a, %b
+  %and8 = and i32 %and7, %sub
+  %result = or i32 %or2, %and8
+  ret i32 %result
+}
+
+; ==============================
+;       Negative Tests
+; ==============================
+
+define i32 @negative_non_bitwise_add(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @negative_non_bitwise_add(
+; CHECK-NEXT:    [[ADD1:%.*]] = add i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[ADD2:%.*]] = add i32 [[ADD1]], [[Z:%.*]]
+; CHECK-NEXT:    ret i32 [[ADD2]]
+;
+  %add1 = add i32 %x, %y
+  %add2 = add i32 %add1, %z
+  ret i32 %add2
+}
+define i32 @negative_two_variables(i32 %x, i32 %y) {
+; CHECK-LABEL: @negative_two_variables(
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[AND]], -1
+; CHECK-NEXT:    ret i32 [[NOT]]
+;
+  %and = and i32 %x, %y
+  %not = xor i32 %and, -1
+  ret i32 %not
+}
+define i32 @negative_four_variables(i32 %x, i32 %y, i32 %z, i32 %w) {
+; CHECK-LABEL: @negative_four_variables(
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[Z:%.*]], [[W:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[AND1]], [[AND2]]
+; CHECK-NEXT:    ret i32 [[OR]]
+;
+  %and1 = and i32 %x, %y
+  %and2 = and i32 %z, %w
+  %or = or i32 %and1, %and2
+  ret i32 %or
+}
+define i32 @negative_simple_expression(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @negative_simple_expression(
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[AND]], [[Z:%.*]]
+; CHECK-NEXT:    ret i32 [[OR]]
+;
+  %and = and i32 %x, %y
+  %or = or i32 %and, %z
+  ret i32 %or
+}
+define i32 @negative_different_basic_blocks(i32 %x, i32 %y, i32 %z, i1 %cond) {
+; CHECK-LABEL: @negative_different_basic_blocks(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[IF_TRUE:%.*]], label [[IF_FALSE:%.*]]
+; CHECK:       if.true:
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[AND1]], [[Z:%.*]]
+; CHECK-NEXT:    ret i32 [[AND2]]
+; CHECK:       if.false:
+; CHECK-NEXT:    ret i32 [[AND1]]
+;
+entry:
+  %and1 = and i32 %x, %y
+  br i1 %cond, label %if.true, label %if.false
+if.true:
+  %and2 = and i32 %and1, %z
+  ret i32 %and2
+if.false:
+  ret i32 %and1
+}
+define i32 @negative_two_vars_one_const(i32 %x, i32 %y) {
+; CHECK-LABEL: @negative_two_vars_one_const(
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[AND1]], 42
+; CHECK-NEXT:    [[AND3_DEMORGAN:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[AND3:%.*]] = xor i32 [[AND3_DEMORGAN]], -1
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[AND2]], [[AND3]]
+; CHECK-NEXT:    ret i32 [[OR]]
+;
+  %and1 = and i32 %x, %y
+  %and2 = and i32 %and1, 42
+  %not_x = xor i32 %x, -1
+  %not_y = xor i32 %y, -1
+  %and3 = and i32 %not_x, %not_y
+  %or = or i32 %and2, %and3
+  ret i32 %or
+}
+
+define i32 @negative_one_var_two_consts(i32 %x) {
+; CHECK-LABEL: @negative_one_var_two_consts(
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[X:%.*]], 7
+; CHECK-NEXT:    [[NOT_X:%.*]] = and i32 [[X]], 3
+; CHECK-NEXT:    [[AND3:%.*]] = xor i32 [[NOT_X]], 3
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[AND2]], [[AND3]]
+; CHECK-NEXT:    ret i32 [[OR]]
+;
+  %and1 = and i32 %x, 15
+  %and2 = and i32 %and1, 7
+  %not_x = xor i32 %x, -1
+  %and3 = and i32 %not_x, 3
+  %or = or i32 %and2, %and3
+  ret i32 %or
+}
+
+define i32 @negative_const_pattern_match(i32 %x, i32 %y) {
+; CHECK-LABEL: @negative_const_pattern_match(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], 255
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[OR]], [[X:%.*]]
+; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[XOR]], -1
+; CHECK-NEXT:    ret i32 [[NOT]]
+;
+  %or = or i32 %y, 255
+  %xor = xor i32 %or, %x
+  %not = xor i32 %xor, -1
+  ret i32 %not
+}
+
+define i32 @negative_mixed_vars_consts(i32 %x, i32 %y) {
+; CHECK-LABEL: @negative_mixed_vars_consts(
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[AND1]], 96
+; CHECK-NEXT:    [[NOT_X:%.*]] = and i32 [[X]], 170
+; CHECK-NEXT:    [[AND3:%.*]] = xor i32 [[NOT_X]], 170
+; CHECK-NEXT:    [[OR1:%.*]] = or i32 [[AND2]], [[AND3]]
+; CHECK-NEXT:    [[AND4:%.*]] = and i32 [[Y]], 204
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[OR1]], [[AND4]]
+; CHECK-NEXT:    ret i32 [[XOR]]
+;
+  %and1 = and i32 %x, %y
+  %and2 = and i32 %and1, 96
+  %not_x = xor i32 %x, -1
+  %and3 = and i32 %not_x, 170
+  %or1 = or i32 %and2, %and3
+  %and4 = and i32 %y, 204
+  %xor = xor i32 %or1, %and4
+  ret i32 %xor
+}
+
+define i32 @negative_const_blocks_extraction(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @negative_const_blocks_extraction(
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[X:%.*]], 42
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[OR1:%.*]] = or i32 [[AND1]], [[AND2]]
+; CHECK-NEXT:    [[AND3:%.*]] = and i32 [[X]], 24
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[OR1]], [[AND3]]
+; CHECK-NEXT:    ret i32 [[XOR]]
+;
+  %and1 = and i32 %x, 42
+  %and2 = and i32 %y, %z
+  %or1 = or i32 %and1, %and2
+  %and3 = and i32 %x, 24
+  %xor = xor i32 %or1, %and3
+  ret i32 %xor
+}
+
+define i32 @negative_single_use_add(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @negative_single_use_add(
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[ADD]], [[Z:%.*]]
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %add = add i32 %x, %y     ; Single-use non-bitwise op
+  %and = and i32 %add, %z   ; Only 2 variables: %add, %z (should not optimize)
+  ret i32 %and
+}
+
+; ==============================
+;       Multi-use Tests
+; ==============================
+declare void @use(i32)
+define i32 @multi_use_not(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @multi_use_not(
+; CHECK-NEXT:    [[NOT1:%.*]] = xor i32 [[X:%.*]], -1
+; CHECK-NEXT:    call void @use(i32 [[NOT1]])
+; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[TMP1]], [[NOT1]]
+; CHECK-NEXT:    ret i32 [[AND2]]
+;
+  %not1 = xor i32 %x, -1
+  call void @use(i32 %not1)
+  %and1 = and i32 %not1, %y
+  %and2 = and i32 %and1, %z
+  ret i32 %and2
+}
+define i32 @multi_use_binop(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @multi_use_binop(
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[AND1]])
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[AND1]], [[Z:%.*]]
+; CHECK-NEXT:    ret i32 [[AND2]]
+;
+  %and1 = and i32 %x, %y
+  call void @use(i32 %and1)
+  %and2 = and i32 %and1, %z
+  ret i32 %and2
+}
+define i32 @multi_use_multiple(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @multi_use_multiple(
+; CHECK-NEXT:    [[NOT1:%.*]] = xor i32 [[X:%.*]], -1
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[Y:%.*]], [[NOT1]]
+; CHECK-NEXT:    call void @use(i32 [[NOT1]])
+; CHECK-NEXT:    call void @use(i32 [[AND1]])
+; CHECK-NEXT:    [[AND2:%.*]] = and i32 [[AND1]], [[Z:%.*]]
+; CHECK-NEXT:    ret i32 [[AND2]]
+;
+  %not1 = xor i32 %x, -1
+  %and1 = and i32 %not1, %y
+  call void @use(i32 %not1)
+  call void @use(i32 %and1)
+  %and2 = and i32 %and1, %z
+  ret i32 %and2
+}
+
+define i32 @multi_use_add_as_variable(i32 %x, i32 %y, i32 %offset) {
+; CHECK-LABEL: @multi_use_add_as_variable(
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[X:%.*]], [[OFFSET:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[ADD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[ADD]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X]]
+; CHECK-NEXT:    [[RESULT:%.*]] = xor i32 [[TMP2]], -1
+; CHECK-NEXT:    ret i32 [[RESULT]]
+;
+  %add = add i32 %x, %offset    ; Multi-use non-bitwise op  
+  call void @use(i32 %add)      ; Extra use
+  %not1 = xor i32 %add, -1
+  %and1 = and i32 %not1, %y
+  %and2 = and i32 %and1, %x
+  %not2 = xor i32 %y, -1
+  %and3 = and i32 %x, %not2
+  %and4 = and i32 %and3, %add
+  %or = or i32 %and2, %and4
+  %not3 = xor i32 %x, -1
+  %not4 = xor i32 %y, -1
+  %and5 = and i32 %not3, %not4
+  %not5 = xor i32 %add, -1
+  %and6 = and i32 %and5, %not5
+  %or2 = or i32 %or, %and6
+  %and7 = and i32 %x, %y
+  %and8 = and i32 %and7, %add
+  %result = or i32 %or2, %and8
+  ret i32 %result
+}
----------------
dtcxzyw wrote:

Missing new line.

https://github.com/llvm/llvm-project/pull/149530


More information about the llvm-commits mailing list