| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ssa |
| |
| import ( |
| "cmd/compile/internal/types" |
| ) |
| |
| const ( |
| InvalidIndex = -1 + iota |
| TrueConditionSuccIndex // Index for true condition successor in block's successor list |
| FalseConditionSuccIndex // Index for false condition successor in block's successor list |
| ) |
| |
| // mergeConditionalBranches performs if-conversion optimization on ARM64 by |
| // transforming nested conditional branches into conditional comparison instructions. |
| // |
| // The optimization detects patterns where two consecutive conditional branches |
| // implement logical AND/OR operations and replaces them with CCMP/CCMN instructions |
| // that execute the second conditionally based on the first comparison result. |
| // |
| // Transformation Pattern: |
| // |
| // Original CFG: |
| // |
| // if1 (outer condition) |
| // / \ |
| // / \ |
| // / if2 (inner condition) |
| // | / \ |
| // | / \ |
| // | / \ |
| // b1 (common) b2 (target) |
| // |
| // Transformed CFG: |
| // |
| // new_if (conditional comparison) |
| // / \ |
| // / \ |
| // / p (empty plain block) |
| // | \ |
| // | \ |
| // | \ |
| // b1 (common) b2 (target) |
| // |
| // Transformation Conditions: |
| // - Both if1 and if2 must be ARM64 conditional blocks |
| // - if2 must have exactly one predecessor from if1 |
| // - if2 must not contain memory operations or side effects |
| // - The transformation must preserve phi node consistency in successor blocks |
| // |
| // This optimization eliminates branch mispredictions and improves instruction-level |
| // parallelism by leveraging ARM64's conditional execution capabilities. |
| // The resulting code uses conditional comparison instructions that test the second |
| // condition only if the first condition evaluates to a specific value. |
| func mergeConditionalBranches(f *Func) { |
| if f.Config.arch != "arm64" { |
| return |
| } |
| |
| // We iterate in postorder to ensure we process inner conditional blocks before |
| // their outer counterparts. This is crucial because: |
| // 1. Transformations create new conditional comparisons that combine inner and outer conditions |
| // 2. Processing inner blocks first ensures we don't miss nested patterns |
| // 3. It maintains the integrity of the CFG during transformations |
| // Reverse order (from leaves to root) allows safe modification without affecting |
| // yet-to-be-processed outer structures. |
| blocks := f.postorder() |
| |
| for _, block := range blocks { |
| // outerSuccIndex: index of the outedge from if1 to if2 |
| // innerSuccIndex: index of the outedge from if2 to b2 |
| outerSuccIndex, innerSuccIndex := detectNestedIfPattern(block) |
| |
| if outerSuccIndex != InvalidIndex && innerSuccIndex != InvalidIndex { |
| transformNestedIfPattern(block, outerSuccIndex, innerSuccIndex) |
| } |
| } |
| } |
| |
| // findFirstNonEmptyPlainBlock finds the first non-empty block in a chain of empty plain blocks |
| // starting from the specified child index of the parent block. It skips over empty blocks |
| // that serve only as pass-through nodes in the control flow graph. |
| func findFirstNonEmptyPlainBlock(parentBlock *Block, childIndex int) *Block { |
| childBlock := parentBlock.Succs[childIndex].Block() |
| for isEmptyPlainBlock(childBlock) { |
| childBlock = childBlock.Succs[0].Block() |
| } |
| return childBlock |
| } |
| |
| // isEmptyPlainBlock checks if a block is empty (contains no values), has exactly one |
| // predecessor and is of kind BlockPlain. Such blocks are typically |
| // artifacts of previous optimizations and can be safely removed or bypassed. |
| func isEmptyPlainBlock(block *Block) bool { |
| return block.Kind == BlockPlain && |
| len(block.Values) == 0 && |
| len(block.Preds) == 1 |
| } |
| |
| // removeEmptyPlainBlockChain removes a chain of empty plain blocks starting from |
| // the specified child index of the parent block. It traverses through consecutive |
| // empty blocks and deletes them from the control flow graph, connecting the parent |
| // directly to the first non-empty block in the chain. |
| func removeEmptyPlainBlockChain(parentBlock *Block, childIndex int) *Block { |
| childBlock := parentBlock.Succs[childIndex].Block() |
| for isEmptyPlainBlock(childBlock) { |
| nextBlock := childBlock.Succs[0].Block() |
| removeEmptyPlainBlock(childBlock) |
| childBlock = nextBlock |
| } |
| return childBlock |
| } |
| |
| // removeEmptyPlainBlock removes a single empty plain block from the control flow graph. |
| // It connects the block's predecessor directly to its successor, effectively bypassing |
| // the empty block, and then marks the block as invalid for future cleanup. |
| func removeEmptyPlainBlock(block *Block) { |
| prevEdge := block.Preds[0] |
| nextEdge := block.Succs[0] |
| |
| prevEdge.b.Succs[prevEdge.i] = nextEdge |
| nextEdge.b.Preds[nextEdge.i] = prevEdge |
| |
| block.removePred(0) |
| block.removeSucc(0) |
| block.Reset(BlockInvalid) |
| } |
| |
| // detectNestedIfPattern detects nested if patterns that can be transformed to conditional comparisons. |
| // It examines the outer block to see if it contains a nested conditional structure that matches |
| // the pattern for if-conversion. Returns the outer successor index (which branch contains the |
| // nested condition) and internal successor index (which branch of the nested condition leads to |
| // the common merge point), or InvalidIndex if no pattern is detected. |
| func detectNestedIfPattern(outerBlock *Block) (int, int) { |
| if !isIfBlock(outerBlock) { |
| // outerBlock doesn't contain comparison |
| return InvalidIndex, InvalidIndex |
| } |
| |
| // Skip empty blocks to find actual conditional targets |
| // Empty plain blocks are often inserted by previous optimizations |
| thenBlock := findFirstNonEmptyPlainBlock(outerBlock, TrueConditionSuccIndex) |
| elseBlock := findFirstNonEmptyPlainBlock(outerBlock, FalseConditionSuccIndex) |
| if thenBlock == elseBlock { |
| // Both branches lead to the same block, cannot transform |
| return InvalidIndex, InvalidIndex |
| } |
| |
| // Check for cyclic patterns where a condition refers back to the original block |
| if thenBlock == outerBlock { |
| // True branch forms a cycle back to outerBlock |
| return detectCyclePattern(outerBlock, FalseConditionSuccIndex) |
| } else if elseBlock == outerBlock { |
| // False branch forms a cycle back to outerBlock |
| return detectCyclePattern(outerBlock, TrueConditionSuccIndex) |
| } |
| |
| outerSuccIndex := InvalidIndex |
| |
| // Check if the true branch contains a nested conditional that can be moved |
| if len(thenBlock.Preds) == 1 && |
| isIfBlock(thenBlock) && |
| canValuesBeMoved(thenBlock) { |
| // True branch contains a valid nested condition |
| outerSuccIndex = TrueConditionSuccIndex |
| } else if len(elseBlock.Preds) == 1 && |
| isIfBlock(elseBlock) && |
| canValuesBeMoved(elseBlock) { |
| // False branch contains a valid nested condition |
| outerSuccIndex = FalseConditionSuccIndex |
| } else { |
| // This chain of blocks is not in pattern. |
| return InvalidIndex, InvalidIndex |
| } |
| |
| // Tree structure: |
| // |
| // outerBlock |
| // / \ |
| // / \ |
| // commonBothBlock innerBlock |
| // / \ |
| // / \ |
| // thenBlock elseBlock |
| // |
| // outerBlock: The outer conditional block being examined |
| // innerBlock: The inner conditional block (nested condition) |
| // commonBothBlock: The block reached when the outer condition is not met |
| // thenBlock/elseBlock: Successors of the inner conditional block |
| innerBlock := findFirstNonEmptyPlainBlock(outerBlock, outerSuccIndex) |
| commonBothBlock := findFirstNonEmptyPlainBlock(outerBlock, outerSuccIndex^1) |
| thenBlock = findFirstNonEmptyPlainBlock(innerBlock, TrueConditionSuccIndex) |
| elseBlock = findFirstNonEmptyPlainBlock(innerBlock, FalseConditionSuccIndex) |
| |
| // Determine which inner branch leads to the common merge point |
| // This identifies the index to NOT common merge point |
| var innerSuccIndex = InvalidIndex |
| switch commonBothBlock { |
| case elseBlock: |
| // Pattern: (if1 && if2) or (!if1 && if2) |
| // False branch of if2 leads to commonBothBlock, |
| // that means True branch leads to b2 (target) |
| innerSuccIndex = TrueConditionSuccIndex |
| case thenBlock: |
| // Pattern: (if1 && !if2) or (!if1 && !if2) |
| // True branch of if2 leads to commonBothBlock, |
| // that means False branch leads to b2 (target) |
| innerSuccIndex = FalseConditionSuccIndex |
| default: |
| // No pattern are matched |
| return InvalidIndex, InvalidIndex |
| } |
| |
| // Critical check: ensure phi nodes in merge blocks have consistent values |
| // This guarantees semantic preservation after transformation |
| outToCommonIndex := outerSuccIndex ^ 1 // index of the outedge from outerBlock to commonBothBlock |
| inToCommonIndex := innerSuccIndex ^ 1 // index of the outedge from innerBlock to commonBothBlock |
| if !checkSameValuesInPhiNodes(outerBlock, innerBlock, outToCommonIndex, inToCommonIndex) { |
| return InvalidIndex, InvalidIndex |
| } |
| |
| return outerSuccIndex, innerSuccIndex |
| } |
| |
| // detectCyclePattern detects cyclic patterns where a conditional block's successor |
| // refers back to the original block. This handles special cases where the control |
| // flow forms a loop-like structure that can still be optimized with conditional comparisons. |
| func detectCyclePattern(outerBlock *Block, outSuccIndex int) (int, int) { |
| secondCondBlock := findFirstNonEmptyPlainBlock(outerBlock, outSuccIndex) |
| |
| if len(secondCondBlock.Preds) != 1 || |
| len(secondCondBlock.Succs) != 2 || |
| !isIfBlock(secondCondBlock) || |
| !canValuesBeMoved(secondCondBlock) { |
| return InvalidIndex, InvalidIndex |
| } |
| |
| thenBlock := findFirstNonEmptyPlainBlock(secondCondBlock, TrueConditionSuccIndex) |
| elseBlock := findFirstNonEmptyPlainBlock(secondCondBlock, FalseConditionSuccIndex) |
| |
| if thenBlock == elseBlock { |
| // Both branches pointing to same block indicates degenerate case |
| return InvalidIndex, InvalidIndex |
| } |
| |
| // Check if the cycle connects back to the original block and verify phi consistency |
| switch outerBlock { |
| case thenBlock: |
| // True branch of inner condition leads back to outerBlock |
| if !checkSameValuesInPhiNodes(outerBlock, thenBlock, outSuccIndex^1, TrueConditionSuccIndex) { |
| return InvalidIndex, InvalidIndex |
| } |
| return outSuccIndex, FalseConditionSuccIndex |
| case elseBlock: |
| // False branch of inner condition leads back to outerBlock |
| if !checkSameValuesInPhiNodes(outerBlock, elseBlock, outSuccIndex^1, FalseConditionSuccIndex) { |
| return InvalidIndex, InvalidIndex |
| } |
| return outSuccIndex, TrueConditionSuccIndex |
| default: |
| // Pattern was not found |
| return InvalidIndex, InvalidIndex |
| } |
| } |
| |
| // checkSameValuesInPhiNodes checks if phi nodes in merge blocks have the same values |
| // for the given successor indices. This ensures that after transformation, phi nodes |
| // will receive the correct values from both paths. Returns true if all phi nodes |
| // have consistent arguments for the specified paths. |
| func checkSameValuesInPhiNodes(outerBlock, innerBlock *Block, outToCommonIndex, inToCommonIndex int) bool { |
| // Skip empty blocks to find actual phi-containing merge blocks |
| // Empty blocks don't affect phi nodes but complicate path tracking |
| for isEmptyPlainBlock(outerBlock.Succs[outToCommonIndex].Block()) { |
| outerBlock = outerBlock.Succs[outToCommonIndex].Block() |
| outToCommonIndex = 0 // After empty block, only one path exists |
| } |
| |
| for isEmptyPlainBlock(innerBlock.Succs[inToCommonIndex].Block()) { |
| innerBlock = innerBlock.Succs[inToCommonIndex].Block() |
| inToCommonIndex = 0 // After empty block, only one path exists |
| } |
| |
| // Both paths must lead to the same merge block for transformation to be valid |
| if outerBlock.Succs[outToCommonIndex].Block() != innerBlock.Succs[inToCommonIndex].Block() { |
| panic("checkSameValuesInPhiNodes: paths do not lead to the same merge block - invalid CFG pattern for if-conversion") |
| } |
| |
| argIndex1 := outerBlock.Succs[outToCommonIndex].Index() |
| argIndex2 := innerBlock.Succs[inToCommonIndex].Index() |
| |
| resultBlock := outerBlock.Succs[outToCommonIndex].Block() |
| for _, v := range resultBlock.Values { |
| if v.Op != OpPhi { |
| continue |
| } |
| |
| // If phi arguments from different paths don't match, |
| // merging the conditions would produce wrong values |
| if v.Args[argIndex1] != v.Args[argIndex2] { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| // canValuesBeMoved checks if all values in a block can be safely moved to another block. |
| // This is necessary because during transformation, values from the inner conditional |
| // block are moved to the outer block. Values with side effects, memory operations, |
| // or phi nodes cannot be moved. |
| func canValuesBeMoved(b *Block) bool { |
| for _, v := range b.Values { |
| if !canValueBeMoved(v) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // canValueBeMoved checks if a single value can be safely moved to another block. |
| // Returns false for values that have side effects, are memory operations, phi nodes, |
| // or nil checks, as moving these could change program semantics. |
| func canValueBeMoved(v *Value) bool { |
| if v.Op == OpPhi { |
| return false |
| } |
| if v.Type.IsMemory() { |
| return false |
| } |
| if v.Op.HasSideEffects() { |
| return false |
| } |
| if opcodeTable[v.Op].nilCheck { |
| return false |
| } |
| if v.MemoryArg() != nil { |
| return false |
| } |
| return true |
| } |
| |
| // isIfBlock checks if a block is a conditional block that can participate in |
| // if-conversion. This includes all ARM64 conditional block kinds (EQ, NE, LT, etc.) |
| // and zero/non-zero test blocks (Z, NZ, ZW, NZW). |
| func isIfBlock(b *Block) bool { |
| switch b.Kind { |
| case BlockARM64EQ, |
| BlockARM64NE, |
| BlockARM64LT, |
| BlockARM64LE, |
| BlockARM64GT, |
| BlockARM64GE, |
| BlockARM64ULT, |
| BlockARM64ULE, |
| BlockARM64UGT, |
| BlockARM64UGE: |
| return isComparisonOperation(b.Controls[0]) |
| case BlockARM64Z, |
| BlockARM64NZ, |
| BlockARM64ZW, |
| BlockARM64NZW: |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // isComparisonOperation checks if a value represents a comparison operation |
| // that can be used in conditional execution. Also ensures the value has only |
| // one use to prevent unexpected side effects from transformation. |
| func isComparisonOperation(value *Value) bool { |
| if value.Uses != 1 { |
| // This value can be transformed to another value. |
| // New value can get another results, not which are expected. |
| // That why we need to check this case. |
| return false |
| } |
| |
| switch value.Op { |
| case OpARM64CMP, |
| OpARM64CMPconst, |
| OpARM64CMN, |
| OpARM64CMNconst, |
| OpARM64CMPW, |
| OpARM64CMPWconst, |
| OpARM64CMNW, |
| OpARM64CMNWconst: |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // transformNestedIfPattern transforms a detected nested if pattern into a |
| // conditional comparison. This is the main transformation function that |
| // coordinates all the steps needed to convert the nested conditionals into |
| // a single conditional comparison instruction. |
| func transformNestedIfPattern(outerBlock *Block, outSuccIndex, inSuccIndex int) { |
| clearPatternFromEmptyPlainBlocks(outerBlock, outSuccIndex) |
| innerBlock := outerBlock.Succs[outSuccIndex].Block() |
| |
| // Transform the control flow step by step: |
| // 1. Transform primary comparison to standard form if needed |
| // 2. Transform dependent comparison to work with conditional execution |
| // 3. Convert to conditional comparison operation (CCMP/CCMN) |
| // 4. Fix comparisons with constants to use constant forms when possible |
| // 5. Set the new control value for the transformed block |
| // 6. Move all values from inner block to outer block |
| // 7. Eliminate the now-redundant nested block |
| transformPrimaryComparisonValue(outerBlock) |
| transformDependentComparisonValue(innerBlock) |
| transformToConditionalComparisonValue(outerBlock, outSuccIndex, inSuccIndex) |
| fixComparisonWithConstant(innerBlock, outSuccIndex) |
| setNewControlValue(outerBlock, innerBlock, outSuccIndex, inSuccIndex) |
| moveAllValues(outerBlock, innerBlock) |
| elimNestedBlock(innerBlock, inSuccIndex) |
| } |
| |
| // clearPatternFromEmptyPlainBlocks removes all empty plain blocks from the |
| // detected pattern to simplify the control flow graph before transformation. |
| func clearPatternFromEmptyPlainBlocks(outerBlock *Block, outSuccIndex int) { |
| innerBlock := removeEmptyPlainBlockChain(outerBlock, outSuccIndex) |
| removeEmptyPlainBlockChain(outerBlock, outSuccIndex^1) |
| |
| removeEmptyPlainBlockChain(innerBlock, TrueConditionSuccIndex) |
| removeEmptyPlainBlockChain(innerBlock, FalseConditionSuccIndex) |
| } |
| |
| // moveAllValues moves all values from the source block to the destination block. |
| // This is used to consolidate the computations from the inner conditional block |
| // into the outer block as part of the if-conversion process. |
| func moveAllValues(dest, src *Block) { |
| for _, value := range src.Values { |
| value.Block = dest |
| dest.Values = append(dest.Values, value) |
| } |
| src.truncateValues(0) |
| } |
| |
| // elimNestedBlock eliminates a nested block that has been incorporated into |
| // the outer block through if-conversion. It removes the specified successor |
| // edge and updates phi nodes in the target block to remove the corresponding argument. |
| func elimNestedBlock(b *Block, index int) { |
| removedEdge := b.Succs[index^1] |
| |
| notBothMetBlock := removedEdge.Block() |
| i := removedEdge.Index() |
| |
| b.removeSucc(index ^ 1) |
| notBothMetBlock.removePred(i) |
| for _, v := range notBothMetBlock.Values { |
| if v.Op != OpPhi { |
| continue |
| } |
| notBothMetBlock.removePhiArg(v, i) |
| } |
| |
| b.Func.invalidateCFG() |
| b.Reset(BlockPlain) |
| b.Likely = BranchUnknown |
| } |
| |
| // setNewControlValue sets the new control value for the transformed block |
| // based on the inner block's control value. It also updates the branch |
| // likelihood based on the original block's branch prediction. |
| func setNewControlValue(outerBlock, innerBlock *Block, outSuccIndex, inSuccIndex int) { |
| outerBlock.resetWithControl(innerBlock.Kind, innerBlock.Controls[0]) |
| if !isBranchLikelyConsistentWithIndex(outerBlock, outSuccIndex) || |
| !isBranchLikelyConsistentWithIndex(innerBlock, inSuccIndex) { |
| outerBlock.Likely = BranchUnknown |
| } |
| } |
| |
| // isBranchLikelyConsistentWithIndex checks if the branch likelihood matches the expected |
| // index. Returns true if the likelihood is consistent with the branch direction. |
| func isBranchLikelyConsistentWithIndex(b *Block, index int) bool { |
| if index == TrueConditionSuccIndex && b.Likely == BranchLikely { |
| return true |
| } else if index == FalseConditionSuccIndex && b.Likely == BranchUnlikely { |
| return true |
| } |
| return false |
| } |
| |
| // transformPrimaryComparisonValue transforms special block kinds (Z, NZ, ZW, NZW) |
| // into standard comparison operations. These block kinds test for zero/non-zero |
| // and need to be converted to explicit comparisons with zero for conditional execution. |
| func transformPrimaryComparisonValue(block *Block) { |
| switch block.Kind { |
| case BlockARM64Z: |
| arg0 := block.Controls[0] |
| controlValue := block.NewValue1I(arg0.Pos, OpARM64CMPconst, types.TypeFlags, 0, arg0) |
| block.resetWithControl(BlockARM64EQ, controlValue) |
| case BlockARM64NZ: |
| arg0 := block.Controls[0] |
| controlValue := block.NewValue1I(arg0.Pos, OpARM64CMPconst, types.TypeFlags, 0, arg0) |
| block.resetWithControl(BlockARM64NE, controlValue) |
| case BlockARM64ZW: |
| arg0 := block.Controls[0] |
| controlValue := block.NewValue1I(arg0.Pos, OpARM64CMPWconst, types.TypeFlags, 0, arg0) |
| block.resetWithControl(BlockARM64EQ, controlValue) |
| case BlockARM64NZW: |
| arg0 := block.Controls[0] |
| controlValue := block.NewValue1I(arg0.Pos, OpARM64CMPWconst, types.TypeFlags, 0, arg0) |
| block.resetWithControl(BlockARM64NE, controlValue) |
| default: |
| return |
| } |
| } |
| |
| // transformDependentComparisonValue transforms the comparison in the dependent |
| // (inner) block to prepare it for conditional execution. This involves converting |
| // constant comparisons to register comparisons and handling special block kinds. |
| func transformDependentComparisonValue(block *Block) { |
| typ := &block.Func.Config.Types |
| |
| switch block.Kind { |
| case BlockARM64EQ, |
| BlockARM64NE, |
| BlockARM64LT, |
| BlockARM64LE, |
| BlockARM64GT, |
| BlockARM64GE, |
| BlockARM64ULT, |
| BlockARM64ULE, |
| BlockARM64UGT, |
| BlockARM64UGE: |
| value := block.Controls[0] |
| |
| switch value.Op { |
| case OpARM64CMPconst: |
| arg0 := value.Args[0] |
| auxConstant := auxIntToInt64(value.AuxInt) |
| value.reset(OpARM64CMP) |
| constantValue := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, auxConstant, true) |
| value.AddArg2(arg0, constantValue) |
| case OpARM64CMNconst: |
| arg0 := value.Args[0] |
| auxConstant := auxIntToInt64(value.AuxInt) |
| value.reset(OpARM64CMN) |
| constantValue := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, auxConstant, true) |
| value.AddArg2(arg0, constantValue) |
| case OpARM64CMPWconst: |
| arg0 := value.Args[0] |
| auxConstant := auxIntToInt32(value.AuxInt) |
| value.reset(OpARM64CMPW) |
| constantValue := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, int64(auxConstant), true) |
| value.AddArg2(arg0, constantValue) |
| case OpARM64CMNWconst: |
| arg0 := value.Args[0] |
| auxConstant := auxIntToInt32(value.AuxInt) |
| value.reset(OpARM64CMNW) |
| constantValue := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, int64(auxConstant), true) |
| value.AddArg2(arg0, constantValue) |
| default: |
| return |
| } |
| case BlockARM64Z: |
| arg0 := block.Controls[0] |
| arg1 := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, 0, true) |
| comparisonValue := block.NewValue2(arg0.Pos, OpARM64CMP, types.TypeFlags, arg0, arg1) |
| block.resetWithControl(BlockARM64EQ, comparisonValue) |
| case BlockARM64NZ: |
| arg0 := block.Controls[0] |
| arg1 := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, 0, true) |
| comparisonValue := block.NewValue2(arg0.Pos, OpARM64CMP, types.TypeFlags, arg0, arg1) |
| block.resetWithControl(BlockARM64NE, comparisonValue) |
| case BlockARM64ZW: |
| arg0 := block.Controls[0] |
| arg1 := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, 0, true) |
| comparisonValue := block.NewValue2(arg0.Pos, OpARM64CMPW, types.TypeFlags, arg0, arg1) |
| block.resetWithControl(BlockARM64EQ, comparisonValue) |
| case BlockARM64NZW: |
| arg0 := block.Controls[0] |
| arg1 := block.Func.constVal(OpARM64MOVDconst, typ.UInt64, 0, true) |
| comparisonValue := block.NewValue2(arg0.Pos, OpARM64CMPW, types.TypeFlags, arg0, arg1) |
| block.resetWithControl(BlockARM64NE, comparisonValue) |
| default: |
| panic("Wrong block kind") |
| } |
| } |
| |
| // fixComparisonWithConstant optimizes conditional comparisons by converting |
| // them to constant forms when one operand is a small constant. This generates |
| // more efficient CCMPconst/CCMNconst instructions. |
| func fixComparisonWithConstant(block *Block, index int) { |
| // Helper function to extract 5-bit immediate from int64 constant (0-31 range) |
| getImm64 := func(auxInt int64) (uint8, bool) { |
| imm := auxIntToInt64(auxInt) |
| if imm&^0x1f == 0 { |
| return uint8(imm), true |
| } |
| return 0, false |
| } |
| |
| // Helper function to extract 5-bit immediate from int32 constant (0-31 range) |
| getImm32 := func(auxInt int64) (uint8, bool) { |
| imm := auxIntToInt32(auxInt) |
| if imm&^0x1f == 0 { |
| return uint8(imm), true |
| } |
| return 0, false |
| } |
| |
| // Helper function to convert conditional comparison to constant form if possible |
| // Algorithm: Check if either operand is a small 5-bit constant (0-31). If found: |
| // 1. Convert operation to constant form (CCMP -> CCMPconst, etc.) |
| // 2. Set the 'ind' flag for immediate mode |
| // 3. When constant is first operand (arg0), swap operands and invert condition |
| tryConvertToConstForm := func(value *Value, newOp Op, getImm func(int64) (uint8, bool)) { |
| params := value.AuxArm64ConditionalParams() |
| arg0 := value.Args[0] |
| arg1 := value.Args[1] |
| arg2 := value.Args[2] |
| // Check second operand for small constant |
| if arg1.Op == OpARM64MOVDconst { |
| if imm, ok := getImm(arg1.AuxInt); ok { |
| value.reset(newOp) |
| params.constValue = imm |
| params.ind = true |
| value.AuxInt = arm64ConditionalParamsToAuxInt(params) |
| value.AddArg2(arg0, arg2) |
| return |
| } |
| } |
| |
| // Check first operand for small constant |
| if arg0.Op == OpARM64MOVDconst { |
| if imm, ok := getImm(arg0.AuxInt); ok { |
| value.reset(newOp) |
| invertConditionsInBlock(block, ¶ms, index) |
| params.constValue = imm |
| params.ind = true |
| value.AuxInt = arm64ConditionalParamsToAuxInt(params) |
| value.AddArg2(arg1, arg2) |
| return |
| } |
| } |
| } |
| |
| // try to convert control value of block to constant form |
| controlValue := block.Controls[0] |
| switch controlValue.Op { |
| case OpARM64CCMP: |
| tryConvertToConstForm(controlValue, OpARM64CCMPconst, getImm64) |
| case OpARM64CCMN: |
| tryConvertToConstForm(controlValue, OpARM64CCMNconst, getImm64) |
| case OpARM64CCMPW: |
| tryConvertToConstForm(controlValue, OpARM64CCMPWconst, getImm32) |
| case OpARM64CCMNW: |
| tryConvertToConstForm(controlValue, OpARM64CCMNWconst, getImm32) |
| default: |
| return |
| } |
| } |
| |
| // invertConditionsInBlock inverts the condition in a block and returns updated |
| // conditional parameters. This is used when swapping operands in constant |
| // optimizations to maintain correct semantics. |
| func invertConditionsInBlock(block *Block, params *arm64ConditionalParams, index int) { |
| invertKind := invertBlockKind(block.Kind) |
| block.Kind = invertKind |
| if index == FalseConditionSuccIndex { |
| invertKind = negateBlockKind(invertKind) |
| } |
| params.nzcv = nzcvByBlockKind(invertKind) |
| } |
| |
| // transformToConditionalComparisonValue transforms the comparison operations |
| // to conditional comparison operations (CCMP/CCMN). This is the core transformation |
| // that creates the conditional execution pattern by combining the outer and inner |
| // conditions into a single conditional comparison instruction. |
| func transformToConditionalComparisonValue(outerBlock *Block, outSuccIndex, inSuccIndex int) { |
| innerBlock := outerBlock.Succs[outSuccIndex].Block() |
| |
| // Adjust block kinds and successors if needed to match expected pattern |
| if outSuccIndex != inSuccIndex { |
| outerBlock.Kind = negateBlockKind(outerBlock.Kind) |
| outerBlock.swapSuccessors() |
| outSuccIndex ^= 1 |
| } |
| |
| outerControl := outerBlock.Controls[0] |
| outerKind := outerBlock.Kind |
| |
| innerControl := innerBlock.Controls[0] |
| innerKind := innerBlock.Kind |
| |
| // Adjust conditions based on successor index |
| if outSuccIndex == FalseConditionSuccIndex { |
| outerKind = negateBlockKind(outerKind) |
| innerKind = negateBlockKind(innerKind) |
| } |
| |
| // Get conditional parameters and transform the operation |
| params := createConditionalParamsByBlockKind(outerKind, innerKind) |
| |
| innerControl.AddArg(outerControl) |
| innerControl.Op = transformOpToConditionalComparisonOperation(innerControl.Op) |
| innerControl.AuxInt = arm64ConditionalParamsToAuxInt(params) |
| } |
| |
| // transformOpToConditionalComparisonOperation maps standard comparison operations |
| // to their conditional comparison counterparts (e.g., CMP -> CCMP, CMN -> CCMN). |
| func transformOpToConditionalComparisonOperation(op Op) Op { |
| switch op { |
| case OpARM64CMP: |
| return OpARM64CCMP |
| case OpARM64CMN: |
| return OpARM64CCMN |
| case OpARM64CMPconst: |
| return OpARM64CCMPconst |
| case OpARM64CMNconst: |
| return OpARM64CCMNconst |
| case OpARM64CMPW: |
| return OpARM64CCMPW |
| case OpARM64CMNW: |
| return OpARM64CCMNW |
| case OpARM64CMPWconst: |
| return OpARM64CCMPWconst |
| case OpARM64CMNWconst: |
| return OpARM64CCMNWconst |
| default: |
| panic("Incorrect operation") |
| } |
| } |
| |
| // createConditionalParamsByBlockKind constructs conditional parameters for ARM64 conditional instructions. |
| // It combines two block kinds: |
| // - outerKind specifies the main condition (e.g., LT, GT) to be evaluated. |
| // - innerKind determines the NZCV flag pattern to be used when the main condition is FALSE. |
| // The resulting parameters are typically used by conditional comparison operations (CCMP, CCMN). |
| func createConditionalParamsByBlockKind(outerKind, innerKind BlockKind) arm64ConditionalParams { |
| cond := condByBlockKind(outerKind) // the condition code for the primary comparison |
| nzcv := nzcvByBlockKind(innerKind) // NZCV flags to apply when the condition is false |
| return arm64ConditionalParamsAuxInt(cond, nzcv) |
| } |
| |
| // condByBlockKind maps block kinds to their corresponding condition codes |
| // for ARM64 conditional execution. |
| func condByBlockKind(kind BlockKind) Op { |
| switch kind { |
| case BlockARM64EQ: |
| return OpARM64Equal |
| case BlockARM64NE: |
| return OpARM64NotEqual |
| case BlockARM64LT: |
| return OpARM64LessThan |
| case BlockARM64LE: |
| return OpARM64LessEqual |
| case BlockARM64GT: |
| return OpARM64GreaterThan |
| case BlockARM64GE: |
| return OpARM64GreaterEqual |
| case BlockARM64ULT: |
| return OpARM64LessThanU |
| case BlockARM64ULE: |
| return OpARM64LessEqualU |
| case BlockARM64UGT: |
| return OpARM64GreaterThanU |
| case BlockARM64UGE: |
| return OpARM64GreaterEqualU |
| default: |
| panic("Incorrect kind of Block") |
| } |
| } |
| |
| // nzcvByBlockKind returns NZCV flags encoding the *logical opposite* of the specified block condition. |
| // The returned flags represent the processor state to be used when the primary comparison condition |
| // evaluates to false. |
| // |
| // Each case constructs the flag pattern for the inverse condition: |
| // |
| // EQ -> NE, LT -> GE, GT -> LE, etc. |
| func nzcvByBlockKind(kind BlockKind) uint8 { |
| switch kind { |
| case BlockARM64EQ: |
| // Encode NE : Z == 0 |
| return packNZCV(false, false, false, false) // N=0,Z=0,C=0,V=0 |
| case BlockARM64NE: |
| // Encode EQ : Z == 1 |
| return packNZCV(false, true, false, false) // N=0,Z=1,C=0,V=0 |
| case BlockARM64LT: |
| // Encode GE : N == V |
| return packNZCV(false, false, false, false) // N=0,Z=0,C=0,V=0 |
| case BlockARM64LE: |
| // Encode GT : (Z == 0) && (N == V) |
| return packNZCV(false, false, false, false) // N=0,Z=0,C=0,V=0 |
| case BlockARM64GT: |
| // Encode LE : (Z == 1) || (N != V) |
| return packNZCV(false, true, false, false) // N=0,Z=1,C=0,V=0 |
| case BlockARM64GE: |
| // Encode LT : N != V |
| return packNZCV(false, false, false, true) // N=0,Z=0,C=0,V=1 |
| case BlockARM64ULT: |
| // Encode UGE : C == 1 |
| return packNZCV(false, false, true, false) // N=0,Z=0,C=1,V=0 |
| case BlockARM64ULE: |
| // Encode UGT : (C == 1) && (Z == 0) |
| return packNZCV(false, false, true, false) // N=0,Z=0,C=1,V=0 |
| case BlockARM64UGT: |
| // Encode ULE : (C == 0) || (Z == 1) |
| return packNZCV(false, false, false, false) // N=0,Z=0,C=0,V=0 |
| case BlockARM64UGE: |
| // Encode ULT : C == 0 |
| return packNZCV(false, false, false, false) // N=0,Z=0,C=0,V=0 |
| default: |
| panic("Incorrect kind of Block") |
| } |
| } |
| |
| // packNZCV packs boolean condition flags into a single byte representing |
| // the ARM64 NZCV condition flags: Negative, Zero, Carry, Overflow. |
| func packNZCV(N, Z, C, V bool) uint8 { |
| var NZCVFlags uint8 = 0 |
| if N { |
| NZCVFlags |= 1 << 3 |
| } |
| if Z { |
| NZCVFlags |= 1 << 2 |
| } |
| if C { |
| NZCVFlags |= 1 << 1 |
| } |
| if V { |
| NZCVFlags |= 1 |
| } |
| return NZCVFlags |
| } |
| |
| // negateBlockKind returns the logical negation of a block kind |
| // (e.g., EQ becomes NE, LT becomes GE). |
| func negateBlockKind(kind BlockKind) BlockKind { |
| switch kind { |
| case BlockARM64EQ: |
| return BlockARM64NE |
| case BlockARM64NE: |
| return BlockARM64EQ |
| case BlockARM64LT: |
| return BlockARM64GE |
| case BlockARM64LE: |
| return BlockARM64GT |
| case BlockARM64GT: |
| return BlockARM64LE |
| case BlockARM64GE: |
| return BlockARM64LT |
| case BlockARM64ULT: |
| return BlockARM64UGE |
| case BlockARM64ULE: |
| return BlockARM64UGT |
| case BlockARM64UGT: |
| return BlockARM64ULE |
| case BlockARM64UGE: |
| return BlockARM64ULT |
| default: |
| panic("Incorrect kind of Block") |
| } |
| } |
| |
| // invertBlockKind inverts the operands of a comparison block kind |
| // (e.g., LT becomes GT, LE becomes GE). |
| func invertBlockKind(kind BlockKind) BlockKind { |
| switch kind { |
| case BlockARM64EQ: |
| return BlockARM64EQ |
| case BlockARM64NE: |
| return BlockARM64NE |
| case BlockARM64LT: |
| return BlockARM64GT |
| case BlockARM64LE: |
| return BlockARM64GE |
| case BlockARM64GT: |
| return BlockARM64LT |
| case BlockARM64GE: |
| return BlockARM64LE |
| case BlockARM64ULT: |
| return BlockARM64UGT |
| case BlockARM64ULE: |
| return BlockARM64UGE |
| case BlockARM64UGT: |
| return BlockARM64ULT |
| case BlockARM64UGE: |
| return BlockARM64ULE |
| default: |
| panic("Incorrect kind of Block") |
| } |
| } |