blob: cdd5a5591eea923b27fc8c2191cba30648039bf2 [file] [log] [blame]
// Copyright 2021 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 gen
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"dawn.googlesource.com/dawn/tools/src/fileutils"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/sem"
)
// Permutator generates permutations of intrinsic overloads
type Permutator struct {
sem *sem.Sem
allTypes []sem.FullyQualifiedName
}
// NewPermutator returns a new initialized Permutator
func NewPermutator(s *sem.Sem) (*Permutator, error) {
// allTypes are the list of FQNs that are used for unconstrained types
allTypes := []sem.FullyQualifiedName{}
for _, ty := range s.Types {
if len(ty.TemplateParams) > 0 {
// Ignore aggregate types for now.
// If generation of e2e tests with aggregate types is required then selectively allow
// them here - just be careful with permutation explosions!
continue
}
allTypes = append(allTypes, sem.FullyQualifiedName{Target: ty})
}
return &Permutator{
sem: s,
allTypes: allTypes,
}, nil
}
// Permutation describes a single permutation of an overload
type Permutation struct {
sem.Overload // The permuted overload signature
ExplicitTemplateArgs []sem.FullyQualifiedName
Desc string // Description of the overload
Hash string // Hash of the overload
}
// Permute generates a set of permutations for the given intrinsic overload
func (p *Permutator) Permute(overload *sem.Overload) ([]Permutation, error) {
state := permutationState{
Permutator: p,
templateTypes: map[sem.TemplateParam]sem.FullyQualifiedName{},
templateNumbers: map[sem.TemplateParam]any{},
parameters: map[int]sem.FullyQualifiedName{},
}
out := []Permutation{}
// Map of hash to permutation description. Used to detect collisions.
hashes := map[string]string{}
// permute appends a permutation to out.
// permute may be chained to generate N-dimensional permutations.
permute := func() error {
// Generate an overload with the templated types replaced with the permuted types.
o := sem.Overload{
Decl: overload.Decl,
Intrinsic: overload.Intrinsic,
CanBeUsedInStage: overload.CanBeUsedInStage,
}
for i, p := range overload.Parameters {
ty := state.parameters[i]
if !validate(ty, &o.CanBeUsedInStage) {
return nil
}
o.Parameters = append(o.Parameters, sem.Parameter{
Name: p.Name,
Type: ty,
IsConst: p.IsConst,
TestValue: p.TestValue,
})
}
if overload.ReturnType != nil {
retTys, err := state.permuteFQN(*overload.ReturnType)
if err != nil {
return fmt.Errorf("while permuting return type: %w", err)
}
if len(retTys) != 1 {
return fmt.Errorf("result type not pinned")
}
o.ReturnType = &retTys[0]
}
explicitTemplateArgs := make([]sem.FullyQualifiedName, len(overload.ExplicitTemplates))
for i, t := range overload.ExplicitTemplates {
ty := state.templateTypes[t]
explicitTemplateArgs[i] = ty
o.ExplicitTemplates = append(o.ExplicitTemplates, &sem.TemplateTypeParam{
Name: t.GetName(),
Type: &ty,
})
}
desc := fmt.Sprint(o)
hash := sha256.Sum256([]byte(desc))
const hashLength = 6
shortHash := hex.EncodeToString(hash[:])[:hashLength]
out = append(out, Permutation{
Overload: o,
ExplicitTemplateArgs: explicitTemplateArgs,
Desc: desc,
Hash: shortHash,
})
// Check for hash collisions
if existing, collision := hashes[shortHash]; collision {
return fmt.Errorf(`hash '%v' collision between
%v
and
%v
Increase hashLength in %v`,
shortHash, existing, desc, fileutils.ThisLine())
}
hashes[shortHash] = desc
return nil
}
for i, param := range overload.Parameters {
i, param := i, param // Capture iterator values for anonymous function
next := permute // Permutation chaining
permute = func() error {
permutations, err := state.permuteFQN(param.Type)
if err != nil {
return fmt.Errorf("while processing parameter %v: %w", i, err)
}
if len(permutations) == 0 {
return fmt.Errorf("parameter %v has no permutations", i)
}
for _, fqn := range permutations {
state.parameters[i] = fqn
if err := next(); err != nil {
return err
}
}
return nil
}
}
for _, t := range overload.AllTemplates() {
t := t // Capture iterator values for anonymous function
next := permute // Permutation chaining
switch t := t.(type) {
case *sem.TemplateTypeParam:
permute = func() error {
types := p.allTypes
if t.Type != nil {
var err error
types, err = state.permuteFQN(*t.Type)
if err != nil {
return fmt.Errorf("while permuting template types: %w", err)
}
}
if len(types) == 0 {
return fmt.Errorf("template type %v has no permutations", t.Name)
}
for _, ty := range types {
state.templateTypes[t] = ty
if err := next(); err != nil {
return err
}
}
return nil
}
case *sem.TemplateEnumParam:
var permutations []sem.FullyQualifiedName
var err error
if t.Matcher != nil {
permutations, err = state.permuteFQN(sem.FullyQualifiedName{Target: t.Matcher})
} else {
permutations, err = state.permuteFQN(sem.FullyQualifiedName{Target: t.Enum})
}
if err != nil {
return nil, fmt.Errorf("while permuting template numbers: %w", err)
}
if len(permutations) == 0 {
return nil, fmt.Errorf("template type %v has no permutations", t.Name)
}
permute = func() error {
for _, n := range permutations {
state.templateNumbers[t] = n
if err := next(); err != nil {
return err
}
}
return nil
}
case *sem.TemplateNumberParam:
// Currently all open numbers are used for vector / matrices
permutations := []int{2, 3, 4}
permute = func() error {
for _, n := range permutations {
state.templateNumbers[t] = n
if err := next(); err != nil {
return err
}
}
return nil
}
}
}
if err := permute(); err != nil {
return nil, fmt.Errorf("%v %v %w\nState: %v", overload.Decl.Source, overload.Decl, err, state)
}
return out, nil
}
type permutationState struct {
*Permutator
templateTypes map[sem.TemplateParam]sem.FullyQualifiedName
templateNumbers map[sem.TemplateParam]any
parameters map[int]sem.FullyQualifiedName
}
func (s permutationState) String() string {
sb := &strings.Builder{}
sb.WriteString("Template types:\n")
for ct, ty := range s.templateTypes {
fmt.Fprintf(sb, " %v: %v\n", ct.GetName(), ty)
}
sb.WriteString("Template numbers:\n")
for cn, v := range s.templateNumbers {
fmt.Fprintf(sb, " %v: %v\n", cn.GetName(), v)
}
return sb.String()
}
func (s *permutationState) permuteFQN(in sem.FullyQualifiedName) ([]sem.FullyQualifiedName, error) {
args := append([]any{}, in.TemplateArguments...)
out := []sem.FullyQualifiedName{}
// permute appends a permutation to out.
// permute may be chained to generate N-dimensional permutations.
var permute func() error
switch target := in.Target.(type) {
case *sem.Type:
permute = func() error {
// Inner-most permute lambda.
// Append a the current permutation to out
out = append(out, sem.FullyQualifiedName{Target: in.Target, TemplateArguments: args})
// Clone the argument list, for the next permutation to use.
args = append([]any{}, args...)
return nil
}
case sem.TemplateParam:
if ty, ok := s.templateTypes[target]; ok {
permute = func() error {
out = append(out, ty)
return nil
}
} else {
return nil, fmt.Errorf("'%v' was not found in templateTypes", target.GetName())
}
case *sem.TypeMatcher:
permute = func() error {
for _, ty := range target.Types {
out = append(out, sem.FullyQualifiedName{Target: ty})
}
return nil
}
case *sem.EnumMatcher:
permute = func() error {
for _, o := range target.Options {
if !o.IsInternal {
out = append(out, sem.FullyQualifiedName{Target: o})
}
}
return nil
}
case *sem.Enum:
permute = func() error {
for _, e := range target.Entries {
if !e.IsInternal {
out = append(out, sem.FullyQualifiedName{Target: e})
}
}
return nil
}
default:
return nil, fmt.Errorf("unhandled target type: %T", in.Target)
}
for i, arg := range in.TemplateArguments {
i := i // Capture iterator value for anonymous functions
next := permute // Permutation chaining
switch arg := arg.(type) {
case sem.FullyQualifiedName:
switch target := arg.Target.(type) {
case sem.TemplateParam:
if ty, ok := s.templateTypes[target]; ok {
args[i] = ty
} else if num, ok := s.templateNumbers[target]; ok {
args[i] = num
} else {
return nil, fmt.Errorf("'%v' was not found in templateTypes or templateNumbers", target.GetName())
}
default:
perms, err := s.permuteFQN(arg)
if err != nil {
return nil, fmt.Errorf("while processing template argument %v: %v", i, err)
}
if len(perms) == 0 {
return nil, fmt.Errorf("template argument %v has no permutations", i)
}
permute = func() error {
for _, f := range perms {
args[i] = f
if err := next(); err != nil {
return err
}
}
return nil
}
}
default:
return nil, fmt.Errorf("permuteFQN() unhandled template argument type: %T", arg)
}
}
if err := permute(); err != nil {
return nil, fmt.Errorf("while processing fully qualified name '%v': %w", in.Target.GetName(), err)
}
return out, nil
}
func validate(fqn sem.FullyQualifiedName, uses *sem.StageUses) bool {
if strings.HasPrefix(fqn.Target.GetName(), "_") {
return false // Builtin, untypeable return type
}
isStorable := func(elTy sem.FullyQualifiedName) bool {
elTyName := elTy.Target.GetName()
switch {
case elTyName == "bool",
strings.Contains(elTyName, "sampler"),
strings.Contains(elTyName, "texture"),
IsAbstract(DeepestElementType(elTy)):
return false
}
return true
}
switch fqn.Target.GetName() {
case "array":
if !isStorable(fqn.TemplateArguments[0].(sem.FullyQualifiedName)) {
return false
}
case "ptr":
// https://gpuweb.github.io/gpuweb/wgsl/#address-space
access := fqn.TemplateArguments[2].(sem.FullyQualifiedName).Target.(*sem.EnumEntry).Name
addressSpace := fqn.TemplateArguments[0].(sem.FullyQualifiedName).Target.(*sem.EnumEntry).Name
switch addressSpace {
case "function", "private":
if access != "read_write" {
return false
}
case "workgroup":
uses.Vertex = false
uses.Fragment = false
if access != "read_write" {
return false
}
if !isStorable(fqn.TemplateArguments[1].(sem.FullyQualifiedName)) {
return false
}
case "uniform":
if access != "read" {
return false
}
case "storage":
if access != "read_write" && access != "read" {
return false
}
case "handle":
if access != "read" {
return false
}
default:
return false
}
}
for _, arg := range fqn.TemplateArguments {
if argFQN, ok := arg.(sem.FullyQualifiedName); ok {
if !validate(argFQN, uses) {
return false
}
}
}
return true
}