// Copyright 2016 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

// We are looking for loops with following structure
// (loop bodies may have control flow inside):
//
//              +--------------+
//              |              |
//              |  preheader   |
//              |              |
//              +-------+------+
//                      |
//                      |
//              +-------v------+
//              |              |
//       +------>    header    |
//       |      |              |
//       |      +-------+------+
//       |              |
//       |              |
//       |      +-------v------+
//       |      |              |
//       +------+  loop body   |
//              |              |
//              +--------------+
//
//
// We consider all phis and memory operations as initial loop dependent set.
// So loop independent values are all loop values,
// minus transitive closure of initial loop dependent values.
// We remove those values from their BBs and move them to preheader.

func licm(f *Func) {
	// See likelyadjust.go for details about loop info.
	nest := loopnestfor(f)
	if len(nest.loops) == 0 || nest.hasIrreducible {
		return
	}

	uses := uses(f)
	defer uses.free(f)

	loopDependent := f.Cache.allocBoolSlice(f.NumValues())
	defer f.Cache.freeBoolSlice(loopDependent)
	queue := f.Cache.allocValueSlice(f.NumValues())
	defer f.Cache.freeValueSlice(queue)
	queue = queue[:0]

	// Start with all values we can't move out of loops.
	for _, b := range f.Blocks {
		if loop := nest.b2l[b.ID]; loop == nil || !loop.isInner {
			// Values outside any loop we don't care about.
			// Values not in a leaf loop we can't handle.
			continue
		}
		for _, v := range b.Values {
			if opcodeTable[v.Op].earlyOk {
				// Double check we didn't mark the wrong ops as earlyOk
				if v.Type.IsMemory() || opcodeTable[v.Op].nilCheck || opcodeTable[v.Op].hasSideEffects || v.MemoryArg() != nil {
					v.Fatalf("op %s has bad earlyOk mark", v.Op)
				}
				if !v.Type.IsPtr() {
					// Note: can't move pointer arithmetic, as it may be guarded by conditionals
					// and thus could materialize a bad pointer across a safepoint.

					continue // Ok to lift out of loop.
				}
			}
			if v.Op == OpSelect0 || v.Op == OpSelect1 {
				// These ops can (and must) move with the op they are selecting from.
				continue
			}
			loopDependent[v.ID] = true
			queue = append(queue, v)
		}
	}

	// If a value can't be moved out of a loop, neither can its users.
	// The queue contains values which are loop dependent, but their users
	// have not been marked as loop dependent yet.
	for len(queue) > 0 {
		v := queue[len(queue)-1]
		queue = queue[:len(queue)-1]

		for _, u := range uses.get(v) {
			if loop := nest.b2l[u.Block.ID]; loop == nil || !loop.isInner {
				continue // see above
			}
			if loopDependent[u.ID] {
				continue
			}
			loopDependent[u.ID] = true
			queue = append(queue, u)
		}
	}

	// Anything not marked as loop-dependent can be moved out of its loop.
	for _, b := range f.Blocks {
		loop := nest.b2l[b.ID]
		if loop == nil || !loop.isInner {
			// loopDependent check is wrong for loops containing other loops,
			// because then a value might have an argument computed inside
			// a nested loop.
			continue
		}
		if len(loop.header.Preds) != 2 {
			continue // is never true?
		}
		anyMoved := false
		for i, v := range b.Values {
			if loopDependent[v.ID] {
				continue
			}
			// Figure out where to move loop-independent values.
			h := loop.header
			var inIdx int
			if int(h.Preds[0].b.ID) >= len(nest.b2l) || nest.b2l[h.Preds[0].b.ID] != loop {
				inIdx = 0
			} else {
				inIdx = 1
			}
			dest := h.Preds[inIdx].b
			if dest.Kind != BlockPlain {
				outIdx := h.Preds[inIdx].i
				// Introduce a new block between the loop
				// header predecessor and the loop header itself.
				mid := f.NewBlock(BlockPlain)
				mid.Pos = dest.Pos
				// Splice into graph.
				mid.Preds = append(mid.Preds, Edge{dest, outIdx})
				mid.Succs = append(mid.Succs, Edge{h, inIdx})
				h.Preds[inIdx] = Edge{mid, 0}
				dest.Succs[outIdx] = Edge{mid, 0}

				dest = mid
			}

			b.Values[i] = nil
			v.Block = dest
			dest.Values = append(dest.Values, v)
			anyMoved = true
		}
		if anyMoved {
			// We just nil'd entries in b.Values above. Compact out the nils.
			i := 0
			for _, v := range b.Values {
				if v == nil {
					continue
				}
				b.Values[i] = v
				i++
			}
			b.Values = b.Values[:i]
		}
	}
}
