Source file src/cmd/compile/internal/liveness/intervals.go

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

View as plain text