Source file src/cmd/compile/internal/ssa/merge_conditional_branches_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssa
     6  
     7  import (
     8  	"cmd/compile/internal/types"
     9  	"testing"
    10  )
    11  
    12  // ARM64Lt specifies BlockARM64LT
    13  func ARM64Lt(cond, sub, alt string) ctrl {
    14  	return ctrl{BlockARM64LT, cond, []string{sub, alt}}
    15  }
    16  
    17  // ARM64Gt specifies BlockARM64GT
    18  func ARM64Gt(cond, sub, alt string) ctrl {
    19  	return ctrl{BlockARM64GT, cond, []string{sub, alt}}
    20  }
    21  
    22  // ARM64Ne specifies BlockARM64NE
    23  func ARM64Ne(cond, sub, alt string) ctrl {
    24  	return ctrl{BlockARM64NE, cond, []string{sub, alt}}
    25  }
    26  
    27  // ARM64Eq specifies BlockARM64EQ
    28  func ARM64Eq(cond, sub, alt string) ctrl {
    29  	return ctrl{BlockARM64EQ, cond, []string{sub, alt}}
    30  }
    31  
    32  // isNewConditionCorrect verifies that a block has been correctly transformed
    33  // to use conditional comparison (CCMP) with the expected parameters.
    34  // It checks:
    35  // - The block kind is BlockARM64LT (less than condition)
    36  // - The control operation is OpARM64CCMPconst (conditional comparison with constant)
    37  // - The condition code is OpARM64GreaterThan
    38  // - The NZCV flags are set to 1
    39  // - The constant value being compared is 4
    40  // Returns true if all conditions match the expected transformation pattern
    41  func isNewConditionCorrect(b *Block) bool {
    42  	if b.Kind != BlockARM64LT {
    43  		return false
    44  	}
    45  
    46  	v := b.Controls[0]
    47  	if v.Op != OpARM64CCMPconst {
    48  		return false
    49  	}
    50  
    51  	params := v.AuxArm64ConditionalParams()
    52  	if params.Cond() != OpARM64GreaterThan {
    53  		return false
    54  	}
    55  	if params.Nzcv() != 1 {
    56  		// NZCV flags should be set to 1 for this specific transformation
    57  		return false
    58  	}
    59  	if imm, ok := params.ConstValue(); !ok || imm != 4 {
    60  		return false
    61  	}
    62  
    63  	return true
    64  }
    65  
    66  // containsOpARM64CCMP checks if a block contains any ARM64 conditional comparison
    67  // operations (CCMP or CCMPconst). This is used in tests to verify that the
    68  // if-conversion optimization successfully generated conditional comparison
    69  // instructions or to ensure they were not generated when inappropriate.
    70  func containsOpARM64CCMP(b *Block) bool {
    71  	for _, v := range b.Values {
    72  		if v.Op == OpARM64CCMP || v.Op == OpARM64CCMPconst {
    73  			return true
    74  		}
    75  	}
    76  	return false
    77  }
    78  
    79  // TestMergeConditionalBranchesWithoutPointers tests the if-conversion optimization
    80  // on a simple case of logical AND (cond1 && cond2) without pointer operations.
    81  // The test verifies that:
    82  // - The optimization correctly transforms nested conditionals into CCMP instructions
    83  // - The block structure is properly simplified (inner block becomes plain and empty)
    84  // - The resulting control flow uses conditional comparison with correct parameters
    85  // - No important blocks are accidentally deleted during transformation
    86  // This represents the ideal case where the optimization should apply successfully.
    87  func TestMergeConditionalBranchesWithoutPointers(t *testing.T) {
    88  	t.Run("arm64", func(t *testing.T) {
    89  		c := testConfigArch(t, "arm64")
    90  		intType := c.config.Types.Int64
    91  		fun := c.Fun("entry",
    92  			Bloc("entry",
    93  				Valu("mem",
    94  					OpInitMem,
    95  					types.TypeMem,
    96  					0, nil,
    97  				),
    98  				Valu("a",
    99  					OpArg,
   100  					intType,
   101  					0, c.Temp(intType),
   102  				),
   103  				Valu("b",
   104  					OpArg,
   105  					intType,
   106  					1, c.Temp(intType),
   107  				),
   108  				Valu("cond1",
   109  					OpARM64CMPconst,
   110  					types.TypeFlags,
   111  					1, nil,
   112  					"a",
   113  				),
   114  				ARM64Gt("cond1", "second_comparison", "ret_false"),
   115  			),
   116  			Bloc("second_comparison",
   117  				Valu("cond2",
   118  					OpARM64CMPconst,
   119  					types.TypeFlags,
   120  					4, nil,
   121  					"b",
   122  				),
   123  				ARM64Lt("cond2", "ret_false", "ret_true"),
   124  			),
   125  			Bloc("ret_true",
   126  				Valu("const1",
   127  					OpARM64MOVDconst,
   128  					intType,
   129  					1, nil,
   130  				),
   131  				Valu("true_result",
   132  					OpMakeResult,
   133  					types.TypeMem,
   134  					0, nil,
   135  					"const1", "mem",
   136  				),
   137  				Ret("true_result"),
   138  			),
   139  			Bloc("ret_false",
   140  				Valu("const0",
   141  					OpARM64MOVDconst,
   142  					intType,
   143  					0, nil,
   144  				),
   145  				Valu("false_result",
   146  					OpMakeResult,
   147  					types.TypeMem,
   148  					0, nil,
   149  					"const0", "mem",
   150  				),
   151  				Ret("false_result"),
   152  			),
   153  		)
   154  
   155  		CheckFunc(fun.f)
   156  		mergeConditionalBranches(fun.f)
   157  		CheckFunc(fun.f)
   158  
   159  		if len(fun.blocks) != 4 {
   160  			t.Errorf("Important block was deleted")
   161  		}
   162  
   163  		entryBlock := fun.blocks["entry"]
   164  		secondBlock := fun.blocks["second_comparison"]
   165  
   166  		if secondBlock.Kind != BlockPlain || len(secondBlock.Values) != 0 {
   167  			t.Errorf("Block with second condition wasn't cleaned")
   168  		}
   169  
   170  		if !isNewConditionCorrect(entryBlock) {
   171  			t.Errorf("Entry block doesn't contain CCMP opertation")
   172  		}
   173  	})
   174  }
   175  
   176  // Test that pointer comparison with memory load doesn't generate CCMP
   177  func TestNoCCMPWithPointerAndMemoryLoad(t *testing.T) {
   178  	t.Run("arm64", func(t *testing.T) {
   179  		c := testConfigArch(t, "arm64")
   180  		intType := c.config.Types.Int64
   181  		ptrType := c.config.Types.BytePtr
   182  
   183  		fun := c.Fun("entry",
   184  			Bloc("entry",
   185  				Valu("mem",
   186  					OpInitMem,
   187  					types.TypeMem,
   188  					0, nil,
   189  				),
   190  				Valu("ptr",
   191  					OpArg,
   192  					ptrType,
   193  					0, c.Temp(ptrType),
   194  				),
   195  				Valu("cond1",
   196  					OpARM64CMPconst,
   197  					types.TypeFlags,
   198  					0, nil, // Compare with nil (0)
   199  					"ptr",
   200  				),
   201  				ARM64Ne("cond1", "second_comparison", "ret_false"), // ptr != nil
   202  			),
   203  			Bloc("second_comparison",
   204  				Valu("load",
   205  					OpLoad,
   206  					intType,
   207  					0, nil,
   208  					"ptr", "mem",
   209  				),
   210  				Valu("cond2",
   211  					OpARM64CMPconst,
   212  					types.TypeFlags,
   213  					3, nil, // Compare with 3
   214  					"load",
   215  				),
   216  				ARM64Eq("cond2", "ret_true", "ret_false"), // *ptr == 3
   217  			),
   218  			Bloc("ret_true",
   219  				Valu("const1",
   220  					OpARM64MOVDconst,
   221  					intType,
   222  					1, nil,
   223  				),
   224  				Valu("true_result",
   225  					OpMakeResult,
   226  					types.TypeMem,
   227  					0, nil,
   228  					"const1", "mem",
   229  				),
   230  				Ret("true_result"),
   231  			),
   232  			Bloc("ret_false",
   233  				Valu("const0",
   234  					OpARM64MOVDconst,
   235  					intType,
   236  					0, nil,
   237  				),
   238  				Valu("false_result",
   239  					OpMakeResult,
   240  					types.TypeMem,
   241  					0, nil,
   242  					"const0", "mem",
   243  				),
   244  				Ret("false_result"),
   245  			),
   246  		)
   247  
   248  		CheckFunc(fun.f)
   249  		mergeConditionalBranches(fun.f)
   250  		CheckFunc(fun.f)
   251  
   252  		// Verify that the second_comparison block still exists (not optimized away)
   253  		if fun.blocks["second_comparison"] == nil {
   254  			t.Errorf("Second comparison block was incorrectly removed")
   255  		}
   256  
   257  		entryBlock := fun.blocks["entry"]
   258  		secondBlock := fun.blocks["second_comparison"]
   259  
   260  		// Verify that entry block doesn't contain CCMP operation
   261  		if containsOpARM64CCMP(entryBlock) {
   262  			t.Errorf("Entry block contains CCMP operation, but shouldn't due to memory load")
   263  		}
   264  
   265  		// Verify that second block contains the load operation
   266  		hasLoad := false
   267  		for _, v := range secondBlock.Values {
   268  			if v.Op == OpLoad {
   269  				hasLoad = true
   270  				break
   271  			}
   272  		}
   273  		if !hasLoad {
   274  			t.Errorf("Second comparison block should contain load operation")
   275  		}
   276  
   277  		// The optimization shouldn't merge these blocks because of the memory operation
   278  		if secondBlock.Kind == BlockPlain {
   279  			t.Errorf("Block with memory load was incorrectly cleaned")
   280  		}
   281  	})
   282  }
   283  

View as plain text