blob: 32059a62be74c2553c74857605a8c5ccc0f8cde2 [file] [log] [blame]
// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package result
import (
"sort"
"dawn.googlesource.com/dawn/tools/src/cts/query"
)
// MinimalVariantTags accepts a list of tag-sets (e.g GPU tags, OS tags, etc),
// and returns an optimized list of variants, folding together variants that
// have identical result query-to-status mappings, and removing redundant tags.
//
// MinimalVariantTags will attempt to remove variant tags starting with the
// first set of tags in tagSets, then second, and so on. If a tag-set cannot
// be removed, then the tags of the set are left alone, and the algorithm will
// progress to the next tag-set.
//
// MinimalVariantTags assumes that there are no duplicate results (same query,
// same tags) in l.
func (l List) MinimalVariantTags(tagSets []Tags) []Variant {
type VariantData struct {
// The variant tags
tags Variant
// The query -> status for all results in l that have this variant's
// tags.
queryToStatus map[query.Query]Status
}
variants := []VariantData{}
// Build the initial list of variants from l.
// Bin result [query -> status] to the variant.
{
variantIndices := map[string]int{}
for _, r := range l {
key := TagsToString(r.Tags)
if idx, found := variantIndices[key]; !found {
variantIndices[key] = len(variants)
variants = append(variants, VariantData{
tags: Variant(r.Tags.Clone()),
queryToStatus: map[query.Query]Status{
r.Query: r.Status,
},
})
} else {
variants[idx].queryToStatus[r.Query] = r.Status
}
}
}
// canReduce checks that the variant would match the same results if the
// tags were reduced to 'tags'. Returns true if the variant's tags could
// be reduced, otherwise false.
canReduce := func(variant VariantData, tags Tags) bool {
for _, r := range l.FilterByTags(tags) {
existing, found := variant.queryToStatus[r.Query]
if !found {
// Removing the tag has expanded the set of queries.
return false
}
if existing != r.Status {
// Removing the tag has resulted in two queries with different
// results.
return false
}
}
return true
}
// tryToRemoveTags will remove all the tags in 'tags' from all variants
// iff doing so does not affect the set of results filtered by each variant.
// If it was possible to remove the tags, then variants that now have the
// same tags may be folded together, reducing the total number of variants.
tryToRemoveTags := func(tags Tags) {
newVariants := make([]VariantData, 0, len(variants))
for _, v := range variants {
// Does the variant even contain these tags?
if !v.tags.ContainsAny(tags) {
// Nope. Skip the canReduce() call, and keep the variant.
newVariants = append(newVariants, v)
continue
}
// Build the new set of tags with 'tags' removed.
newTags := v.tags.Clone()
newTags.RemoveAll(tags)
// Check wether removal of these tags affected the outcome.
if !canReduce(v, newTags) {
// Removing these tags resulted in differences.
return // Abort
}
newVariants = append(newVariants, VariantData{newTags, v.queryToStatus})
}
// Remove variants that are now subsets of others.
// Start by sorting the variants by number of tags.
// This ensures that the variants with fewer tags (fewer constraints)
// come first.
sort.Slice(newVariants, func(i, j int) bool {
return len(newVariants[i].tags) < len(newVariants[j].tags)
})
// Now check each variant's tags against the previous variant tags.
// As we've sorted, we know that supersets (fewer-tags) come before
// subsets (more-tags).
variants = []VariantData{}
nextVariant:
for i, v1 := range newVariants { // for variants 0..N
for _, v2 := range newVariants[:i] { // for variants 0..i
if v1.tags.ContainsAll(v2.tags) {
continue nextVariant // v1 is a subset of v2. Omit.
}
}
variants = append(variants, v1)
}
}
// Attempt to remove the tag sets from the variants, one by one.
for _, tags := range tagSets {
tryToRemoveTags(tags)
}
// Return the final set of unique variants
out := make([]Variant, len(variants))
for i, v := range variants {
out[i] = v.tags
}
return out
}