xref: /aosp_15_r20/build/blueprint/depset/depset.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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
22	"github.com/google/blueprint/gobtools"
23)
24
25// DepSet is designed to be conceptually compatible with Bazel's depsets:
26// https://docs.bazel.build/versions/master/skylark/depsets.html
27
28type Order int
29
30const (
31	PREORDER Order = iota
32	POSTORDER
33	TOPOLOGICAL
34)
35
36func (o Order) String() string {
37	switch o {
38	case PREORDER:
39		return "PREORDER"
40	case POSTORDER:
41		return "POSTORDER"
42	case TOPOLOGICAL:
43		return "TOPOLOGICAL"
44	default:
45		panic(fmt.Errorf("Invalid Order %d", o))
46	}
47}
48
49type depSettableType comparable
50
51// A DepSet efficiently stores a slice of an arbitrary type from transitive dependencies without
52// copying. It is stored as a DAG of DepSet nodes, each of which has some direct contents and a list
53// of dependency DepSet nodes.
54//
55// A DepSet has an order that will be used to walk the DAG when ToList() is called.  The order
56// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
57// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
58// elements of children are listed after all of their parents (unless there are duplicate direct
59// elements in the DepSet or any of its transitive dependencies, in which case the ordering of the
60// duplicated element is not guaranteed).
61//
62// A DepSet is created by New or NewBuilder.Build from the slice for direct contents
63// and the *DepSets of dependencies. A DepSet is immutable once created.
64type DepSet[T depSettableType] struct {
65	handle *depSet[T]
66}
67
68type depSet[T depSettableType] struct {
69	preorder   bool
70	reverse    bool
71	order      Order
72	direct     []T
73	transitive []DepSet[T]
74}
75
76func (d DepSet[T]) impl() *depSet[T] {
77	return d.handle
78}
79
80func (d DepSet[T]) order() Order {
81	impl := d.impl()
82	return impl.order
83}
84
85type depSetGob[T depSettableType] struct {
86	Preorder   bool
87	Reverse    bool
88	Order      Order
89	Direct     []T
90	Transitive []DepSet[T]
91}
92
93func (d *DepSet[T]) ToGob() *depSetGob[T] {
94	impl := d.impl()
95	return &depSetGob[T]{
96		Preorder:   impl.preorder,
97		Reverse:    impl.reverse,
98		Order:      impl.order,
99		Direct:     impl.direct,
100		Transitive: impl.transitive,
101	}
102}
103
104func (d *DepSet[T]) FromGob(data *depSetGob[T]) {
105	d.handle = &depSet[T]{
106		preorder:   data.Preorder,
107		reverse:    data.Reverse,
108		order:      data.Order,
109		direct:     data.Direct,
110		transitive: data.Transitive,
111	}
112}
113
114func (d DepSet[T]) GobEncode() ([]byte, error) {
115	return gobtools.CustomGobEncode[depSetGob[T]](&d)
116}
117
118func (d *DepSet[T]) GobDecode(data []byte) error {
119	return gobtools.CustomGobDecode[depSetGob[T]](data, d)
120}
121
122// New returns an immutable DepSet with the given order, direct and transitive contents.
123func New[T depSettableType](order Order, direct []T, transitive []DepSet[T]) DepSet[T] {
124	var directCopy []T
125	var transitiveCopy []DepSet[T]
126	nonEmptyTransitiveCount := 0
127	for _, t := range transitive {
128		if t.handle != nil {
129			if t.order() != order {
130				panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
131					order, t.order()))
132			}
133			nonEmptyTransitiveCount++
134		}
135	}
136
137	directCopy = slices.Clone(direct)
138	if nonEmptyTransitiveCount > 0 {
139		transitiveCopy = make([]DepSet[T], 0, nonEmptyTransitiveCount)
140	}
141	var transitiveIter iter.Seq2[int, DepSet[T]]
142	if order == TOPOLOGICAL {
143		// TOPOLOGICAL is implemented as a postorder traversal followed by reversing the output.
144		// Pre-reverse the inputs here so their order is maintained in the output.
145		slices.Reverse(directCopy)
146		transitiveIter = slices.Backward(transitive)
147	} else {
148		transitiveIter = slices.All(transitive)
149	}
150	for _, t := range transitiveIter {
151		if t.handle != nil {
152			transitiveCopy = append(transitiveCopy, t)
153		}
154	}
155
156	if len(directCopy) == 0 && len(transitive) == 0 {
157		return DepSet[T]{nil}
158	}
159
160	depSet := &depSet[T]{
161		preorder:   order == PREORDER,
162		reverse:    order == TOPOLOGICAL,
163		order:      order,
164		direct:     directCopy,
165		transitive: transitiveCopy,
166	}
167
168	return DepSet[T]{depSet}
169}
170
171// Builder is used to create an immutable DepSet.
172type Builder[T depSettableType] struct {
173	order      Order
174	direct     []T
175	transitive []DepSet[T]
176}
177
178// NewBuilder returns a Builder to create an immutable DepSet with the given order and
179// type, represented by a slice of type that will be in the DepSet.
180func NewBuilder[T depSettableType](order Order) *Builder[T] {
181	return &Builder[T]{
182		order: order,
183	}
184}
185
186// DirectSlice adds direct contents to the DepSet being built by a Builder. Newly added direct
187// contents are to the right of any existing direct contents.
188func (b *Builder[T]) DirectSlice(direct []T) *Builder[T] {
189	b.direct = append(b.direct, direct...)
190	return b
191}
192
193// Direct adds direct contents to the DepSet being built by a Builder. Newly added direct
194// contents are to the right of any existing direct contents.
195func (b *Builder[T]) Direct(direct ...T) *Builder[T] {
196	b.direct = append(b.direct, direct...)
197	return b
198}
199
200// Transitive adds transitive contents to the DepSet being built by a Builder. Newly added
201// transitive contents are to the right of any existing transitive contents.
202func (b *Builder[T]) Transitive(transitive ...DepSet[T]) *Builder[T] {
203	for _, t := range transitive {
204		if t.handle != nil && t.order() != b.order {
205			panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
206				b.order, t.order()))
207		}
208	}
209	b.transitive = append(b.transitive, transitive...)
210	return b
211}
212
213// Build returns the DepSet being built by this Builder.  The Builder retains its contents
214// for creating more depSets.
215func (b *Builder[T]) Build() DepSet[T] {
216	return New(b.order, b.direct, b.transitive)
217}
218
219// walk calls the visit method in depth-first order on a DepSet, preordered if d.preorder is set,
220// otherwise postordered.
221func (d DepSet[T]) walk(visit func([]T)) {
222	visited := make(map[DepSet[T]]bool)
223
224	var dfs func(d DepSet[T])
225	dfs = func(d DepSet[T]) {
226		impl := d.impl()
227		visited[d] = true
228		if impl.preorder {
229			visit(impl.direct)
230		}
231		for _, dep := range impl.transitive {
232			if !visited[dep] {
233				dfs(dep)
234			}
235		}
236
237		if !impl.preorder {
238			visit(impl.direct)
239		}
240	}
241
242	dfs(d)
243}
244
245// ToList returns the DepSet flattened to a list.  The order in the list is based on the order
246// of the DepSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
247// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
248// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
249// its transitive dependencies, in which case the ordering of the duplicated element is not
250// guaranteed).
251func (d DepSet[T]) ToList() []T {
252	if d.handle == nil {
253		return nil
254	}
255	impl := d.impl()
256	var list []T
257	d.walk(func(paths []T) {
258		list = append(list, paths...)
259	})
260	list = firstUniqueInPlace(list)
261	if impl.reverse {
262		slices.Reverse(list)
263	}
264	return list
265}
266
267// firstUniqueInPlace returns all unique elements of a slice, keeping the first copy of
268// each.  It modifies the slice contents in place, and returns a subslice of the original
269// slice.
270func firstUniqueInPlace[T comparable](slice []T) []T {
271	// 128 was chosen based on BenchmarkFirstUniqueStrings results.
272	if len(slice) > 128 {
273		return firstUniqueMap(slice)
274	}
275	return firstUniqueList(slice)
276}
277
278// firstUniqueList is an implementation of firstUnique using an O(N^2) list comparison to look for
279// duplicates.
280func firstUniqueList[T any](in []T) []T {
281	writeIndex := 0
282outer:
283	for readIndex := 0; readIndex < len(in); readIndex++ {
284		for compareIndex := 0; compareIndex < writeIndex; compareIndex++ {
285			if interface{}(in[readIndex]) == interface{}(in[compareIndex]) {
286				// The value at readIndex already exists somewhere in the output region
287				// of the slice before writeIndex, skip it.
288				continue outer
289			}
290		}
291		if readIndex != writeIndex {
292			in[writeIndex] = in[readIndex]
293		}
294		writeIndex++
295	}
296	return in[0:writeIndex]
297}
298
299// firstUniqueMap is an implementation of firstUnique using an O(N) hash set lookup to look for
300// duplicates.
301func firstUniqueMap[T comparable](in []T) []T {
302	writeIndex := 0
303	seen := make(map[T]bool, len(in))
304	for readIndex := 0; readIndex < len(in); readIndex++ {
305		if _, exists := seen[in[readIndex]]; exists {
306			continue
307		}
308		seen[in[readIndex]] = true
309		if readIndex != writeIndex {
310			in[writeIndex] = in[readIndex]
311		}
312		writeIndex++
313	}
314	return in[0:writeIndex]
315}
316