1// Copyright 2024 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
5package liveness
6
7// This file defines an "Intervals" helper type that stores a
8// sorted sequence of disjoint ranges or intervals. An Intervals
9// example: { [0,5) [9-12) [100,101) }, which corresponds to the
10// numbers 0-4, 9-11, and 100. Once an Intervals object is created, it
11// can be tested to see if it has any overlap with another Intervals
12// object, or it can be merged with another Intervals object to form a
13// union of the two.
14//
15// The intended use case for this helper is in describing object or
16// variable lifetime ranges within a linearized program representation
17// where each IR instruction has a slot or index. Example:
18//
19//          b1:
20//  0        VarDef abc
21//  1        memset(abc,0)
22//  2        VarDef xyz
23//  3        memset(xyz,0)
24//  4        abc.f1 = 2
25//  5        xyz.f3 = 9
26//  6        if q goto B4
27//  7 B3:    z = xyz.x
28//  8        goto B5
29//  9 B4:    z = abc.x
30//           // fallthrough
31// 10 B5:    z++
32//
33// To describe the lifetime of the variables above we might use these
34// intervals:
35//
36//    "abc"   [1,7), [9,10)
37//    "xyz"   [3,8)
38//
39// Clients can construct an Intervals object from a given IR sequence
40// using the "IntervalsBuilder" helper abstraction (one builder per
41// candidate variable), by making a
42// backwards sweep and invoking the Live/Kill methods to note the
43// starts and end of a given lifetime. For the example above, we would
44// expect to see this sequence of calls to Live/Kill:
45//
46//    abc:  Live(9), Kill(8), Live(6), Kill(0)
47//    xyz:  Live(8), Kill(2)
48
49import (
50	"fmt"
51	"os"
52	"strings"
53)
54
55const debugtrace = false
56
57// Interval hols the range [st,en).
58type Interval struct {
59	st, en int
60}
61
62// Intervals is a sequence of sorted, disjoint intervals.
63type Intervals []Interval
64
65func (i Interval) String() string {
66	return fmt.Sprintf("[%d,%d)", i.st, i.en)
67}
68
69// TEMPORARY until bootstrap version catches up.
70func imin(i, j int) int {
71	if i < j {
72		return i
73	}
74	return j
75}
76
77// TEMPORARY until bootstrap version catches up.
78func imax(i, j int) int {
79	if i > j {
80		return i
81	}
82	return j
83}
84
85// Overlaps returns true if here is any overlap between i and i2.
86func (i Interval) Overlaps(i2 Interval) bool {
87	return (imin(i.en, i2.en) - imax(i.st, i2.st)) > 0
88}
89
90// adjacent returns true if the start of one interval is equal to the
91// end of another interval (e.g. they represent consecutive ranges).
92func (i1 Interval) adjacent(i2 Interval) bool {
93	return i1.en == i2.st || i2.en == i1.st
94}
95
96// MergeInto merges interval i2 into i1. This version happens to
97// require that the two intervals either overlap or are adjacent.
98func (i1 *Interval) MergeInto(i2 Interval) error {
99	if !i1.Overlaps(i2) && !i1.adjacent(i2) {
100		return fmt.Errorf("merge method invoked on non-overlapping/non-adjacent")
101	}
102	i1.st = imin(i1.st, i2.st)
103	i1.en = imax(i1.en, i2.en)
104	return nil
105}
106
107// IntervalsBuilder is a helper for constructing intervals based on
108// live dataflow sets for a series of BBs where we're making a
109// backwards pass over each BB looking for uses and kills. The
110// expected use case is:
111//
112//   - invoke MakeIntervalsBuilder to create a new object "b"
113//   - series of calls to b.Live/b.Kill based on a backwards reverse layout
114//     order scan over instructions
115//   - invoke b.Finish() to produce final set
116//
117// See the Live method comment for an IR example.
118type IntervalsBuilder struct {
119	s Intervals
120	// index of last instruction visited plus 1
121	lidx int
122}
123
124func (c *IntervalsBuilder) last() int {
125	return c.lidx - 1
126}
127
128func (c *IntervalsBuilder) setLast(x int) {
129	c.lidx = x + 1
130}
131
132func (c *IntervalsBuilder) Finish() (Intervals, error) {
133	// Reverse intervals list and check.
134	// FIXME: replace with slices.Reverse once the
135	// bootstrap version supports it.
136	for i, j := 0, len(c.s)-1; i < j; i, j = i+1, j-1 {
137		c.s[i], c.s[j] = c.s[j], c.s[i]
138	}
139	if err := check(c.s); err != nil {
140		return Intervals{}, err
141	}
142	r := c.s
143	return r, nil
144}
145
146// Live method should be invoked on instruction at position p if instr
147// contains an upwards-exposed use of a resource. See the example in
148// the comment at the beginning of this file for an example.
149func (c *IntervalsBuilder) Live(pos int) error {
150	if pos < 0 {
151		return fmt.Errorf("bad pos, negative")
152	}
153	if c.last() == -1 {
154		c.setLast(pos)
155		if debugtrace {
156			fmt.Fprintf(os.Stderr, "=-= begin lifetime at pos=%d\n", pos)
157		}
158		c.s = append(c.s, Interval{st: pos, en: pos + 1})
159		return nil
160	}
161	if pos >= c.last() {
162		return fmt.Errorf("pos not decreasing")
163	}
164	// extend lifetime across this pos
165	c.s[len(c.s)-1].st = pos
166	c.setLast(pos)
167	return nil
168}
169
170// Kill method should be invoked on instruction at position p if instr
171// should be treated as as having a kill (lifetime end) for the
172// resource. See the example in the comment at the beginning of this
173// file for an example. Note that if we see a kill at position K for a
174// resource currently live since J, this will result in a lifetime
175// segment of [K+1,J+1), the assumption being that the first live
176// instruction will be the one after the kill position, not the kill
177// position itself.
178func (c *IntervalsBuilder) Kill(pos int) error {
179	if pos < 0 {
180		return fmt.Errorf("bad pos, negative")
181	}
182	if c.last() == -1 {
183		return nil
184	}
185	if pos >= c.last() {
186		return fmt.Errorf("pos not decreasing")
187	}
188	c.s[len(c.s)-1].st = pos + 1
189	// terminate lifetime
190	c.setLast(-1)
191	if debugtrace {
192		fmt.Fprintf(os.Stderr, "=-= term lifetime at pos=%d\n", pos)
193	}
194	return nil
195}
196
197// check examines the intervals in "is" to try to find internal
198// inconsistencies or problems.
199func check(is Intervals) error {
200	for i := 0; i < len(is); i++ {
201		st := is[i].st
202		en := is[i].en
203		if en <= st {
204			return fmt.Errorf("bad range elem %d:%d, en<=st", st, en)
205		}
206		if i == 0 {
207			continue
208		}
209		// check for badly ordered starts
210		pst := is[i-1].st
211		pen := is[i-1].en
212		if pst >= st {
213			return fmt.Errorf("range start not ordered %d:%d less than prev %d:%d", st, en,
214				pst, pen)
215		}
216		// check end of last range against start of this range
217		if pen > st {
218			return fmt.Errorf("bad range elem %d:%d overlaps prev %d:%d", st, en,
219				pst, pen)
220		}
221	}
222	return nil
223}
224
225func (is *Intervals) String() string {
226	var sb strings.Builder
227	for i := range *is {
228		if i != 0 {
229			sb.WriteString(" ")
230		}
231		sb.WriteString((*is)[i].String())
232	}
233	return sb.String()
234}
235
236// intWithIdx holds an interval i and an index pairIndex storing i's
237// position (either 0 or 1) within some previously specified interval
238// pair <I1,I2>; a pairIndex of -1 is used to signal "end of
239// iteration". Used for Intervals operations, not expected to be
240// exported.
241type intWithIdx struct {
242	i         Interval
243	pairIndex int
244}
245
246func (iwi intWithIdx) done() bool {
247	return iwi.pairIndex == -1
248}
249
250// pairVisitor provides a way to visit (iterate through) each interval
251// within a pair of Intervals in order of increasing start time. Expected
252// usage model:
253//
254//	func example(i1, i2 Intervals) {
255//	  var pairVisitor pv
256//	  cur := pv.init(i1, i2);
257//	  for !cur.done() {
258//	     fmt.Printf("interval %s from i%d", cur.i.String(), cur.pairIndex+1)
259//	     cur = pv.nxt()
260//	  }
261//	}
262//
263// Used internally for Intervals operations, not expected to be exported.
264type pairVisitor struct {
265	cur    intWithIdx
266	i1pos  int
267	i2pos  int
268	i1, i2 Intervals
269}
270
271// init initializes a pairVisitor for the specified pair of intervals
272// i1 and i2 and returns an intWithIdx object that points to the first
273// interval by start position within i1/i2.
274func (pv *pairVisitor) init(i1, i2 Intervals) intWithIdx {
275	pv.i1, pv.i2 = i1, i2
276	pv.cur = pv.sel()
277	return pv.cur
278}
279
280// nxt advances the pairVisitor to the next interval by starting
281// position within the pair, returning an intWithIdx that describes
282// the interval.
283func (pv *pairVisitor) nxt() intWithIdx {
284	if pv.cur.pairIndex == 0 {
285		pv.i1pos++
286	} else {
287		pv.i2pos++
288	}
289	pv.cur = pv.sel()
290	return pv.cur
291}
292
293// sel is a helper function used by 'init' and 'nxt' above; it selects
294// the earlier of the two intervals at the current positions within i1
295// and i2, or a degenerate (pairIndex -1) intWithIdx if we have no
296// more intervals to visit.
297func (pv *pairVisitor) sel() intWithIdx {
298	var c1, c2 intWithIdx
299	if pv.i1pos >= len(pv.i1) {
300		c1.pairIndex = -1
301	} else {
302		c1 = intWithIdx{i: pv.i1[pv.i1pos], pairIndex: 0}
303	}
304	if pv.i2pos >= len(pv.i2) {
305		c2.pairIndex = -1
306	} else {
307		c2 = intWithIdx{i: pv.i2[pv.i2pos], pairIndex: 1}
308	}
309	if c1.pairIndex == -1 {
310		return c2
311	}
312	if c2.pairIndex == -1 {
313		return c1
314	}
315	if c1.i.st <= c2.i.st {
316		return c1
317	}
318	return c2
319}
320
321// Overlaps returns whether any of the component ranges in is overlaps
322// with some range in is2.
323func (is Intervals) Overlaps(is2 Intervals) bool {
324	// check for empty intervals
325	if len(is) == 0 || len(is2) == 0 {
326		return false
327	}
328	li := len(is)
329	li2 := len(is2)
330	// check for completely disjoint ranges
331	if is[li-1].en <= is2[0].st ||
332		is[0].st >= is2[li2-1].en {
333		return false
334	}
335	// walk the combined sets of intervals and check for piecewise
336	// overlap.
337	var pv pairVisitor
338	first := pv.init(is, is2)
339	for {
340		second := pv.nxt()
341		if second.done() {
342			break
343		}
344		if first.pairIndex == second.pairIndex {
345			first = second
346			continue
347		}
348		if first.i.Overlaps(second.i) {
349			return true
350		}
351		first = second
352	}
353	return false
354}
355
356// Merge combines the intervals from "is" and "is2" and returns
357// a new Intervals object containing all combined ranges from the
358// two inputs.
359func (is Intervals) Merge(is2 Intervals) Intervals {
360	if len(is) == 0 {
361		return is2
362	} else if len(is2) == 0 {
363		return is
364	}
365	// walk the combined set of intervals and merge them together.
366	var ret Intervals
367	var pv pairVisitor
368	cur := pv.init(is, is2)
369	for {
370		second := pv.nxt()
371		if second.done() {
372			break
373		}
374
375		// Check for overlap between cur and second. If no overlap
376		// then add cur to result and move on.
377		if !cur.i.Overlaps(second.i) && !cur.i.adjacent(second.i) {
378			ret = append(ret, cur.i)
379			cur = second
380			continue
381		}
382		// cur overlaps with second; merge second into cur
383		cur.i.MergeInto(second.i)
384	}
385	ret = append(ret, cur.i)
386	return ret
387}
388