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

View as plain text