blob: 2a5330023d47ea30ae098583be20b1e4a2a3f3fd [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 holds types and helpers for generating templated code from the
// intrinsic.def file.
//
// Used by tools/src/cmd/gen/main.go
package gen
import (
"fmt"
"strings"
"dawn.googlesource.com/dawn/tools/src/lut"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/sem"
)
// IntrinsicTable holds data specific to the intrinsic_table.inl.tmpl template
type IntrinsicTable struct {
// The semantic info
Sem *sem.Sem
// TMatchers are all the sem.TemplateType, sem.Type and sem.TypeMatchers.
// These are all implemented by classes deriving from tint::TypeMatcher
TMatchers []sem.Named
TMatcherIndex map[sem.Named]int // [object -> index] in TMatcher
// NMatchers are all the sem.TemplateNumber and sem.EnumMatchers.
// These are all implemented by classes deriving from tint::NumberMatcher
NMatchers []sem.Named
NMatcherIndex map[sem.Named]int // [object -> index] in NMatchers
MatcherIndices []int // kMatcherIndices table content
Templates []Template // kTemplates table content
Parameters []Parameter // kParameters table content
Overloads []Overload // kOverloads table content
Builtins []Intrinsic // kBuiltins table content
UnaryOperators []Intrinsic // kUnaryOperators table content
BinaryOperators []Intrinsic // kBinaryOperators table content
ConstructorsAndConverters []Intrinsic // kInitializersAndConverters table content
ConstEvalFunctions []string // kConstEvalFunctions table content
}
// Template is used to create the C++ TemplateInfo structure
type Template struct {
// Name of the template type (e.g. 'T')
Name string
// Kind of the template
Kind sem.TemplateKind
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers required to match
// the parameter type.
// The matcher indices index into IntrinsicTable::TMatchers and IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
MatcherIndicesOffset int
}
// Parameter is used to create the C++ ParameterInfo structure
type Parameter struct {
// The parameter usage (parameter name)
Usage string
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers required to match
// the parameter type.
// The matcher indices index into IntrinsicTable::TMatchers and IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
MatcherIndicesOffset int
}
// Overload is used to create the C++ OverloadInfo structure
type Overload struct {
// Total number of parameters for the overload
NumParameters int
// Total number of explicit templates for the overload
NumExplicitTemplates int
// Total number of explicit and implicit templates for the overload
NumTemplates int
// Index to the first template in IntrinsicTable.Templates
// This is a list of template starting with the explicit templates, then the implicit templates.
TemplatesOffset int
// Index to the first parameter in IntrinsicTable.Parameters
ParametersOffset int
// Index into IntrinsicTable.matcherIndices, beginning the list of matchers
// required to match the return type.
// The matcher indices index into IntrinsicTable::TMatchers.
// These indices are consumed by the matchers themselves.
ReturnMatcherIndicesOffset int
// Index into IntrinsicTable.ConstEvalFunctions.
ConstEvalFunctionOffset int
// StageUses describes the stages an overload can be used in
CanBeUsedInStage sem.StageUses
// True if the overload is marked as @must_use
MustUse bool
// True if the overload is marked as @member_function
MemberFunction bool
// True if the overload is marked as deprecated
IsDeprecated bool
// The kind of overload
Kind string
}
// Intrinsic is used to create the C++ IntrinsicInfo structure
type Intrinsic struct {
Name string
OverloadDescriptions []string
NumOverloads int
OverloadsOffset *int
}
// Helper for building the IntrinsicTable
type IntrinsicTableBuilder struct {
// The output of the builder
IntrinsicTable
// Lookup tables.
// These are packed (compressed) once all the entries have been added.
lut struct {
matcherIndices lut.LUT[int]
templates lut.LUT[Template]
constEvalFunctionIndices lut.LUT[string]
parameters lut.LUT[Parameter]
overloads lut.LUT[Overload]
}
}
type parameterBuilder struct {
usage string
matcherIndicesOffset *int
}
type templateBuilder struct {
// The index of the template in kTypeMatchers / kNumberMatchers
matcherIndex int
// The matcher indices for this template type / number
constraintIndicesOffset *int
}
// Helper for building a single overload
type overloadBuilder struct {
*IntrinsicTableBuilder
// The overload being built
overload *sem.Overload
// Map of TemplateParam to templatesBuilder
templateBuilders map[sem.TemplateParam]*templateBuilder
// Templates used by the overload
// This is a list of explicit template types followed by the implicit template types.
templates []Template
// Index to the first template in IntrinsicTable.Templates
// This is a list of template starting with the explicit templates, then the implicit templates.
templateOffset *int
// Builders for all parameters
parameterBuilders []parameterBuilder
// Index to the first parameter in IntrinsicTable.Parameters
parametersOffset *int
// Index into IntrinsicTable.ConstEvalFunctions
constEvalFunctionOffset *int
// Index into IntrinsicTable.matcherIndices, beginning the list of
// matchers required to match the return type.
// The matcher indices index into IntrinsicTable::TMatchers.
// These indices are consumed by the matchers themselves.
returnMatcherIndicesOffset *int
}
// layoutMatchers assigns each of the TMatchers and NMatchers a unique index.
func (b *IntrinsicTableBuilder) layoutMatchers(s *sem.Sem) {
// First MaxTemplates of TMatchers and NMatchers are template types
b.TMatchers = make([]sem.Named, s.MaxTemplates)
b.NMatchers = make([]sem.Named, s.MaxTemplates)
for _, m := range s.Types {
b.TMatcherIndex[m] = len(b.TMatchers)
b.TMatchers = append(b.TMatchers, m)
}
for _, m := range s.TypeMatchers {
b.TMatcherIndex[m] = len(b.TMatchers)
b.TMatchers = append(b.TMatchers, m)
}
for _, m := range s.EnumMatchers {
b.NMatcherIndex[m] = len(b.NMatchers)
b.NMatchers = append(b.NMatchers, m)
}
}
func (b *IntrinsicTableBuilder) newOverloadBuilder(o *sem.Overload) *overloadBuilder {
return &overloadBuilder{
IntrinsicTableBuilder: b,
overload: o,
templateBuilders: map[sem.TemplateParam]*templateBuilder{},
}
}
// processStage0 begins processing of the overload.
// Preconditions:
// - Must be called before any LUTs are compacted.
// Populates:
// - b.templateBuilders
// - b.parameterBuilders
// - b.returnMatcherIndicesOffset
// - b.constEvalFunctionOffset
func (b *overloadBuilder) processStage0() error {
// Calculate the template matcher indices
for _, t := range b.overload.AllTemplates() {
b.templateBuilders[t] = &templateBuilder{matcherIndex: len(b.templateBuilders)}
}
for _, t := range b.overload.AllTemplates() {
switch t := t.(type) {
case *sem.TemplateTypeParam:
if t.Type != nil {
indices, err := b.collectMatcherIndices(*t.Type)
if err != nil {
return err
}
b.templateBuilders[t].constraintIndicesOffset = b.lut.matcherIndices.Add(indices)
}
case *sem.TemplateEnumParam:
if t.Matcher != nil {
index, err := b.matcherIndex(t.Matcher)
if err != nil {
return err
}
b.templateBuilders[t].constraintIndicesOffset = b.lut.matcherIndices.Add([]int{index})
}
}
}
if b.overload.ReturnType != nil {
indices, err := b.collectMatcherIndices(*b.overload.ReturnType)
if err != nil {
return err
}
b.returnMatcherIndicesOffset = b.lut.matcherIndices.Add(indices)
}
b.parameterBuilders = make([]parameterBuilder, len(b.overload.Parameters))
for i, p := range b.overload.Parameters {
matcherIndices, err := b.collectMatcherIndices(p.Type)
if err != nil {
return err
}
b.parameterBuilders[i] = parameterBuilder{
usage: p.Name,
matcherIndicesOffset: b.lut.matcherIndices.Add(matcherIndices),
}
}
if b.overload.ConstEvalFunction != "" {
b.constEvalFunctionOffset = b.lut.constEvalFunctionIndices.Add([]string{b.overload.ConstEvalFunction})
}
return nil
}
// processStage1 builds the Parameters used by the overload
// Must only be called after the following LUTs have been compacted:
// - b.lut.matcherIndices
// Populates:
// - b.templates
// - b.templateOffset
// - b.parametersOffset
func (b *overloadBuilder) processStage1() error {
b.templates = []Template{}
for _, t := range b.overload.AllTemplates() {
b.templates = append(b.templates, Template{
Name: t.GetName(),
Kind: t.TemplateKind(),
MatcherIndicesOffset: loadOrMinusOne(b.templateBuilders[t].constraintIndicesOffset),
})
}
b.templateOffset = b.lut.templates.Add(b.templates)
parameters := make([]Parameter, len(b.parameterBuilders))
for i, pb := range b.parameterBuilders {
parameters[i] = Parameter{
Usage: pb.usage,
MatcherIndicesOffset: loadOrMinusOne(pb.matcherIndicesOffset),
}
}
b.parametersOffset = b.lut.parameters.Add(parameters)
return nil
}
func (b *overloadBuilder) build() (Overload, error) {
return Overload{
NumParameters: len(b.parameterBuilders),
NumExplicitTemplates: len(b.overload.ExplicitTemplates),
NumTemplates: len(b.overload.ExplicitTemplates) + len(b.overload.ImplicitTemplates),
TemplatesOffset: loadOrMinusOne(b.templateOffset),
ParametersOffset: loadOrMinusOne(b.parametersOffset),
ConstEvalFunctionOffset: loadOrMinusOne(b.constEvalFunctionOffset),
ReturnMatcherIndicesOffset: loadOrMinusOne(b.returnMatcherIndicesOffset),
CanBeUsedInStage: b.overload.CanBeUsedInStage,
MustUse: b.overload.MustUse,
MemberFunction: b.overload.MemberFunction,
IsDeprecated: b.overload.IsDeprecated,
Kind: string(b.overload.Decl.Kind),
}, nil
}
// matcherIndex returns the matcher indices into IntrinsicTable.TMatcher and
// IntrinsicTable.NMatcher, respectively for the given named entity.
func (b *overloadBuilder) matcherIndex(n sem.Named) (int, error) {
switch n := n.(type) {
case *sem.Type, *sem.TypeMatcher:
if i, ok := b.TMatcherIndex[n]; ok {
return i, nil
}
return -1, fmt.Errorf("TMatcherIndex missing entry for %v %T", n.GetName(), n)
case *sem.EnumMatcher:
if i, ok := b.NMatcherIndex[n]; ok {
return i, nil
}
return -1, fmt.Errorf("NMatcherIndex missing entry for %v %T", n.GetName(), n)
case sem.TemplateParam:
if b, ok := b.templateBuilders[n]; ok {
return b.matcherIndex, nil
}
return -1, fmt.Errorf("templatesBuilders missing entry for %v %T", n.GetName(), n)
default:
return -1, fmt.Errorf("overload.matcherIndices() does not handle %v %T", n, n)
}
}
// collectMatcherIndices returns the full list of matcher indices required to
// match the fully-qualified-name. For names that have do not have templated
// arguments, collectMatcherIndices() will return a single TMatcher index.
// For names that do have templated arguments, collectMatcherIndices() returns
// a list of type matcher indices, starting with the target of the fully
// qualified name, then followed by each of the template arguments from left to
// right. Note that template arguments may themselves have template arguments,
// and so collectMatcherIndices() may call itself.
// The order of returned matcher indices is always the order of the fully
// qualified name as read from left to right.
// For example, calling collectMatcherIndices() for the fully qualified name:
//
// A<B<C, D>, E<F, G<H>, I>
//
// Would return the matcher indices:
//
// A, B, C, D, E, F, G, H, I
func (b *overloadBuilder) collectMatcherIndices(fqn sem.FullyQualifiedName) ([]int, error) {
base, err := b.matcherIndex(fqn.Target)
if err != nil {
return nil, err
}
indices := []int{base}
for _, arg := range fqn.TemplateArguments {
subIndices, err := b.collectMatcherIndices(arg.(sem.FullyQualifiedName))
if err != nil {
return nil, err
}
indices = append(indices, subIndices...)
}
return indices, nil
}
// BuildIntrinsicTable builds the IntrinsicTable from the semantic info
func BuildIntrinsicTable(s *sem.Sem) (*IntrinsicTable, error) {
b := IntrinsicTableBuilder{
IntrinsicTable: IntrinsicTable{
Sem: s,
TMatcherIndex: map[sem.Named]int{},
NMatcherIndex: map[sem.Named]int{},
},
}
b.layoutMatchers(s)
intrinsicGroups := []struct {
in []*sem.Intrinsic
out *[]Intrinsic
}{
{s.Builtins, &b.Builtins},
{s.UnaryOperators, &b.UnaryOperators},
{s.BinaryOperators, &b.BinaryOperators},
{s.ConstructorsAndConverters, &b.ConstructorsAndConverters},
}
// Create an overload builder for every overload
overloadToBuilder := map[*sem.Overload]*overloadBuilder{}
overloadBuilders := []*overloadBuilder{}
for _, intrinsics := range intrinsicGroups {
for _, f := range intrinsics.in {
for _, o := range f.Overloads {
builder := b.newOverloadBuilder(o)
overloadToBuilder[o] = builder
overloadBuilders = append(overloadBuilders, builder)
}
}
}
// Perform the 'stage-0' processing of the overloads
b.lut.matcherIndices = lut.New[int]()
b.lut.constEvalFunctionIndices = lut.New[string]()
for _, b := range overloadBuilders {
if err := b.processStage0(); err != nil {
return nil, fmt.Errorf("while processing stage 0 of '%v'\n%w", b.overload, err)
}
}
// Clear the compacted LUTs to prevent use-after-compaction
b.MatcherIndices = b.lut.matcherIndices.Compact()
b.ConstEvalFunctions = b.lut.constEvalFunctionIndices.Compact()
b.lut.matcherIndices = nil
b.lut.constEvalFunctionIndices = nil
b.lut.templates = lut.New[Template]()
// Perform the 'stage-1' processing of the overloads
b.lut.parameters = lut.New[Parameter]()
for _, b := range overloadBuilders {
if err := b.processStage1(); err != nil {
return nil, fmt.Errorf("while processing stage 1 of '%v'\n%w", b.overload, err)
}
}
b.Parameters = b.lut.parameters.Compact()
b.Templates = b.lut.templates.Compact()
b.lut.parameters = nil
b.lut.templates = nil
// Build the Intrinsics
b.lut.overloads = lut.New[Overload]()
for _, intrinsics := range intrinsicGroups {
out := make([]Intrinsic, len(intrinsics.in))
for i, f := range intrinsics.in {
overloads := make([]Overload, len(f.Overloads))
overloadDescriptions := make([]string, len(f.Overloads))
for i, o := range f.Overloads {
overloadDescriptions[i] = fmt.Sprint(o.Decl)
overload, err := overloadToBuilder[o].build()
if err != nil {
return nil, err
}
overloads[i] = overload
}
out[i] = Intrinsic{
Name: f.Name,
OverloadDescriptions: overloadDescriptions,
NumOverloads: len(overloads),
OverloadsOffset: b.lut.overloads.Add(overloads),
}
}
*intrinsics.out = out
}
b.Overloads = b.lut.overloads.Compact()
return &b.IntrinsicTable, nil
}
// SplitDisplayName splits displayName into parts, where text wrapped in {}
// braces are not quoted and the rest is quoted. This is used to help process
// the string value of the [[display()]] decoration. For example:
//
// SplitDisplayName("vec{N}<{T}>")
//
// would return the strings:
//
// [`"vec"`, `N`, `"<"`, `T`, `">"`]
func SplitDisplayName(displayName string) []string {
parts := []string{}
pending := strings.Builder{}
for _, r := range displayName {
switch r {
case '{':
if pending.Len() > 0 {
parts = append(parts, fmt.Sprintf(`"%v"`, pending.String()))
pending.Reset()
}
case '}':
if pending.Len() > 0 {
parts = append(parts, pending.String())
pending.Reset()
}
default:
pending.WriteRune(r)
}
}
if pending.Len() > 0 {
parts = append(parts, fmt.Sprintf(`"%v"`, pending.String()))
}
return parts
}
// ElementType returns the nested type for type represented by the fully qualified name.
// If the type is not a composite type, then the fully qualified name is returned
func ElementType(fqn sem.FullyQualifiedName) sem.FullyQualifiedName {
switch fqn.Target.GetName() {
case "vec2", "vec3", "vec4":
return fqn.TemplateArguments[0].(sem.FullyQualifiedName)
case "vec":
return fqn.TemplateArguments[1].(sem.FullyQualifiedName)
case "mat":
return fqn.TemplateArguments[2].(sem.FullyQualifiedName)
case "array":
return fqn.TemplateArguments[0].(sem.FullyQualifiedName)
}
return fqn
}
// DeepestElementType returns the inner most nested type for type represented by the
// fully qualified name.
func DeepestElementType(fqn sem.FullyQualifiedName) sem.FullyQualifiedName {
switch fqn.Target.GetName() {
case "vec2", "vec3", "vec4":
return fqn.TemplateArguments[0].(sem.FullyQualifiedName)
case "vec":
return fqn.TemplateArguments[1].(sem.FullyQualifiedName)
case "mat2x2", "mat2x3", "mat2x4",
"mat3x2", "mat3x3", "mat3x4",
"mat4x2", "mat4x3", "mat4x4":
return DeepestElementType(fqn.TemplateArguments[0].(sem.FullyQualifiedName))
case "mat":
return DeepestElementType(fqn.TemplateArguments[2].(sem.FullyQualifiedName))
case "array":
return DeepestElementType(fqn.TemplateArguments[0].(sem.FullyQualifiedName))
case "ptr":
return DeepestElementType(fqn.TemplateArguments[1].(sem.FullyQualifiedName))
}
return fqn
}
// IsAbstract returns true if the FullyQualifiedName refers to an abstract numeric type float.
// Use DeepestElementType if you want to include vector, matrices and arrays of abstract types.
func IsAbstract(fqn sem.FullyQualifiedName) bool {
switch fqn.Target.GetName() {
case "ia", "fa":
return true
}
return false
}
// IsDeclarable returns false if the FullyQualifiedName refers to an abstract
// numeric type, or if it starts with a leading underscore.
func IsDeclarable(fqn sem.FullyQualifiedName) bool {
return !IsAbstract(DeepestElementType(fqn)) && !strings.HasPrefix(fqn.Target.GetName(), "_")
}
// IsHostShareable returns true if the FullyQualifiedName refers to a type that is host-sharable.
// See https://www.w3.org/TR/WGSL/#host-shareable-types
func IsHostShareable(fqn sem.FullyQualifiedName) bool {
return IsDeclarable(fqn) && DeepestElementType(fqn).Target.GetName() != "bool"
}
// OverloadUsesType returns true if the overload uses the given type anywhere in the signature.
func OverloadUsesType(overload sem.Overload, ty string) bool {
pending := []sem.FullyQualifiedName{}
for _, param := range overload.Parameters {
pending = append(pending, param.Type)
}
if ret := overload.ReturnType; ret != nil {
pending = append(pending, *ret)
}
for len(pending) > 0 {
fqn := pending[len(pending)-1]
pending = pending[:len(pending)-1]
if fqn.Target.GetName() == ty {
return true
}
for _, arg := range fqn.TemplateArguments {
switch arg := arg.(type) {
case sem.FullyQualifiedName:
pending = append(pending, arg)
case sem.Named:
if fqn.Target.GetName() == ty {
return true
}
}
}
}
return false
}
// OverloadUsesReadWriteStorageTexture returns true if the overload uses a read-only or read-write
// storage texture.
func OverloadUsesReadWriteStorageTexture(overload sem.Overload) bool {
for _, param := range overload.Parameters {
if strings.HasPrefix(param.Type.Target.GetName(), "texture_storage") {
access := param.Type.TemplateArguments[1].(sem.FullyQualifiedName).Target.GetName()
if access == "read" || access == "read_write" {
return true
}
}
}
return false
}
func loadOrMinusOne(p *int) int {
if p != nil {
return *p
}
return -1
}