┏━━━━━━━━┓ ┏━━━━━━┓ ┃ SPIR━V ┃ ┃ WGSL ┃ ┗━━━━┃━━━┛ ┗━━━┃━━┛ ▼ ▼ ┏━━━━━━━━━┃━━━━━━━━━━━━━━━━━━━━━━━━━━━┃━━━━━━━━┓ ┃ ┃ Reader ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┏━━━━━━━┻━━━━━━┓ ┏━━━━━━┻━━━━━━┓ ┃ ┃ ┃ SPIRV-Reader ┃ ┃ WGSL-Reader ┃ ┃ ┃ ┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛ ▼ ┏━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┓ ┃ ProgramBuilder ┃ ┃ (mutable) ┃ ┏━━━━━━━━━━━━►┫ ┏━━━━━┓ ┏━━━━━━━━━┓ ┃ ┃ ┃ ┃ AST ┃ ┃ Symbols ┃ ┃ ┃ ┃ ┗━━━━━┛ ┗━━━━━━━━━┛ ┃ ┃ ┗━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛ ┃ ▼ ┃ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ ▲ ┆ Resolve ▼ ┆ ┏━━━┻━━━┓ ┆ ┏━━━━━━━━┻━━━━━━━━┓ ┆ ┃ Clone ┃ ┆ ┃ Resolver ┃ ┆ ┗━━━┳━━━┛ ┆ ┗━━━━━━━━━━━━━━━━━┛ ┆ ▲ └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘ ┃ ▼ ┃ ┏━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━┓ ┃ ┃ Program ┃ ┃ ┃ (immutable) ┃ ┣━━━━━━◄┫ ┏━━━━━┓ ┏━━━━━━━━━━┓ ┏━━━━━━━━━┓ ┃ ┃ ┃ ┃ AST ┃ ┃ Semantic ┃ ┃ Symbols ┃ ┃ ┃ ┃ ┗━━━━━┛ ┗━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛ ▲ ▼ ┏━━━━━┻━━━━━┓ ┃ ┏━━━━━━━━━━━┓ ┃ Transform ┃◄━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━►┃ Inspector ┃ ┗━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━┛ ▼ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Writers ┃ ┃ ┃ ┃ ┏━━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━┓ ┃ ┃ ┃ SPIRV-Writer ┃┃ WGSL-Writer ┃┃ HLSL-Writer ┃┃ GLSL-Writer ┃┃ MSL-Writer ┃ ┃ ┃ ┗━━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━┛ ┃ ┗━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━┛ ▼ ▼ ▼ ▼ ▼ ┏━━━━┻━━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━┻━━┓ ┃ SPIR-V ┃ ┃ WGSL ┃ ┃ HLSL ┃ ┃ GLSL ┃ ┃ MSL ┃ ┗━━━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━┛
Readers are responsible for parsing a shader program and populating a ProgramBuilder
with the parsed AST and symbol information.
The WGSL reader is a recursive descent parser. It closely follows the WGSL grammar in the naming of the parse methods.
A ProgramBuilder
is the interface to construct an immutable Program
. There are a large number of helper methods for simplifying the creation of the AST nodes. A ProgramBuilder
can only be used once, and must be discarded after the Program
is constructed.
A Program
is built from the ProgramBuilder
by std::move()
ing the ProgramBuilder
to a new Program
object. When built, resolution is performed so the produced Program
will contain all the needed semantic information.
At any time before building the Program
, ProgramBuilder::IsValid()
may be called to ensure that no error diagnostics have been raised during the construction of the AST. This includes parser syntax errors, but not semantic validation which happens during the Resolve
phase.
If further changes to the Program
are needed (say via a Transform
) then a new ProgramBuilder
can be produced by cloning the Program
into a new ProgramBuilder
.
Unlike Program
s, ProgramBuilder
s are not part of the public Tint API.
The Abstract Syntax Tree is a directed acyclic graph of ast::Node
s which encode the syntactic structure of the WGSL program.
The root of the AST is the ast::Module
class which holds each of the declared functions, variables and user declared types (type aliases and structures).
Each ast::Node
represents a single part of the program's source, and so ast::Node
s are not shared.
The AST does not perform any verification of its content. For example, the ast::Array
node has numeric size parameter, which is not validated to be within the WGSL specification limits until validation of the Resolver
.
Semantic information is held by sem::Node
s which describe the program at a higher / more abstract level than the AST. This includes information such as the resolved type of each expression, the resolved overload of a builtin function call, and the module scoped variables used by each function.
Semantic information is generated by the Resolver
when the Program
is built from a ProgramBuilder
.
The sem::Info
class holds a map of ast::Node
s to sem::Node
s. This map is many-to-one - i.e. while a AST node might have a single corresponding semantic node, the reverse may not be true. For example: many ast::IdentifierExpression
nodes may map to a single sem::Variable
, and so the sem::Variable
does not have a single corresponding ast::Node
.
Unlike ast::Node
s, semantic nodes may not necessarily form a directed acyclic graph, and the semantic graph may contain diamonds.
AST types are regular AST nodes, in that they uniquely represent a single part of the parsed source code. Unlike semantic types, identical AST types are not de-duplicated as they refer to the source usage of the type.
Semantic types are constructed during the Resolver
phase, and are held by the Program
or ProgramBuilder
.
Each sem::Type
node uniquely represents a particular WGSL type within the program, so you can compare type::Type*
pointers to check for type equivalence. For example, a Program
will only hold one instance of the sem::I32
semantic type, no matter how many times an i32
is mentioned in the source program.
WGSL type aliases resolve to their target semantic type. For example, given:
type MyI32 = i32; const a : i32 = 1; const b : MyI32 = 2;
The semantic types for the variables a
and b
will both be the same sem::I32
node pointer.
Symbols represent a unique string identifier in the source program. These string identifiers are transformed into symbols within the Reader
s.
During the Writer phase, symbols may be emitted as strings using a Namer
. A Namer
may output the symbol in any form that preserves the uniqueness of that symbol.
The Resolver
will automatically run when a Program
is built. A Resolver
creates the Program
s semantic information by analyzing the Program
s AST and type information.
The Resolver
will validate to make sure the generated Program
is semantically valid.
A Program
holds an immutable version of the information from the ProgramBuilder
along with semantic information generated by the Resolver
.
Program::IsValid()
may be called to ensure the program is structurally correct and semantically valid, and that the Resolver
did not report any errors during validation.
Unlike the ProgramBuilder
, a Program
is fully immutable, and is part of the public Tint API. The immutable nature of Program
s make these entirely safe to share between multiple threads without the use of synchronization primitives.
The inspectors job is to go through the Program
and pull out various pieces of information. The information may be used to pass information into the downstream compilers (things like specialization constants) or may be used to pass into transforms to update the AST before generating the resulting code.
The input Program
to the inspector must be valid (pass validation).
There maybe various transforms we want to run over the Program
. This is for things like Vertex Pulling or Robust Buffer Access.
A transform operates by cloning the input Program
into a new ProgramBuilder
, applying the required changes, and then finally building and returning a new output Program
. As the resolver is always run when a Program
is built, Transforms will always emit a Program
with semantic information.
The input Program
to a transform must be valid (pass validation). If the input Program
of a transform is valid then the transform must guarantee that the output program is also valid.
A writer is responsible for writing the Program
in the target shader language.
The input Program
to a writer must be valid (pass validation).