mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-10 05:57:51 +00:00
[tools]: Generate intrinisic test cases
Add test/intrinsics/intrinsics.wgsl.tmpl that generates a vast set of intrinsic overload permutations into test/intrinsics/gen/... Add expected output for all of these, including 'SKIP' headers for those that currently fail. Fixed: tint:832 Change-Id: Id6888df52c07f35e7a55199f2ad4d842c6e2595c Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53051 Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
committed by
Tint LUCI CQ
parent
2270b434bc
commit
b07a7cead6
@@ -30,6 +30,7 @@ type generator struct {
|
||||
t *template.Template
|
||||
cached struct {
|
||||
intrinsicTable *IntrinsicTable // lazily built by intrinsicTable()
|
||||
permuter *Permuter // lazily built by permute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ func (g *generator) generate(tmpl string, w io.Writer, writeFile WriteFile) erro
|
||||
"IsFirstIn": isFirstIn,
|
||||
"IsLastIn": isLastIn,
|
||||
"IntrinsicTable": g.intrinsicTable,
|
||||
"Permute": g.permute,
|
||||
"Eval": g.eval,
|
||||
"WriteFile": func(relpath, content string) (string, error) { return "", writeFile(relpath, content) },
|
||||
}).Option("missingkey=error").
|
||||
@@ -108,6 +110,19 @@ func (g *generator) intrinsicTable() (*IntrinsicTable, error) {
|
||||
return g.cached.intrinsicTable, nil
|
||||
}
|
||||
|
||||
// permute lazily calls buildPermuter(), caching the result for repeated
|
||||
// calls, then passes the argument to Permutator.Permute()
|
||||
func (g *generator) permute(overload *sem.Overload) ([]Permutation, error) {
|
||||
if g.cached.permuter == nil {
|
||||
var err error
|
||||
g.cached.permuter, err = buildPermuter(g.s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return g.cached.permuter.Permute(overload)
|
||||
}
|
||||
|
||||
// Map is a simple generic key-value map, which can be used in the template
|
||||
type Map map[interface{}]interface{}
|
||||
|
||||
|
||||
349
tools/src/cmd/intrinsic-gen/gen/permutate.go
Normal file
349
tools/src/cmd/intrinsic-gen/gen/permutate.go
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// 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 gen
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/sem"
|
||||
"dawn.googlesource.com/tint/tools/src/fileutils"
|
||||
)
|
||||
|
||||
// Permuter generates permutations of intrinsic overloads
|
||||
type Permuter struct {
|
||||
sem *sem.Sem
|
||||
allTypes []sem.FullyQualifiedName
|
||||
}
|
||||
|
||||
// buildPermuter returns a new initialized Permuter
|
||||
func buildPermuter(s *sem.Sem) (*Permuter, error) {
|
||||
// allTypes are the list of FQNs that are used for open, unconstrained types
|
||||
allTypes := []sem.FullyQualifiedName{}
|
||||
for _, ty := range s.Types {
|
||||
if len(ty.TemplateParams) > 0 {
|
||||
// Ignore aggregate types for now.
|
||||
// TODO(bclayton): Support a limited set of aggregate types
|
||||
continue
|
||||
}
|
||||
allTypes = append(allTypes, sem.FullyQualifiedName{Target: ty})
|
||||
}
|
||||
return &Permuter{
|
||||
sem: s,
|
||||
allTypes: allTypes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Permutation describes a single permutation of an overload
|
||||
type Permutation struct {
|
||||
sem.Overload // The permutated overload signature
|
||||
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 *Permuter) Permute(overload *sem.Overload) ([]Permutation, error) {
|
||||
state := permutationState{
|
||||
Permuter: p,
|
||||
closedTypes: map[sem.TemplateParam]sem.FullyQualifiedName{},
|
||||
closedNumbers: map[sem.TemplateParam]interface{}{},
|
||||
parameters: map[int]sem.FullyQualifiedName{},
|
||||
}
|
||||
|
||||
out := []Permutation{}
|
||||
|
||||
// Map of hash to permutation description. Used to detect collisions.
|
||||
hashes := map[string]string{}
|
||||
|
||||
// permutate appends a permutation to out.
|
||||
// permutate may be chained to generate N-dimensional permutations.
|
||||
permutate := func() error {
|
||||
o := sem.Overload{
|
||||
Decl: overload.Decl,
|
||||
Function: overload.Function,
|
||||
CanBeUsedInStage: overload.CanBeUsedInStage,
|
||||
}
|
||||
for i, p := range overload.Parameters {
|
||||
ty := state.parameters[i]
|
||||
if !validate(ty) {
|
||||
return nil
|
||||
}
|
||||
o.Parameters = append(o.Parameters, sem.Parameter{
|
||||
Name: p.Name,
|
||||
Type: ty,
|
||||
})
|
||||
}
|
||||
if overload.ReturnType != nil {
|
||||
retTys, err := state.permutateFQN(*overload.ReturnType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while permutating return type: %w", err)
|
||||
}
|
||||
if len(retTys) != 1 {
|
||||
return fmt.Errorf("result type not pinned")
|
||||
}
|
||||
o.ReturnType = &retTys[0]
|
||||
}
|
||||
desc := fmt.Sprint(o)
|
||||
hash := sha256.Sum256([]byte(desc))
|
||||
const hashLength = 6
|
||||
shortHash := hex.EncodeToString(hash[:])[:hashLength]
|
||||
out = append(out, Permutation{
|
||||
Overload: o,
|
||||
Desc: desc,
|
||||
Hash: shortHash,
|
||||
})
|
||||
|
||||
// Check for hash collisions
|
||||
if existing, collision := hashes[shortHash]; collision {
|
||||
return fmt.Errorf("hash '%v' collision between %v and %v\nIncrease hashLength in %v",
|
||||
shortHash, existing, desc, fileutils.GoSourcePath())
|
||||
}
|
||||
hashes[shortHash] = desc
|
||||
return nil
|
||||
}
|
||||
for i, param := range overload.Parameters {
|
||||
i, param := i, param // Capture iterator values for anonymous function
|
||||
next := permutate // Permutation chaining
|
||||
permutate = func() error {
|
||||
permutations, err := state.permutateFQN(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.TemplateParams {
|
||||
next := permutate // Permutation chaining
|
||||
switch t := t.(type) {
|
||||
case *sem.TemplateTypeParam:
|
||||
types := p.allTypes
|
||||
if t.Type != nil {
|
||||
var err error
|
||||
types, err = state.permutateFQN(sem.FullyQualifiedName{Target: t.Type})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while permutating open types: %w", err)
|
||||
}
|
||||
}
|
||||
if len(types) == 0 {
|
||||
return nil, fmt.Errorf("open type %v has no permutations", t.Name)
|
||||
}
|
||||
permutate = func() error {
|
||||
for _, ty := range types {
|
||||
state.closedTypes[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.permutateFQN(sem.FullyQualifiedName{Target: t.Matcher})
|
||||
} else {
|
||||
permutations, err = state.permutateFQN(sem.FullyQualifiedName{Target: t.Enum})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while permutating open numbers: %w", err)
|
||||
}
|
||||
if len(permutations) == 0 {
|
||||
return nil, fmt.Errorf("open type %v has no permutations", t.Name)
|
||||
}
|
||||
permutate = func() error {
|
||||
for _, n := range permutations {
|
||||
state.closedNumbers[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}
|
||||
permutate = func() error {
|
||||
for _, n := range permutations {
|
||||
state.closedNumbers[t] = n
|
||||
if err := next(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := permutate(); err != nil {
|
||||
return nil, fmt.Errorf("%v %v %w\nState: %v", overload.Decl.Source, overload.Decl, err, state)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type permutationState struct {
|
||||
*Permuter
|
||||
closedTypes map[sem.TemplateParam]sem.FullyQualifiedName
|
||||
closedNumbers map[sem.TemplateParam]interface{}
|
||||
parameters map[int]sem.FullyQualifiedName
|
||||
}
|
||||
|
||||
func (s permutationState) String() string {
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString("Closed types:\n")
|
||||
for ct, ty := range s.closedTypes {
|
||||
fmt.Fprintf(sb, " %v: %v\n", ct.GetName(), ty)
|
||||
}
|
||||
sb.WriteString("Closed numbers:\n")
|
||||
for cn, v := range s.closedNumbers {
|
||||
fmt.Fprintf(sb, " %v: %v\n", cn.GetName(), v)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (s *permutationState) permutateFQN(in sem.FullyQualifiedName) ([]sem.FullyQualifiedName, error) {
|
||||
args := append([]interface{}{}, in.TemplateArguments...)
|
||||
out := []sem.FullyQualifiedName{}
|
||||
|
||||
// permutate appends a permutation to out.
|
||||
// permutate may be chained to generate N-dimensional permutations.
|
||||
var permutate func() error
|
||||
|
||||
switch target := in.Target.(type) {
|
||||
case *sem.Type:
|
||||
permutate = func() error {
|
||||
out = append(out, sem.FullyQualifiedName{Target: in.Target, TemplateArguments: args})
|
||||
args = append([]interface{}{}, in.TemplateArguments...)
|
||||
return nil
|
||||
}
|
||||
case sem.TemplateParam:
|
||||
if ty, ok := s.closedTypes[target]; ok {
|
||||
permutate = func() error {
|
||||
out = append(out, ty)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("'%v' was not found in closedTypes", target.GetName())
|
||||
}
|
||||
case *sem.TypeMatcher:
|
||||
permutate = func() error {
|
||||
for _, ty := range target.Types {
|
||||
out = append(out, sem.FullyQualifiedName{Target: ty})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case *sem.EnumMatcher:
|
||||
permutate = func() error {
|
||||
for _, o := range target.Options {
|
||||
if !o.IsInternal {
|
||||
out = append(out, sem.FullyQualifiedName{Target: o})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case *sem.Enum:
|
||||
permutate = 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 := permutate // Permutation chaining
|
||||
switch arg := arg.(type) {
|
||||
case sem.FullyQualifiedName:
|
||||
switch target := arg.Target.(type) {
|
||||
case sem.TemplateParam:
|
||||
if ty, ok := s.closedTypes[target]; ok {
|
||||
args[i] = ty
|
||||
} else if num, ok := s.closedNumbers[target]; ok {
|
||||
args[i] = num
|
||||
} else {
|
||||
return nil, fmt.Errorf("'%v' was not found in closedTypes or closedNumbers", target.GetName())
|
||||
}
|
||||
default:
|
||||
perms, err := s.permutateFQN(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)
|
||||
}
|
||||
permutate = func() error {
|
||||
for _, f := range perms {
|
||||
args[i] = f
|
||||
if err := next(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("permutateFQN() unhandled template argument type: %T", arg)
|
||||
}
|
||||
}
|
||||
|
||||
if err := permutate(); 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) bool {
|
||||
switch fqn.Target.GetName() {
|
||||
case "array":
|
||||
elTy := fqn.TemplateArguments[0].(sem.FullyQualifiedName)
|
||||
elTyName := elTy.Target.GetName()
|
||||
switch {
|
||||
case elTyName == "bool" ||
|
||||
strings.Contains(elTyName, "sampler"),
|
||||
strings.Contains(elTyName, "texture"):
|
||||
return false // Not storable
|
||||
}
|
||||
case "ptr":
|
||||
storage := fqn.TemplateArguments[0].(sem.FullyQualifiedName).Target.(*sem.EnumEntry)
|
||||
return storage.Name == "function"
|
||||
}
|
||||
|
||||
for _, arg := range fqn.TemplateArguments {
|
||||
if argFQN, ok := arg.(sem.FullyQualifiedName); ok {
|
||||
if !validate(argFQN) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -82,7 +82,8 @@ func run() error {
|
||||
// Recursively find all the template files in the <tint>/src directory
|
||||
files, err := glob.Scan(projectRoot, glob.MustParseConfig(`{
|
||||
"paths": [{"include": [
|
||||
"src/**.tmpl"
|
||||
"src/**.tmpl",
|
||||
"test/**.tmpl"
|
||||
]}]
|
||||
}`))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user