summaryrefslogtreecommitdiff
path: root/android/depset_generic.go
blob: f00e462c20b2c17c94c84d593424a92bb1e8cc8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package android

import (
	"fmt"
	"reflect"
)

// depSet is designed to be conceptually compatible with Bazel's depsets:
// https://docs.bazel.build/versions/master/skylark/depsets.html

type DepSetOrder int

const (
	PREORDER DepSetOrder = iota
	POSTORDER
	TOPOLOGICAL
)

func (o DepSetOrder) String() string {
	switch o {
	case PREORDER:
		return "PREORDER"
	case POSTORDER:
		return "POSTORDER"
	case TOPOLOGICAL:
		return "TOPOLOGICAL"
	default:
		panic(fmt.Errorf("Invalid DepSetOrder %d", o))
	}
}

// A depSet efficiently stores a slice of an arbitrary type from transitive dependencies without
// copying. It is stored as a DAG of depSet nodes, each of which has some direct contents and a list
// of dependency depSet nodes.
//
// A depSet has an order that will be used to walk the DAG when ToList() is called.  The order
// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
// elements of children are listed after all of their parents (unless there are duplicate direct
// elements in the depSet or any of its transitive dependencies, in which case the ordering of the
// duplicated element is not guaranteed).
//
// A depSet is created by newDepSet or newDepSetBuilder.Build from the slice for direct contents
// and the *depSets of dependencies. A depSet is immutable once created.
//
// This object uses reflection to remain agnostic to the type it contains.  It should be replaced
// with generics once those exist in Go.  Callers should generally use a thin wrapper around depSet
// that provides type-safe methods like DepSet for Paths.
type depSet struct {
	preorder   bool
	reverse    bool
	order      DepSetOrder
	direct     interface{}
	transitive []*depSet
}

type depSetInterface interface {
	embeddedDepSet() *depSet
}

func (d *depSet) embeddedDepSet() *depSet {
	return d
}

var _ depSetInterface = (*depSet)(nil)

// newDepSet returns an immutable depSet with the given order, direct and transitive contents.
// direct must be a slice, but is not type-safe due to the lack of generics in Go.  It can be a
// nil slice, but not a nil interface{}, i.e. []string(nil) but not nil.
func newDepSet(order DepSetOrder, direct interface{}, transitive interface{}) *depSet {
	var directCopy interface{}
	transitiveDepSet := sliceToDepSets(transitive, order)

	if order == TOPOLOGICAL {
		directCopy = reverseSlice(direct)
		reverseSliceInPlace(transitiveDepSet)
	} else {
		directCopy = copySlice(direct)
	}

	return &depSet{
		preorder:   order == PREORDER,
		reverse:    order == TOPOLOGICAL,
		order:      order,
		direct:     directCopy,
		transitive: transitiveDepSet,
	}
}

// depSetBuilder is used to create an immutable depSet.
type depSetBuilder struct {
	order      DepSetOrder
	direct     reflect.Value
	transitive []*depSet
}

// newDepSetBuilder returns a depSetBuilder to create an immutable depSet with the given order and
// type, represented by a slice of type that will be in the depSet.
func newDepSetBuilder(order DepSetOrder, typ interface{}) *depSetBuilder {
	empty := reflect.Zero(reflect.TypeOf(typ))
	return &depSetBuilder{
		order:  order,
		direct: empty,
	}
}

// sliceToDepSets converts a slice of any type that implements depSetInterface (by having a depSet
// embedded in it) into a []*depSet.
func sliceToDepSets(in interface{}, order DepSetOrder) []*depSet {
	slice := reflect.ValueOf(in)
	length := slice.Len()
	out := make([]*depSet, length)
	for i := 0; i < length; i++ {
		vi := slice.Index(i)
		depSetIntf, ok := vi.Interface().(depSetInterface)
		if !ok {
			panic(fmt.Errorf("element %d is a %s, not a depSetInterface", i, vi.Type()))
		}
		depSet := depSetIntf.embeddedDepSet()
		if depSet.order != order {
			panic(fmt.Errorf("incompatible order, new depSet is %s but transitive depSet is %s",
				order, depSet.order))
		}
		out[i] = depSet
	}
	return out
}

// DirectSlice adds direct contents to the depSet being built by a depSetBuilder. Newly added direct
// contents are to the right of any existing direct contents.  The argument must be a slice, but
// is not type-safe due to the lack of generics in Go.
func (b *depSetBuilder) DirectSlice(direct interface{}) *depSetBuilder {
	b.direct = reflect.AppendSlice(b.direct, reflect.ValueOf(direct))
	return b
}

// Direct adds direct contents to the depSet being built by a depSetBuilder. Newly added direct
// contents are to the right of any existing direct contents.  The argument must be the same type
// as the element of the slice passed to newDepSetBuilder, but is not type-safe due to the lack of
// generics in Go.
func (b *depSetBuilder) Direct(direct interface{}) *depSetBuilder {
	b.direct = reflect.Append(b.direct, reflect.ValueOf(direct))
	return b
}

// Transitive adds transitive contents to the DepSet being built by a DepSetBuilder. Newly added
// transitive contents are to the right of any existing transitive contents.  The argument can
// be any slice of type that has depSet embedded in it.
func (b *depSetBuilder) Transitive(transitive interface{}) *depSetBuilder {
	depSets := sliceToDepSets(transitive, b.order)
	b.transitive = append(b.transitive, depSets...)
	return b
}

// Returns the depSet being built by this depSetBuilder.  The depSetBuilder retains its contents
// for creating more depSets.
func (b *depSetBuilder) Build() *depSet {
	return newDepSet(b.order, b.direct.Interface(), b.transitive)
}

// walk calls the visit method in depth-first order on a DepSet, preordered if d.preorder is set,
// otherwise postordered.
func (d *depSet) walk(visit func(interface{})) {
	visited := make(map[*depSet]bool)

	var dfs func(d *depSet)
	dfs = func(d *depSet) {
		visited[d] = true
		if d.preorder {
			visit(d.direct)
		}
		for _, dep := range d.transitive {
			if !visited[dep] {
				dfs(dep)
			}
		}

		if !d.preorder {
			visit(d.direct)
		}
	}

	dfs(d)
}

// ToList returns the depSet flattened to a list.  The order in the list is based on the order
// of the depSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
// its transitive dependencies, in which case the ordering of the duplicated element is not
// guaranteed).
//
// This method uses a reflection-based implementation to find the unique elements in slice, which
// is around 3x slower than a concrete implementation.  Type-safe wrappers around depSet can
// provide their own implementation of ToList that calls depSet.toList with a method that
// uses a concrete implementation.
func (d *depSet) ToList() interface{} {
	return d.toList(firstUnique)
}

// toList returns the depSet flattened to a list.  The order in the list is based on the order
// of the depSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
// its transitive dependencies, in which case the ordering of the duplicated element is not
// guaranteed).  The firstUniqueFunc is used to remove duplicates from the list.
func (d *depSet) toList(firstUniqueFunc func(interface{}) interface{}) interface{} {
	if d == nil {
		return nil
	}
	slice := reflect.Zero(reflect.TypeOf(d.direct))
	d.walk(func(paths interface{}) {
		slice = reflect.AppendSlice(slice, reflect.ValueOf(paths))
	})
	list := slice.Interface()
	list = firstUniqueFunc(list)
	if d.reverse {
		reverseSliceInPlace(list)
	}
	return list
}

// firstUnique returns all unique elements of a slice, keeping the first copy of each.  It
// modifies the slice contents in place, and returns a subslice of the original slice.  The
// argument must be a slice, but is not type-safe due to the lack of reflection in Go.
//
// Performance of the reflection-based firstUnique is up to 3x slower than a concrete type
// version such as FirstUniqueStrings.
func firstUnique(slice interface{}) interface{} {
	// 4 was chosen based on Benchmark_firstUnique results.
	if reflect.ValueOf(slice).Len() > 4 {
		return firstUniqueMap(slice)
	}
	return firstUniqueList(slice)
}

// firstUniqueList is an implementation of firstUnique using an O(N^2) list comparison to look for
// duplicates.
func firstUniqueList(in interface{}) interface{} {
	writeIndex := 0
	slice := reflect.ValueOf(in)
	length := slice.Len()
outer:
	for readIndex := 0; readIndex < length; readIndex++ {
		readValue := slice.Index(readIndex)
		for compareIndex := 0; compareIndex < writeIndex; compareIndex++ {
			compareValue := slice.Index(compareIndex)
			// These two Interface() calls seem to cause an allocation and significantly
			// slow down this list-based implementation.  The map implementation below doesn't
			// have this issue because reflect.Value.MapIndex takes a Value and appears to be
			// able to do the map lookup without an allocation.
			if readValue.Interface() == compareValue.Interface() {
				// The value at readIndex already exists somewhere in the output region
				// of the slice before writeIndex, skip it.
				continue outer
			}
		}
		if readIndex != writeIndex {
			writeValue := slice.Index(writeIndex)
			writeValue.Set(readValue)
		}
		writeIndex++
	}
	return slice.Slice(0, writeIndex).Interface()
}

var trueValue = reflect.ValueOf(true)

// firstUniqueList is an implementation of firstUnique using an O(N) hash set lookup to look for
// duplicates.
func firstUniqueMap(in interface{}) interface{} {
	writeIndex := 0
	slice := reflect.ValueOf(in)
	length := slice.Len()
	seen := reflect.MakeMapWithSize(reflect.MapOf(slice.Type().Elem(), trueValue.Type()), slice.Len())
	for readIndex := 0; readIndex < length; readIndex++ {
		readValue := slice.Index(readIndex)
		if seen.MapIndex(readValue).IsValid() {
			continue
		}
		seen.SetMapIndex(readValue, trueValue)
		if readIndex != writeIndex {
			writeValue := slice.Index(writeIndex)
			writeValue.Set(readValue)
		}
		writeIndex++
	}
	return slice.Slice(0, writeIndex).Interface()
}

// reverseSliceInPlace reverses the elements of a slice in place.  The argument must be a slice, but
// is not type-safe due to the lack of reflection in Go.
func reverseSliceInPlace(in interface{}) {
	swapper := reflect.Swapper(in)
	slice := reflect.ValueOf(in)
	length := slice.Len()
	for i, j := 0, length-1; i < j; i, j = i+1, j-1 {
		swapper(i, j)
	}
}

// reverseSlice returns a copy of a slice in reverse order.  The argument must be a slice, but is
// not type-safe due to the lack of reflection in Go.
func reverseSlice(in interface{}) interface{} {
	slice := reflect.ValueOf(in)
	if !slice.IsValid() || slice.IsNil() {
		return in
	}
	if slice.Kind() != reflect.Slice {
		panic(fmt.Errorf("%t is not a slice", in))
	}
	length := slice.Len()
	if length == 0 {
		return in
	}
	out := reflect.MakeSlice(slice.Type(), length, length)
	for i := 0; i < length; i++ {
		out.Index(i).Set(slice.Index(length - 1 - i))
	}
	return out.Interface()
}

// copySlice returns a copy of a slice.  The argument must be a slice, but is not type-safe due to
// the lack of reflection in Go.
func copySlice(in interface{}) interface{} {
	slice := reflect.ValueOf(in)
	if !slice.IsValid() || slice.IsNil() {
		return in
	}
	length := slice.Len()
	if length == 0 {
		return in
	}
	out := reflect.MakeSlice(slice.Type(), length, length)
	reflect.Copy(out, slice)
	return out.Interface()
}