| // 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 ssacompile | |
| import ( | |
| "fmt" | |
| "cmd/compile/internal/ssa" | |
| "cmd/compile/internal/ssa/ssaop" | |
| ) | |
| // 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 *ssa.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 !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 ssaop.OpLess8, ssaop.OpLess16, ssaop.OpLess32, ssaop.OpLess64, ssaop.OpLeq8, ssaop.OpLeq16, ssaop.OpLeq32, ssaop.OpLeq64: | |
| if _, ok := ssa.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 ssaop.OpLess8U, ssaop.OpLess16U, ssaop.OpLess32U, ssaop.OpLess64U, ssaop.OpLeq8U, ssaop.OpLeq16U, ssaop.OpLeq32U, ssaop.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 ssaop.OpEq8, ssaop.OpEq16, ssaop.OpEq32, ssaop.OpEq64, ssaop.OpNeq8, ssaop.OpNeq16, ssaop.OpNeq32, ssaop.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 ssaop.OpAdd8: | |
| nxt.Op = ssaop.OpSub8 | |
| case ssaop.OpAdd16: | |
| nxt.Op = ssaop.OpSub16 | |
| case ssaop.OpAdd32: | |
| nxt.Op = ssaop.OpSub32 | |
| case ssaop.OpAdd64: | |
| nxt.Op = ssaop.OpSub64 | |
| case ssaop.OpSub8: | |
| nxt.Op = ssaop.OpAdd8 | |
| case ssaop.OpSub16: | |
| nxt.Op = ssaop.OpAdd16 | |
| case ssaop.OpSub32: | |
| nxt.Op = ssaop.OpAdd32 | |
| case ssaop.OpSub64: | |
| nxt.Op = ssaop.OpAdd64 | |
| default: | |
| panic("unreachable") | |
| } | |
| if f.Pass.Debug > 0 { | |
| f.Warnl(ind.Pos, "Inverted loop iteration") | |
| } | |
| } |