• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package depset
16
17import (
18	"fmt"
19	"iter"
20	"slices"
21	"unique"
22
23	"github.com/google/blueprint/gobtools"
24	"github.com/google/blueprint/uniquelist"
25)
26
27// DepSet is designed to be conceptually compatible with Bazel's depsets:
28// https://docs.bazel.build/versions/master/skylark/depsets.html
29
30type Order int
31
32const (
33	PREORDER Order = iota
34	POSTORDER
35	TOPOLOGICAL
36)
37
38func (o Order) String() string {
39	switch o {
40	case PREORDER:
41		return "PREORDER"
42	case POSTORDER:
43		return "POSTORDER"
44	case TOPOLOGICAL:
45		return "TOPOLOGICAL"
46	default:
47		panic(fmt.Errorf("Invalid Order %d", o))
48	}
49}
50
51type depSettableType comparable
52
53// A DepSet efficiently stores a slice of an arbitrary type from transitive dependencies without
54// copying. It is stored as a DAG of DepSet nodes, each of which has some direct contents and a list
55// of dependency DepSet nodes.
56//
57// A DepSet has an order that will be used to walk the DAG when ToList() is called.  The order
58// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
59// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
60// elements of children are listed after all of their parents (unless there are duplicate direct
61// elements in the DepSet or any of its transitive dependencies, in which case the ordering of the
62// duplicated element is not guaranteed).
63//
64// A DepSet is created by New or NewBuilder.Build from the slice for direct contents
65// and the DepSets of dependencies. A DepSet is immutable once created.
66//
67// DepSets are stored using UniqueList which uses the unique package to intern them, which ensures
68// that the graph semantics of the DepSet are maintained even after serializing/deserializing or
69// when mixing newly created and deserialized DepSets.
70type DepSet[T depSettableType] struct {
71	// handle is a unique.Handle to an internal depSet object, which makes DepSets effectively a
72	// single pointer.
73	handle unique.Handle[depSet[T]]
74}
75
76type depSet[T depSettableType] struct {
77	preorder   bool
78	reverse    bool
79	order      Order
80	direct     uniquelist.UniqueList[T]
81	transitive uniquelist.UniqueList[DepSet[T]]
82}
83
84// impl returns a copy of the uniquified  depSet for a DepSet.
85func (d DepSet[T]) impl() depSet[T] {
86	return d.handle.Value()
87}
88
89func (d DepSet[T]) order() Order {
90	impl := d.impl()
91	return impl.order
92}
93
94type depSetGob[T depSettableType] struct {
95	Preorder   bool
96	Reverse    bool
97	Order      Order
98	Direct     []T
99	Transitive []DepSet[T]
100}
101
102func (d *DepSet[T]) ToGob() *depSetGob[T] {
103	impl := d.impl()
104	return &depSetGob[T]{
105		Preorder:   impl.preorder,
106		Reverse:    impl.reverse,
107		Order:      impl.order,
108		Direct:     impl.direct.ToSlice(),
109		Transitive: impl.transitive.ToSlice(),
110	}
111}
112
113func (d *DepSet[T]) FromGob(data *depSetGob[T]) {
114	d.handle = unique.Make(depSet[T]{
115		preorder:   data.Preorder,
116		reverse:    data.Reverse,
117		order:      data.Order,
118		direct:     uniquelist.Make(data.Direct),
119		transitive: uniquelist.Make(data.Transitive),
120	})
121}
122
123func (d DepSet[T]) GobEncode() ([]byte, error) {
124	return gobtools.CustomGobEncode[depSetGob[T]](&d)
125}
126
127func (d *DepSet[T]) GobDecode(data []byte) error {
128	return gobtools.CustomGobDecode[depSetGob[T]](data, d)
129}
130
131// New returns an immutable DepSet with the given order, direct and transitive contents.
132func New[T depSettableType](order Order, direct []T, transitive []DepSet[T]) DepSet[T] {
133	var directCopy []T
134	var transitiveCopy []DepSet[T]
135
136	// Create a zero value of DepSet, which will be used to check if the unique.Handle is the zero value.
137	var zeroDepSet DepSet[T]
138
139	nonEmptyTransitiveCount := 0
140	for _, t := range transitive {
141		// A zero valued DepSet has no associated unique.Handle for a depSet.  It has no contents, so it can
142		// be skipped.
143		if t != zeroDepSet {
144			if t.handle.Value().order != order {
145				panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
146					order, t.handle.Value().order))
147			}
148			nonEmptyTransitiveCount++
149		}
150	}
151
152	directCopy = slices.Clone(direct)
153	if nonEmptyTransitiveCount > 0 {
154		transitiveCopy = make([]DepSet[T], 0, nonEmptyTransitiveCount)
155	}
156	var transitiveIter iter.Seq2[int, DepSet[T]]
157	if order == TOPOLOGICAL {
158		// TOPOLOGICAL is implemented as a postorder traversal followed by reversing the output.
159		// Pre-reverse the inputs here so their order is maintained in the output.
160		slices.Reverse(directCopy)
161		transitiveIter = slices.Backward(transitive)
162	} else {
163		transitiveIter = slices.All(transitive)
164	}
165
166	// Copy only the non-zero-valued elements in the transitive list.  transitiveIter may be a forwards
167	// or backards iterator.
168	for _, t := range transitiveIter {
169		if t != zeroDepSet {
170			transitiveCopy = append(transitiveCopy, t)
171		}
172	}
173
174	// Optimization:  If both the direct and transitive lists are empty then this DepSet is semantically
175	// equivalent to the zero valued DepSet (effectively a nil pointer).  Returning the zero value will
176	// allow this DepSet to be skipped in DepSets that reference this one as a transitive input, saving
177	// memory.
178	if len(directCopy) == 0 && len(transitive) == 0 {
179		return DepSet[T]{}
180	}
181
182	// Create a depSet to hold the contents.
183	depSet := depSet[T]{
184		preorder:   order == PREORDER,
185		reverse:    order == TOPOLOGICAL,
186		order:      order,
187		direct:     uniquelist.Make(directCopy),
188		transitive: uniquelist.Make(transitiveCopy),
189	}
190
191	// Uniquify the depSet and store it in a DepSet.
192	return DepSet[T]{unique.Make(depSet)}
193}
194
195// Builder is used to create an immutable DepSet.
196type Builder[T depSettableType] struct {
197	order      Order
198	direct     []T
199	transitive []DepSet[T]
200}
201
202// NewBuilder returns a Builder to create an immutable DepSet with the given order and
203// type, represented by a slice of type that will be in the DepSet.
204func NewBuilder[T depSettableType](order Order) *Builder[T] {
205	return &Builder[T]{
206		order: order,
207	}
208}
209
210// DirectSlice adds direct contents to the DepSet being built by a Builder. Newly added direct
211// contents are to the right of any existing direct contents.
212func (b *Builder[T]) DirectSlice(direct []T) *Builder[T] {
213	b.direct = append(b.direct, direct...)
214	return b
215}
216
217// Direct adds direct contents to the DepSet being built by a Builder. Newly added direct
218// contents are to the right of any existing direct contents.
219func (b *Builder[T]) Direct(direct ...T) *Builder[T] {
220	b.direct = append(b.direct, direct...)
221	return b
222}
223
224// Transitive adds transitive contents to the DepSet being built by a Builder. Newly added
225// transitive contents are to the right of any existing transitive contents.
226func (b *Builder[T]) Transitive(transitive ...DepSet[T]) *Builder[T] {
227	var zeroDepSet DepSet[T]
228	for _, t := range transitive {
229		if t != zeroDepSet && t.order() != b.order {
230			panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
231				b.order, t.order()))
232		}
233	}
234	b.transitive = append(b.transitive, transitive...)
235	return b
236}
237
238// Build returns the DepSet being built by this Builder.  The Builder retains its contents
239// for creating more depSets.
240func (b *Builder[T]) Build() DepSet[T] {
241	return New(b.order, b.direct, b.transitive)
242}
243
244// collect collects the contents of the DepSet in depth-first order, preordered if d.preorder is set,
245// otherwise postordered.
246func (d DepSet[T]) collect() []T {
247	visited := make(map[DepSet[T]]bool)
248	var list []T
249
250	var dfs func(d DepSet[T])
251	dfs = func(d DepSet[T]) {
252		impl := d.impl()
253		visited[d] = true
254		if impl.preorder {
255			list = impl.direct.AppendTo(list)
256		}
257		for dep := range impl.transitive.Iter() {
258			if !visited[dep] {
259				dfs(dep)
260			}
261		}
262
263		if !impl.preorder {
264			list = impl.direct.AppendTo(list)
265		}
266	}
267
268	dfs(d)
269
270	return list
271}
272
273// ToList returns the DepSet flattened to a list.  The order in the list is based on the order
274// of the DepSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
275// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
276// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
277// its transitive dependencies, in which case the ordering of the duplicated element is not
278// guaranteed).
279func (d DepSet[T]) ToList() []T {
280	var zeroDepSet unique.Handle[depSet[T]]
281	if d.handle == zeroDepSet {
282		return nil
283	}
284	impl := d.impl()
285	list := d.collect()
286	list = firstUniqueInPlace(list)
287	if impl.reverse {
288		slices.Reverse(list)
289	}
290	return list
291}
292
293// firstUniqueInPlace returns all unique elements of a slice, keeping the first copy of
294// each.  It modifies the slice contents in place, and returns a subslice of the original
295// slice.
296func firstUniqueInPlace[T comparable](slice []T) []T {
297	// 128 was chosen based on BenchmarkFirstUniqueStrings results.
298	if len(slice) > 128 {
299		return firstUniqueMap(slice)
300	}
301	return firstUniqueList(slice)
302}
303
304// firstUniqueList is an implementation of firstUnique using an O(N^2) list comparison to look for
305// duplicates.
306func firstUniqueList[T any](in []T) []T {
307	writeIndex := 0
308outer:
309	for readIndex := 0; readIndex < len(in); readIndex++ {
310		for compareIndex := 0; compareIndex < writeIndex; compareIndex++ {
311			if interface{}(in[readIndex]) == interface{}(in[compareIndex]) {
312				// The value at readIndex already exists somewhere in the output region
313				// of the slice before writeIndex, skip it.
314				continue outer
315			}
316		}
317		if readIndex != writeIndex {
318			in[writeIndex] = in[readIndex]
319		}
320		writeIndex++
321	}
322	return in[0:writeIndex]
323}
324
325// firstUniqueMap is an implementation of firstUnique using an O(N) hash set lookup to look for
326// duplicates.
327func firstUniqueMap[T comparable](in []T) []T {
328	writeIndex := 0
329	seen := make(map[T]bool, len(in))
330	for readIndex := 0; readIndex < len(in); readIndex++ {
331		if _, exists := seen[in[readIndex]]; exists {
332			continue
333		}
334		seen[in[readIndex]] = true
335		if readIndex != writeIndex {
336			in[writeIndex] = in[readIndex]
337		}
338		writeIndex++
339	}
340	return in[0:writeIndex]
341}
342