| // Copyright 2026 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 "fmt" |
| |
| // maybeRewriteLoopToDownwardCountingLoop tries to rewrite the loop to a |
| // downward counting loop checking against start if the loop body does |
| // not depend on ind or nxt and end is known before the loop. |
| // That means this code: |
| // |
| // loop: |
| // ind = (Phi (Const [x]) nxt), |
| // if ind < end |
| // then goto enter_loop |
| // else goto exit_loop |
| // |
| // enter_loop: |
| // do something without using ind nor nxt |
| // nxt = inc + ind |
| // goto loop |
| // |
| // exit_loop: |
| // |
| // is rewritten to: |
| // |
| // loop: |
| // ind = (Phi end nxt) |
| // if (Const [x]) < ind |
| // then goto enter_loop |
| // else goto exit_loop |
| // |
| // enter_loop: |
| // do something without using ind nor nxt |
| // nxt = ind - inc |
| // goto loop |
| // |
| // exit_loop: |
| // |
| // This is better because it only requires to keep ind then nxt alive while looping, |
| // while the original form keeps ind then nxt and end alive. |
| // |
| // If the loop could not be rewritten, it is left unchanged. |
| func maybeRewriteLoopToDownwardCountingLoop(f *Func, v indVar) { |
| ind := v.ind |
| nxt := v.nxt |
| if !(ind.Uses == 2 && // 2 used by comparison and next |
| nxt.Uses == 1) { // 1 used by induction |
| return |
| } |
| |
| start, end := v.min, v.max |
| if v.step < 0 { |
| start, end = end, start |
| } |
| |
| if !start.isGenericIntConst() { |
| // if start is not a constant we would be winning nothing from inverting the loop |
| return |
| } |
| if end.isGenericIntConst() { |
| // TODO: if both start and end are constants we should rewrite such that the comparison |
| // is against zero and nxt is ++ or -- operation |
| // That means: |
| // for i := 2; i < 11; i += 2 { |
| // should be rewritten to: |
| // for i := 5; 0 < i; i-- { |
| return |
| } |
| |
| if end.Block == ind.Block { |
| // we can't rewrite loops where the condition depends on the loop body |
| // this simple check is forced to work because if this is true a Phi in ind.Block must exist |
| return |
| } |
| |
| check := v.entry.Preds[0].b.Controls[0] |
| |
| neededRoom := -v.step |
| |
| // The whole range of safe numbers to land in to stop the loop is shifted by one if the bounds are exclusive. |
| if neededRoom < 0 && v.flags&indVarMinExc == 1 { |
| neededRoom++ // safe because it is always against the number's sign |
| } |
| if neededRoom > 0 && v.flags&indVarMaxInc == 0 { |
| neededRoom-- // safe because it is always against the number's sign |
| } |
| |
| switch check.Op { |
| case OpLess8, OpLess16, OpLess32, OpLess64, OpLeq8, OpLeq16, OpLeq32, OpLeq64: |
| if _, ok := safeAdd(start.AuxInt, neededRoom, uint(start.Type.Size())*8); !ok { |
| // We lack sufficient room after start to safely land without an overflow. |
| // See go.dev/issue/78303 |
| return |
| } |
| case OpLess8U, OpLess16U, OpLess32U, OpLess64U, OpLeq8U, OpLeq16U, OpLeq32U, OpLeq64U: |
| panic(`parseIndVar didn't yet support unsigned induction variables, this code doesn't yet support them either. |
| If you are seeing this it is probably because you've fixed https://go.dev/issue/65918. |
| You need to update this code and add tests then.`) |
| case OpEq8, OpEq16, OpEq32, OpEq64, OpNeq8, OpNeq16, OpNeq32, OpNeq64: |
| panic(`parseIndVar didn't yet support induction variables using == or !=. |
| If you are seeing this it is probably because you've added support for them. |
| You need to update this code and add tests then.`) |
| default: |
| panic(fmt.Sprintf("unreachable; unexpected induction variable comparator %v %v", check, check.Op)) |
| } |
| |
| idxEnd, idxStart := -1, -1 |
| for i, v := range check.Args { |
| if v == end { |
| idxEnd = i |
| break |
| } |
| } |
| for i, v := range ind.Args { |
| if v == start { |
| idxStart = i |
| break |
| } |
| } |
| if idxEnd < 0 || idxStart < 0 { |
| return |
| } |
| |
| sdom := f.Sdom() |
| // the end may not dominate the ind after rewrite, check it first |
| if !sdom.IsAncestorEq(end.Block, ind.Block) { |
| return |
| } |
| |
| // swap start and end in the loop |
| check.SetArg(idxEnd, start) |
| ind.SetArg(idxStart, end) |
| |
| // invert the check |
| check.Args[0], check.Args[1] = check.Args[1], check.Args[0] |
| |
| if nxt.Args[0] != ind { |
| // unlike additions subtractions are not commutative so be sure we get it right |
| nxt.Args[0], nxt.Args[1] = nxt.Args[1], nxt.Args[0] |
| } |
| |
| switch nxt.Op { |
| case OpAdd8: |
| nxt.Op = OpSub8 |
| case OpAdd16: |
| nxt.Op = OpSub16 |
| case OpAdd32: |
| nxt.Op = OpSub32 |
| case OpAdd64: |
| nxt.Op = OpSub64 |
| case OpSub8: |
| nxt.Op = OpAdd8 |
| case OpSub16: |
| nxt.Op = OpAdd16 |
| case OpSub32: |
| nxt.Op = OpAdd32 |
| case OpSub64: |
| nxt.Op = OpAdd64 |
| default: |
| panic("unreachable") |
| } |
| |
| if f.pass.debug > 0 { |
| f.Warnl(ind.Pos, "Inverted loop iteration") |
| } |
| } |