Merge remote-tracking branch 'tint/main' into HEAD

Integrates Tint repo into Dawn

KIs:
- Building docs for Tint is turned off, because it fails due to lack
  of annotations in Dawn source files.
- Dawn CQ needs to be updated to run Tint specific tests
- Significant post-merge cleanup needed

R=bclayton,cwallez
BUG=dawn:1339

Change-Id: I6c9714a0030934edd6c51f3cac4684dcd59d1ea3
This commit is contained in:
Ryan Harrison
2022-04-06 15:37:27 -04:00
12772 changed files with 839109 additions and 90 deletions

195
docs/tint/arch.md Normal file
View File

@@ -0,0 +1,195 @@
# Tint Architecture
```
┏━━━━━━━━┓ ┏━━━━━━┓
┃ SPIR━V ┃ ┃ WGSL ┃
┗━━━━┃━━━┛ ┗━━━┃━━┛
▼ ▼
┏━━━━━━━━━┃━━━━━━━━━━━━━━━━━━━━━━━━━━━┃━━━━━━━━┓
┃ ┃ Reader ┃ ┃
┃ ┃ ┃ ┃
┃ ┏━━━━━━━┻━━━━━━┓ ┏━━━━━━┻━━━━━━┓ ┃
┃ ┃ SPIRV-Reader ┃ ┃ WGSL-Reader ┃ ┃
┃ ┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┓
┃ ProgramBuilder ┃
┃ (mutable) ┃
┏━━━━━━━━━━━━►┫ ┏━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━┓ ┃
┃ ┃ ┃ AST ┃ ┃ Types ┃ ┃ Symbols ┃ ┃
┃ ┃ ┗━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━┛ ┃
┃ ┗━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛
┃ ▼
┃ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
▲ ┆ Build ▼ ┆
┏━━━┻━━━┓ ┆ ┏━━━━━━━━┻━━━━━━━━┓ ┆
┃ Clone ┃ ┆ ┃ Resolver ┃ ┆
┗━━━┳━━━┛ ┆ ┗━━━━━━━━━━━━━━━━━┛ ┆
▲ └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
┃ ▼
┃ ┏━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃ Program ┃
┃ ┃ (immutable) ┃
┣━━━━━━◄┫ ┏━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━━┓ ┏━━━━━━━━━┓ ┃
┃ ┃ ┃ AST ┃ ┃ Types ┃ ┃ Semantic ┃ ┃ Symbols ┃ ┃
┃ ┃ ┗━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┃
┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛
▲ ▼
┏━━━━━┻━━━━━┓ ┃ ┏━━━━━━━━━━━┓
┃ Transform ┃◄━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━►┃ Inspector ┃
┗━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Writers ┃
┃ ┃
┃ ┏━━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━┓ ┃
┃ ┃ SPIRV-Writer ┃┃ WGSL-Writer ┃┃ HLSL-Writer ┃┃ GLSL-Writer ┃┃ MSL-Writer ┃ ┃
┃ ┗━━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━┛ ┃
┗━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━┛
▼ ▼ ▼ ▼ ▼
┏━━━━┻━━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━┻━━┓
┃ SPIR-V ┃ ┃ WGSL ┃ ┃ HLSL ┃ ┃ GLSL ┃ ┃ MSL ┃
┗━━━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━┛
```
## Reader
Readers are responsible for parsing a shader program and populating a
`ProgramBuilder` with the parsed AST, type and symbol information.
The WGSL reader is a recursive descent parser. It closely follows the WGSL
grammar in the naming of the parse methods.
## ProgramBuilder
A `ProgramBuilder` is the primary interface to construct an immutable `Program`.
There are a number of methods exposed which make creating of the `Program`
simpler. 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 the AST is **structurally** correct. This checks that things
like `if` statements have a condition and body attached.
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.
## AST
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 defined 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::StrideAttribute` node has numeric stride parameter, which is a count of
the number of bytes from the start of one array element to the start of the
next. The AST node itself does not constrain the set of stride values that you
can set, aside from storing it as an unsigned integer.
## Types
Types are constructed during the Reader and resolution phases, and are
held by the `Program` or `ProgramBuilder`. AST and semantic nodes can both
reference types.
Each `type::Type` node **uniquely** represents a particular spelling of a WGSL
type within the program, so you can compare `type::Type*` pointers to check for
equivalence of type expressions.
For example, there is only one `type::Type` node for the `i32` type, no matter
how many times it is mentioned in the source program.
However, if `MyI32` is a type alias for `i32`, then they will have two different
type nodes.
## Semantic information
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.
## Symbols
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.
## Resolver
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.
## Program
A `Program` holds an immutable version of the information from the
`ProgramBuilder` along with semantic information generated by the
`Resolver`.
Like `ProgramBuilder`, `Program::IsValid()` may be called to ensure the AST is
structurally correct and semantically valid, and that the `Resolver` did not
report any errors.
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.
## Inspector
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).
## Transforms
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.
## Writers
A writer is responsible for writing the `Program` in the target shader language.
The input `Program` to a writer must be valid (pass validation).

View File

@@ -0,0 +1,119 @@
# Compound Statements
Compound statements are statements that can hold other statements.
This document maps the WGSL compound statements to their semantic tree representations.
## if statement
WGSL:
```
if (condition_a) {
statement_a;
} else if (condition_b) {
statement_b;
} else {
statement_c;
}
```
Semantic tree:
```
sem::IfStatement {
condition_a
sem::BlockStatement {
statement_a
}
sem::ElseStatement {
condition_b
sem::BlockStatement {
statement_b
}
}
sem::ElseStatement {
sem::BlockStatement {
statement_c
}
}
}
```
## for loop
WGSL:
```
for (initializer; condition; continuing) {
statement;
}
```
Semantic tree:
```
sem::ForLoopStatement {
sem::Statement initializer
sem::Expression condition
sem::Statement continuing
sem::LoopBlockStatement {
sem::Statement statement
}
}
```
## loop
WGSL:
```
loop (condition) {
statement_a;
continuing {
statement_b;
}
}
```
Semantic tree:
```
sem::LoopStatement {
sem::Expression condition
sem::LoopBlockStatement {
sem::Statement statement_a
sem::LoopContinuingBlockStatement {
sem::Statement statement_b
}
}
}
```
## switch statement
WGSL:
```
switch (condition) {
case literal_a, literal_b: {
statement_a;
}
default {
statement_b;
}
}
```
Semantic tree:
```
sem::SwitchStatement {
sem::Expression condition
sem::CaseStatement {
sem::BlockStatement {
sem::Statement statement_a
}
}
sem::CaseStatement {
sem::BlockStatement {
sem::Statement statement_b
}
}
}
```

View File

@@ -0,0 +1,24 @@
# Generating and viewing Tint code-coverage
Requirements:
* Host running Linux or macOS
* Clang toolchain on the `PATH` environment variable
## Building Tint with coverage generation enabled
Follow the steps [to build Tint with CMake](../README.md), but include the additional `-DTINT_EMIT_COVERAGE=1` CMake flag.
## Generate coverage information
Use the `<tint>/tools/tint-generate-coverage` script to run the tint executable or unit tests and generate the coverage information.
The script takes the executable to invoke as the first command line argument, followed by additional arguments to pass to the executable.
For example, to see the code coverage for all unit tests, run:
`<tint>/tools/tint-generate-coverage <build>/tint_unittests --gtest_brief`
The script will emit two files at the root of the tint directory:
* `coverage.summary` - A text file giving a coverage summary for all Tint source files.
* `lcov.info` - A binary coverage file that can be consumed with the [VSCode Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) extension.

View File

@@ -0,0 +1,126 @@
# Tint diagnostic style guide
This guide provides a set of best practices when writing code that emits
diagnostic messages in Tint. These diagnostics are messages presented to the
user in case of error or warning.
The goal of this document is to have our diagnostic messages be clear and
understandable to our users, so that problems are easy to fix, and to try and
keep a consistent style.
## Message style
* Start diagnostic messages with a lower-case letter
* Try to keep the message to a single sentence, if possible
* Do not end the message with punctuation (full stop, exclamation mark, etc)
**Don't:**
```
shader.wgsl:7:1 error: Cannot take the address of expression.
```
**Do:**
```
shader.wgsl:7:1 error: cannot take the address of expression
```
**Justification:**
Succinct messages are more important than grammatical correctness. \
This style matches the style found in most other compilers.
## Prefer to use a `Source` location instead of quoting the code in the message
**Don't:**
```
shader.wgsl:5:7 error: cannot multiply 'expr_a * expr_b' with types i32 and f32
var res : f32 = expr_a * expr_b
^^^^^^^^^^^^^^^
```
**Do:**
```
shader.wgsl:5:7 error: cannot multiply types i32 and f32
var res : f32 = expr_a * expr_b
^^^^^^^^^^^^^^^
```
**Justification:**
The highlighted line provides even more contextual information than the quoted
source, and duplicating this information doesn't provide any more help to the
developer. \
Quoting single word identifiers or keywords from the source is not discouraged.
## Use `note` diagnostics for providing additional links to relevant code
**Don't:**
```
shader.wgsl:5:11 error: type cannot be used in storage class 'storage' as it is non-host-shareable
cond : bool;
^^^^
```
**Do:**
```
shader.wgsl:5:11 error: type cannot be used in storage class 'storage' as it is non-host-shareable
cond : bool;
^^^^
shader.wgsl:8:4 note: while instantiating variable 'StorageBuffer'
var<storage> sb : StorageBuffer;
^^
```
**Justification:**
To properly understand some diagnostics requires looking at more than a single
line. \
Multi-source links can greatly reduce the time it takes to properly
understand a diagnostic message. \
This is especially important for diagnostics raised from complex whole-program
analysis, but can also greatly aid simple diagnostics like symbol collision errors.
## Use simple terminology
**Don't:**
```
shader.wgsl:7:1 error: the originating variable of the left-hand side of an assignment expression must not be declared with read access control.
```
**Do:**
```
shader.wgsl:7:1 error: cannot assign to variable with read access control
x.y = 1;
^^^^^^^
shader.wgsl:2:8 note: read access control declared here
var<storage, read> x : i32;
^^^^
```
**Justification:**
Diagnostics will be read by Web developers who may not be native English
speakers and are unlikely to be familiar with WGSL specification terminology.
Too much technical jargon can be intimidating and confusing. \
Diagnostics should give enough information to explain what's wrong, and most
importantly, give enough information so that a fix actionable.
**Caution:** Be careful to not over simplify. Use the specification terminology
if there's potential ambiguity by not including it.

View File

@@ -0,0 +1,35 @@
# Tint end-to-end tests
This repo contains a large number of end-to-end tests at `<tint>/test`.
## Test files
Test input files have either the `.wgsl`, `.spv` or `.spvasm` file extension.
Each test input file is tested against each of the Tint backends. There are `<number-of-input-files>` &times; `<number-of-tint-backends>` tests that are performed on an unfiltered end-to-end test run.
Each backend test can have an **expectation file**. This expectation file sits next to the input file, with a `<input-file>.expected.<format>` extension. For example the test `test/foo.wgsl` would have the HLSL expectation file `test/foo.wgsl.expected.hlsl`.
An expectation file contains the expected output of Tint, when passed the input file for the given backend.
If the first line of the expectation file starts `SKIP`, then the test will be skipped instead of failing the end-to-end test run. It is good practice to include after the `SKIP` a reason for why the test is being skipped, along with any additional details, such as compiler error messages.
## Running
To run the end-to-end tests use the `<tint>/test/test-all.sh` script, passing the path to the tint executable as the first command line argument.
You can pass `--help` to see the full list of command line flags.\
The most commonly used flags are:
| flag | description |
|----------------------|-------------|
|`--filter` | Filters the testing to subset of the tests. The filter argument is a glob pattern that can include `*` for any substring of a file or directory, and `**` for any number of directories.<br>Example: `--filter 'expressions/**/i32.wgsl'` will test all the `i32.wgsl` expression tests.
|`--format` | Filters the tests to the particular backend.<br>Example: `--format hlsl` will just test the HLSL backend.
|`--generate-expected` | Generate expectation files for the tests that previously had no expectation file, or were marked as `SKIP` but now pass.
|`--generate-skip` | Generate `SKIP` expectation files for tests that are not currently passing.
## Authoring guidelines
Each test should be as small as possible, and focused on the particular feature being tested.
Use sub-directories whenever possible to group similar tests, and try to keep the pattern of directories as consistent as possible between different tests. This helps filter tests using the `--filter` glob patterns.

View File

@@ -0,0 +1,44 @@
# Experimental extensions
Sometimes a language feature proposed for WGSL requires experiementation
to prove its worth. Tint needs to support these, in general to enable
that experimentation.
The steps for doing so are:
1. Choose a name for the feature, to be used in an `enable` directive.
An experimental extension should use prefix of `google_experimental_`
Example:
enable google_experimental_f16;
2. Write down what the feature is supposed to mean.
This informs the Tint implementation, and tells shader authors what
has changed.
Ideally, this will take the form of one of the following:
- A PR against the WGSL spec.
- A description of what the contents of that PR would be, committed
as a document in this Tint repository.
3. File a tracking bug for adding the feature.
Note: Should the Tint repo have a label for experimental features?
4. File a tracking bug for removing the feature or converting it to
non-experimental.
5. Write a plan for removal of the experiment.
- Ideally, this plan is committed to this repository, especially the
description of public activities and commitments. However, we recognize
that some internal goals or metrics may be sensitive, and can be hidden.
- The plan is about process, not technical details. It should include:
- Who is the point of contact for this feature? The point of contact
is responsible when the feature causes an issue or gets in the way.
- What is your target date for declaring the experiment a success or
failure. In Chrome an experiment must be shipped or removed, in
finite time.
- What experience are you hoping to gain? Do you have target metrics?
- What approvals, if any, do you need from W3C? What is your plan to
present your case to W3C?
- The bug tracking removal of the experiment.

View File

@@ -0,0 +1,133 @@
# Tint changes during Origin Trial
## Changes for M102
### New Features
* Parentheses are no longer required around expressions for if and switch statements [tint:1424](crbug.com/tint/1424)
* Compound assignment statements are now supported. [tint:1325](https://crbug.com/tint/1325)
* The colon in case statements is now optional. [tint:1485](crbug.com/tint/1485)
### Breaking changes
* Struct members are now separated by commas. [tint:1475](crbug.com/tint/1475)
* The `@block` attribute has been removed. [tint:1324](crbug.com/tint/1324)
* The `@stride` attribute has been removed. [tint:1381](crbug.com/tint/1381)
* Attributes using `[[attribute]]` syntax are no longer supported. [tint:1382](crbug.com/tint/1382)
* The `elseif` keyword is no longer supported. [tint:1289](crbug.com/tint/1289)
### Deprecated Features
* The `smoothStep()` builtin has been renamed to `smoothstep()`. [tint:1483](crbug.com/tint/1483)
## Changes for M101
### New Features
* Tint now supports unicode identifiers. [tint:1437](crbug.com/tint/1437)
### Breaking changes
* The `isNan()`, `isInf()`, `isFinite()`, and `isNormal()` builtins have been removed. [tint:1312](https://crbug.com/tint/1312)
## Changes for M100
### Breaking changes
* The `@interpolate(flat)` attribute must now be specified on integral user-defined IO. [tint:1224](crbug.com/tint/1224)
* The `ignore()` intrinsic has been removed. Use phoney-assignment instead: `ignore(expr);` -> `_ = expr;`.
* `break` statements in `continuing` blocks are now correctly validated.
### New Features
* Module-scope declarations can now be declared in any order. [tint:1266](crbug.com/tint/1266)
* The `override` keyword and `@id()` attribute for pipeline-overridable constants are now supported, replacing the `@override` attribute. [tint:1403](crbug.com/tint/1403)
## Changes for M99
### Breaking changes
Obviously infinite loops (no condition, no break) are now a validation error.
### Deprecated Features
The following features have been deprecated and will be removed in M102:
* The `[[block]]` attribute has been deprecated. [tint:1324](https://crbug.com/tint/1324)
* Attributes now use the `@decoration` syntax instead of the `[[decoration]]` syntax. [tint:1382](https://crbug.com/tint/1382)
* `elseif` has been replaced with `else if`. [tint:1289](https://crbug.com/tint/1289)
* The `[[stride]]` attribute has been deprecated. [tint:1381](https://crbug.com/tint/1381)
### New Features
* Vector and matrix element type can now be inferred from constructor argument types. [tint:1334](https://crbug.com/tint/1334)
* Added builtins `degrees()` and `radians()` for converting between degrees and radians. [tint:1329](https://crbug.com/tint/1329)
* `let` arrays and matrices can now be dynamically indexed. [tint:1352](https://crbug.com/tint/1352)
* Storage and Uniform buffer types no longer have to be structures. [tint:1372](crbug.com/tint/1372)
* A struct declaration does not have to be followed by a semicolon. [tint:1380](crbug.com/tint/1380)
### Fixes
* Fixed an issue where for-loops that contain array or structure constructors in the loop initializer statements, condition expressions or continuing statements could fail to compile. [tint:1364](https://crbug.com/tint/1364)
## Changes for M98
### Breaking Changes
* Taking the address of a vector component is no longer allowed.
* Module-scope declarations can no longer alias a builtin name. [tint:1318](https://crbug.com/tint/1318)
* It is now an error to call a function either directly or transitively, from a loop continuing block, that uses `discard`. [tint:1302](https://crbug.com/tint/1302)
### Deprecated Features
* The `isNan()`, `isInf()`, `isFinite()` and `isNormal()` builtins has been deprecated and will be removed in M101. [tint:1312](https://crbug.com/tint/1312)
### New Features
* New texture gather builtins: `textureGather()` and `textureGatherCompare()`. [tint:1330](https://crbug.com/tint/1330)
* Shadowing is now fully supported. [tint:819](https://crbug.com/tint/819)
* The `dot()` builtin now supports integer vector types.
* Identifiers can now start with a single leading underscore. [tint:1292](https://crbug.com/tint/1292)
* Control flow analysis has been improved, and functions no longer need to `return` if the statement is unreachable. [tint:1302](https://crbug.com/tint/1302)
* Unreachable statements now produce a warning instead of an error, to allow WGSL code to be updated to the new analysis behavior. These warnings may become errors in the future [gpuweb#2378](https://github.com/gpuweb/gpuweb/issues/2378)
### Fixes
* Fixed an issue where using a module-scoped `let` in a `workgroup_size` may result in a compilation error. [tint:1320](https://crbug.com/tint/1320)
## Changes for M97
### Breaking Changes
* Deprecated `modf()` and `frexp()` builtin overloads that take a pointer second parameter have been removed.
* Deprecated texture builtin functions that accepted a `read` access controlled storage texture have been removed.
* Storage textures must now only use the `write` access control.
### Deprecated Features
* The `ignore()` builtin has been replaced with phony-assignment. [gpuweb#2127](https://github.com/gpuweb/gpuweb/pull/2127)
### New Features
* `any()` and `all()` now support a `bool` parameter. These simply return the passed argument. [tint:1253](https://crbug.com/tint/1253)
* Call statements may now include functions that return a value (`ignore()` is no longer needed).
* The `interpolate(flat)` attribute can now be specified on integral user-defined IO. It will eventually become an error to define integral user-defined IO without this attribute.
* Matrix construction from scalar element values is now supported.
### Fixes
* Swizzling of `vec3` types in `storage` and `uniform` buffers has been fixed for Metal 1.x. [tint:1249](https://crbug.com/tint/1249)
* Calling a function that returns an unused value no longer produces an FXC compilation error. [tint:1259](https://crbug.com/tint/1259)
* `abs()` fixed for unsigned integers on SPIR-V backend
## Changes for M95
### New Features
* The size of an array can now be defined using a non-overridable module-scope constant
* The `num_workgroups` builtin is now supported.
### Fixes
* Hex floats: now correctly errors when the magnitude is non-zero, and the exponent would cause overflow. [tint:1150](https://crbug.com/tint/1150), [tint:1166](https://crbug.com/tint/1166)
* Identifiers beginning with an underscore are now correctly rejected. [tint:1179](https://crbug.com/tint/1179)

View File

@@ -0,0 +1,267 @@
# SPIR-V translation of shader input and output variables
WGSL [MR 1315](https://github.com/gpuweb/gpuweb/issues/1315) changed WGSL so
that pipeline inputs and outputs are handled similar to HLSL:
- Shader pipeline inputs are the WGSL entry point function arguments.
- Shader pipeline outputs are the WGSL entry point return value.
Note: In both cases, a struct may be used to pack multiple values together.
In that case, I/O specific attributes appear on struct members at the struct declaration.
Resource variables, e.g. buffers, samplers, and textures, are still declared
as variables at module scope.
## Vulkan SPIR-V today
SPIR-V for Vulkan models inputs and outputs as module-scope variables in
the Input and Output storage classes, respectively.
The `OpEntryPoint` instruction has a list of module-scope variables that must
be a superset of all the input and output variables that are statically
accessed in the shader call tree.
From SPIR-V 1.4 onward, all interface variables that might be statically accessed
must appear on that list.
So that includes all resource variables that might be statically accessed
by the shader call tree.
## Translation scheme for SPIR-V to WGSL
A translation scheme from SPIR-V to WGSL is as follows:
Each SPIR-V entry point maps to a set of Private variables proxying the
inputs and outputs, and two functions:
- An inner function with no arguments or return values, and whose body
is the same as the original SPIR-V entry point.
- Original input variables are mapped to pseudo-in Private variables
with the same store types, but no other attributes or properties copied.
In Vulkan, Input variables don't have initalizers.
- Original output variables are mapped to pseudo-out Private variables
with the same store types and optional initializer, but no other attributes
or properties are copied.
- A wrapper entry point function whose arguments correspond in type, location
and builtin attributes the original input variables, and whose return type is
a structure containing members correspond in type, location, and builtin
attributes to the original output variables.
The body of the wrapper function the following phases:
- Copy formal parameter values into pseudo-in variables.
- Insert a bitcast if the WGSL builtin variable has different signedness
from the SPIR-V declared type.
- Execute the inner function.
- Copy pseudo-out variables into the return structure.
- Insert a bitcast if the WGSL builtin variable has different signedness
from the SPIR-V declared type.
- Return the return structure.
- Replace uses of the the original input/output variables to the pseudo-in and
pseudo-out variables, respectively.
- Remap pointer-to-Input with pointer-to-Private
- Remap pointer-to-Output with pointer-to-Private
We are not concerned with the cost of extra copying input/output values.
First, the pipeline inputs/outputs tend to be small.
Second, we expect the backend compiler in the driver will be able to see
through the copying and optimize the result.
### Example
```glsl
#version 450
layout(location = 0) out vec4 frag_colour;
layout(location = 0) in vec4 the_colour;
void bar() {
frag_colour = the_colour;
}
void main() {
bar();
}
```
Current translation, through SPIR-V, SPIR-V reader, WGSL writer:
```groovy
@location(0) var<out> frag_colour : vec4<f32>;
@location(0) var<in> the_colour : vec4<f32>;
fn bar_() -> void {
const x_14 : vec4<f32> = the_colour;
frag_colour = x_14;
return;
}
@stage(fragment)
fn main() -> void {
bar_();
return;
}
```
Proposed translation, through SPIR-V, SPIR-V reader, WGSL writer:
```groovy
// 'in' variables are now 'private'.
var<private> frag_colour : vec4<f32>;
var<private> the_colour : vec4<f32>;
fn bar_() -> void {
// Accesses to the module-scope variables do not change.
// This is a big simplifying advantage.
const x_14 : vec4<f32> = the_colour;
frag_colour = x_14;
return;
}
fn main_inner() -> void {
bar_();
return;
}
// Declare a structure type to collect the return values.
struct main_result_type {
@location(0) frag_color : vec4<f32>;
};
@stage(fragment)
fn main(
// 'in' variables are entry point parameters
@location(0) the_color_arg : vec4<f32>
) -> main_result_type {
// Save 'in' arguments to 'private' variables.
the_color = the_color_arg;
// Initialize 'out' variables.
// Use the zero value, since no initializer was specified.
frag_color = vec4<f32>();
// Invoke the original entry point.
main_inner();
// Collect outputs into a structure and return it.
var result : main_outer_result_type;
result.frag_color = frag_color;
return result;
}
```
Alternately, we could emit the body of the original entry point at
the point of invocation.
However that is more complex because the original entry point function
may return from multiple locations, and we would like to have only
a single exit path to construct and return the result value.
### Handling fragment discard
In SPIR-V `OpKill` causes immediate termination of the shader.
Is the shader obligated to write its outputs when `OpKill` is executed?
The Vulkan fragment operations are as follows:
(see [6. Fragment operations](https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#fragops)).
* Scissor test
* Sample mask test
* Fragment shading
* Multisample coverage
* Depth bounds test
* Stencil test
* Depth test
* Sample counting
* Coverage reduction
After that, the fragment results are used to update output attachments, including
colour, depth, and stencil attachments.
Vulkan says:
> If a fragment operation results in all bits of the coverage mask being 0,
> the fragment is discarded, and no further operations are performed.
> Fragments can also be programmatically discarded in a fragment shader by executing one of
>
> OpKill.
I interpret this to mean that the outputs of a discarded fragment are ignored.
Therefore, `OpKill` does not require us to modify the basic scheme from the previous
section.
The `OpDemoteToHelperInvocationEXT`
instruction is an alternative way to throw away a fragment, but which
does not immediately terminate execution of the invocation.
It is introduced in the [`SPV_EXT_demote_to_helper_invocation](http://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/master/extensions/EXT/SPV_EXT_demote_to_helper_invocation.html)
extension. WGSL does not have this feature, but we expect it will be introduced by a
future WGSL extension. The same analysis applies to demote-to-helper. When introduced,
it will not affect translation of pipeline outputs.
### Handling depth-replacing mode
A Vulkan fragment shader must write to the fragment depth builtin if and only if it
has a `DepthReplacing` execution mode. Otherwise behaviour is undefined.
We will ignore the case where the SPIR-V shader writes to the `FragDepth` builtin
and then discards the fragment.
This is justified because "no further operations" are performed by the pipeline
after the fragment is discarded, and that includes writing to depth output attachments.
Assuming the shader is valid, no special translation is required.
### Handling output sample mask
By the same reasoning as for depth-replacing, it is ok to incidentally not write
to the sample-mask builtin variable when the fragment is discarded.
### Handling clip distance and cull distance
Most builtin variables are scalars or vectors.
However, the `ClipDistance` and `CullDistance` builtin variables are arrays of 32-bit float values.
Each entry defines a clip half-plane (respectively cull half-plane)
A Vulkan implementation must support array sizes of up to 8 elements.
How prevalent are shaders that use these features?
These variables are supported when Vulkan features `shaderClipDistance` and `shaderCullDistance`
are supported.
According to gpuinfo.org as of this writing, those
Vulkan features appear to be nearly universally supported on Windows devices (>99%),
but by only 70% on Android.
It appears that Qualcomm devices support them, but Mali devices do not (e.g. Mali-G77).
The proposed translation scheme forces a copy of each array from private
variables into the return value of a vertex shader, or into a private
variable of a fragment shader.
In addition to the register pressure, there may be a performance degradation
due to the bulk copying of data.
We think this is an acceptable tradeoff for the gain in usability and
consistency with other pipeline inputs and outputs.
## Translation scheme for WGSL AST to SPIR-V
To translate from the WGSL AST to SPIR-V, do the following:
- Each entry point formal parameter is mapped to a SPIR-V `Input` variable.
- Struct and array inputs may have to be broken down into individual variables.
- The return of the entry point is broken down into fields, with one
`Output` variable per field.
- In the above, builtins must be separated from user attributes.
- Builtin attributes are moved to the corresponding variable.
- Location and interpolation attributes are moved to the corresponding
variables.
- This translation relies on the fact that pipeline inputs and pipeline
outputs are IO-shareable types. IO-shareable types are always storable,
and can be the store type of input/output variables.
- Input function parameters will be automatically initialized by the system
as part of setting up the pipeline inputs to the entry point.
- Replace each return statement in the entry point with a code sequence
which writes the return value components to the synthesized output variables,
and then executes an `OpReturn` (without value).
This translation is sufficient even for fragment shaders with discard.
In that case, outputs will be ignored because downstream pipeline
operations will not be performed.
This is the same rationale as for translation from SPIR-V to WGSL AST.

115
docs/tint/spirv-ptr-ref.md Normal file
View File

@@ -0,0 +1,115 @@
# SPIR-V translation of WGSL pointers and references
WGSL was updated to have two kinds of memory views: pointers and references.
See https://github.com/gpuweb/gpuweb/pull/1569
In summary:
* Reference types are never explicitly mentioned in WGSL source.
* A use of a variable is a value of reference type corresponding
to the reference memory view of the storage allocated for the
variable.
* Let-declared constants can be of pointer type, but not reference
type.
* Function parameter can be of pointer type, but not reference type.
* A variable's store type is never a pointer type, and never a
reference type.
* The "Load Rule" allows a reference to decay to the underlying
store type, by issuing a load of the value in the underlying memory.
* For an assignment:
* The right-hand side evaluates to a non-reference type (atomic-free
plain type).
* The left-hand side evaluates to a reference type, whose store
type is the same as the result of evaluating the right hand side.
* The address-of (unary `&`) operator converts a reference to a
pointer.
* The dereference (unary `*`) operator converts a pointer to a
reference.
TODO: Passing textures and samplers to helper functions might be
done by "handler value", or by pointer-to-handle.
## Writing SPIR-V from WGSL
The distinction in WGSL between reference and pointer disappears
at the SPIR-V level. Both types map into pointer types in SPIR-V.
To translate a valid WGSL program to SPIR-V:
* The dereference operator (unary `*`) is the identity operation.
* The address-of operator (unary `&`) is the identity operation.
* Assignment maps to OpStore.
* The Load Rule translates to OpLoad.
## Reading SPIR-V to create WGSL
The main changes to the SPIR-V reader are:
* When translating a SPIR-V pointer expression, track whether the
corresponding WGSL expression is of corresponding WGSL pointer
type or correspoinding WGSL type.
* Insert dereference (unary-`*`) or address-of (unary-`&`) operators
as needed to generate valid WGSL expressions.
The choices can be made deterministic, as described below.
The SPIR-V reader only supports baseline functionality in Vulkan.
Therefore we assume no VariablePointers or VariablePointersStorageBuffer
capabilities. All pointers are
[SPIR-V logical pointers](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#LogicalPointerType).
The [SPIR-V Universal Validation Rules](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_universal_validation_rules)
specify where logical pointers can appear as results of instructions
or operands of instructions.
Each SPIR-V pointer result expression is a logical pointer, and
therefore is one of:
* OpVariable: map to the reference type.
* OpFunctionParameter: map to the pointer type.
* OpCopyObject:
* When these only have one use, then these often fold away.
Otherwise, they map to a a let-declared constant.
* Map to the pointer type.
* OpAccessChain, OpInBoundsAccessChain:
* This could map to either pointer or reference, and adjustments
in other areas could make it work. However, we recommend mapping
this to the reference type.
* OpImageTexelPointer is not supported in WGSL.
It is used to get a pointer into a storage texture, for use with
atomic instructions. But image atomics is not supported in
WebGPU/WGSL.
Each SPIR-V pointer operand is also a logical pointer, and is an
operand to one of:
* OpLoad Pointer operand:
* Map to reference, inserting a dereference operator if needed.
* OpStore Pointer operand:
* Map to reference, inserting a dereference operator if needed.
* OpStore Pointer operand:
* OpAccessChain, OpInBoundsAccessChain Base operand:
* WGSL array-access and subfield access only works on references.
* [Gpuweb issue 1530](https://github.com/gpuweb/gpuweb/issues/1530)
is filed to allow those operations to work on pointers.
* Map to reference, inserting a dereference operator if needed.
* OpFunctionCall function argument pointer operands
* Function operands can't be references.
* Map to pointer, inserting an address-of operator if needed.
* OpAtomic instruction Pointer operand
* These map to WGSL atomic builtins.
* Map to pointer, inserting an address-of operator if needed.
* Note: As of this writing, the atomic instructions are not supported
by the SPIR-V reader.
* OpCopyObject source operand
* This could have been mapped either way, but it's easiest to
map to pointer, to match the choice for OpCopyObject result type.
* Map to pointer, inserting an address-of operator if needed.
* OpCopyMemory, source and destination operands
* This acts as an assignment.
* Map both source and destination to reference, inserting dereference
operators if needed.
* Note: As of this writing, OpCopyMemory is not supported by the
SPIR-V reader.
* Extended instruction set instructions Modf and Frexp
* These map to builtins.
* Map the pointer operand to pointer, inserting an address-of
operator if needed.

47
docs/tint/style_guide.md Normal file
View File

@@ -0,0 +1,47 @@
# Tint style guide
* Generally, follow the [Chromium style guide for C++](https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++.md)
which itself is built on the [Google C++ style guide](https://google.github.io/styleguide/cppguide.html).
* Overall try to use the same style and convention as code around your change.
* Code must be formatted. Use `clang-format` with the provided [.clang-format](../.clang-format)
file. The `tools/format` script runs the formatter.
* Code should not have linting errors.
The `tools/lint` script runs the linter. So does `git cl upload`.
* Do not use C++ exceptions
* Do not use C++ RTTI.
Instead, use `tint::Castable::As<T>()` from
[src/castable.h](../src/castable.h)
* Generally, avoid `assert`. Instead, issue a [diagnostic](../src/diagnostic.h)
and fail gracefully, possibly by returning an error sentinel value.
Code that should not be reachable should call `TINT_UNREACHABLE` macro
and other internal error conditions should call the `TINT_ICE` macro.
See [src/debug.h](../src/debug.h)
* Use `type` as part of a name only when the name refers to a type
in WGSL or another shader language processed by Tint. If the concept you are
trying to name is about distinguishing between alternatives, use `kind` instead.
## Compiler support
Tint requires C++17.
Tint uses the Chromium build system and will stay synchronized with that system.
Compiler configurations beyond that baseline is on a best-effort basis.
We strive to support recent GCC and MSVC compilers.
## Test code
We might relax the above rules rules for test code, since test code
shouldn't ship to users.
However, test code should still be readable and maintainable.
For test code, the tradeoff between readability and maintainability
and other factors is weighted even more strongly toward readability
and maintainability.

187
docs/tint/translations.md Normal file
View File

@@ -0,0 +1,187 @@
# Translations
This document attempts to document how WGSL translates into the various backends
for the cases where the translation is not a direct mapping.
# Access Control
## HLSL
* ReadOnly -> `ByteAddressBuffer`
* ReadWrite -> `RWByteAddressBuffer`
## MSL
* ReadOnly -> `const`
## SPIR-V
There are two ways this can be achieved in SPIR-V. Either the variable can be
decorated with `NonWritable` or each member of the struct can be decorated with
`NonWritable`. We chose to go the struct member route.
* The read-only becomes part of the type in this case. Otherwise, you are
treating the readonly type information as part of the variable which is
confusing.
* Treating the readonly as part of the variable means we should be
deduplicating the types behind the access control, which causes confusing
with the type_names and various tracking systems within Tint.
# Builtin Decorations
| Name | SPIR-V | MSL | HLSL |
|------|--------|-----|------|
| position | SpvBuiltInPosition |position | SV_Position |
| vertex_index | SpvBuiltInVertexIndex |vertex_id | SV_VertexID |
| instance_index | SpvBuiltInInstanceIndex | instance_id| SV_InstanceID |
| front_facing | SpvBuiltInFrontFacing | front_facing | SV_IsFrontFacing |
| frag_coord | SpvBuiltInFragCoord | position | SV_Position |
| frag_depth | SpvBuiltInFragDepth | depth(any) | SV_Depth |
| local_invocation_id | SpvBuiltInLocalInvocationId | thread_position_in_threadgroup | SV_GroupThreadID |
| local_invocation_index | SpvBuiltInLocalInvocationIndex | thread_index_in_threadgroup | SV_GroupIndex |
| global_invocation_id | SpvBuiltInGlobalInvocationId | thread_position_in_grid | SV_DispatchThreadID |
# Builtins Methods
| Name | SPIR-V | MSL | HLSL |
| ------|--------|-----|------ |
| abs | GLSLstd450FAbs or GLSLstd450SAbs| fabs or abs | abs |
| acos | GLSLstd450Acos | acos | acos |
| all | SpvOpAll | all | all |
| any | SpvOpAny | any | any |
| arrayLength | SpvOpArrayLength | | |
| asin | GLSLstd450Asin | asin | asin |
| atan | GLSLstd450Atan | atan | atan |
| atan2 | GLSLstd450Atan2| atan2 | atan2 |
| ceil | GLSLstd450Ceil| ceil | ceil |
| clamp | GLSLstd450NClamp or GLSLstd450UClamp or GLSLstd450SClamp| clamp | clamp |
| cos | GLSLstd450Cos | cos | cos |
| cosh | GLSLstd450Cosh | cosh | cosh |
| countOneBits | SpvOpBitCount | popcount | countbits |
| cross | GLSLstd450Cross | cross | cross |
| determinant | GLSLstd450Determinant | determinant | determinant |
| distance | GLSLstd450Distance | distance | distance |
| dot | SpOpDot | dot | dot |
| dpdx | SpvOpDPdx | dpdx | ddx |
| dpdxCoarse | SpvOpDPdxCoarse | dpdx | ddx_coarse |
| dpdxFine | SpvOpDPdxFine | dpdx | ddx_fine |
| dpdy | SpvOpDPdy | dpdy | ddy |
| dpdyCoarse | SpvOpDPdyCoarse | dpdy | ddy_coarse |
| dpdyFine | SpvOpDPdyFine | dpdy | ddy_fine |
| exp | GLSLstd450Exp | exp | exp |
| exp2 | GLSLstd450Exp2 | exp2 | exp2 |
| faceForward | GLSLstd450FaceForward | faceforward | faceforward |
| floor | GLSLstd450Floor | floor | floor |
| fma | GLSLstd450Fma | fma | fma |
| fract | GLSLstd450Fract | fract | frac |
| frexp | GLSLstd450Frexp | | |
| fwidth | SpvOpFwidth | fwidth | fwidth |
| fwidthCoarse | SpvOpFwidthCoarse | fwidth | fwidth |
| fwidthFine | SpvOpFwidthFine | fwidth | fwidth |
| inverseSqrt | GLSLstd450InverseSqrt | rsqrt | rsqrt |
| ldexp | GLSLstd450Ldexp | | |
| length | GLSLstd450Length | length | length |
| log | GLSLstd450Log | log | log |
| log2 | GLSLstd450Log2 | log2 | log2 |
| max | GLSLstd450NMax or GLSLstd450SMax or GLSLstd450UMax | fmax or max | max |
| min | GLSLstd450NMin or GLSLstd450SMin or GLSLstd450UMin | fmin or min | min |
| mix | GLSLstd450FMix | mix | mix |
| modf | GLSLstd450Modf | | |
| normalize | GLSLstd450Normalize | normalize | normalize |
| pow | GLSLstd450Pow | pow | pow |
| reflect | GLSLstd450Reflect | reflect | reflect |
| reverseBits | SpvOpBitReverse | reverse_bits | reversebits |
| round | GLSLstd450Round | round | round |
| select | SpvOpSelect | select | |
| sign | GLSLstd450FSign | sign | sign |
| sin | GLSLstd450Sin | sin | sin |
| sinh | GLSLstd450Sinh | sinh | sinh |
| smoothStep | GLSLstd450SmoothStep | smoothstep | smoothstep |
| sqrt | GLSLstd450Sqrt | sqrt | sqrt |
| step | GLSLstd450Step | step | step |
| tan | GLSLstd450Tan | tan | tan |
| tanh | GLSLstd450Tanh | tanh | tanh |
| trunc | GLSLstd450Trunc | trunc | trunc |
# Types
## Sampler Types
| WGSL | SPIR-V | MSL | HLSL |
|------|--------|-----|------|
| sampler | OpTypeSampler | sampler | SamplerState |
| sampler_comparison | OpTypeSampler | sampler | SamplerComparisonState |
## Texture Types
| WGSL | SPIR-V | MSL | HLSL |
|------|--------|-----|------|
| texture_1d&lt;type&gt; | OpTypeImage 1D Sampled=1 | texture1d&lt;type, access::sample&gt; | Texture1D |
| texture_2d&lt;type&gt; | OpTypeImage 2D Sampled=1 | texture2d&lt;type, access::sample&gt; | Texture2D |
| texture_2d_array&lt;type&gt; | OpTypeImage 2D Arrayed=1 Sampled=1 | texture2d_array&lt;type, access::sample&gt; | Texture2DArray |
| texture_3d&lt;type&gt; | OpTypeImage 3D Sampled=1 | texture3d&lt;type, access::sample&gt; | Texture3D |
| texture_cube&lt;type&gt; | OpTypeImage Cube Sampled=1 | texturecube&lt;type, access::sample&gt; | TextureCube |
| texture_cube_array&lt;type&gt; | OpTypeImage Cube Arrayed=1 Sampled=1 | texturecube_array&lt;type, access::sample&gt; | TextureCubeArray |
| | | |
| texture_multisampled_2d&lt;type&gt; | OpTypeImage 2D MS=1 Sampled=1 | texture2d_ms&lt;type, access::sample&gt; | Texture2D |
| | | |
| texture_depth_2d | OpTypeImage 2D Depth=1 Sampled=1 | depth2d&lt;float, access::sample&gt;| Texture2D |
| texture_depth_2d_array | OpTypeImage 2D Depth=1 Arrayed=1 Sampled=1 | depth2d_array&lt;float, access::sample&gt; | Texture2DArray |
| texture_depth_cube | OpTypeImage Cube Depth=1 Sampled=1 | depthcube&lt;float, access::sample&gt; | TextureCube |
| texture_depth_cube_array | OpTypeImage Cube Depth=1 Arrayed=1 Sampled=1 | depthcube_array&lt;float, access::sample&gt; | TextureCubeArray |
| texture_depth_multisampled_2d | OpTypeImage 2D Depth=1 MS=1 Sampled=1 | depth2d&lt;float, access::sample&gt;| Texture2DMSArray |
| | | |
| texture_storage_1d&lt;image_storage_type&gt; | OpTypeImage 1D Sampled=2| texture1d&lt;type, access::read&gt; | RWTexture1D |
| texture_storage_2d&lt;image_storage_type&gt; | OpTypeImage 2D Sampled=2 | texture2d&lt;type, access::read&gt; | RWTexture2D |
| texture_storage_2d_array&lt;image_storage_type&gt; | OpTypeImage 2D Arrayed=1 Sampled=2 | texture2d_array&lt;type, access::read&gt; | RWTexture2DArray |
| texture_storage_3d&lt;image_storage_type&gt; | OpTypeImage 3D Sampled=2 | texture3d&lt;type, access::read&gt; | RWTexture3D |
| | | |
| texture_storage_1d&lt;image_storage_type&gt; | OpTypeImage 1D Sampled=2 | texture1d&lt;type, access::write&gt; | RWTexture1D |
| texture_storage_2d&lt;image_storage_type&gt; | OpTypeImage 2D Sampled=1 | texture2d&lt;type, access::write&gt; | RWTexture2D |
| texture_storage_2d_array&lt;image_storage_type&gt; | OpTypeImage 2D Arrayed=1 Sampled=2 | texture2d_array&lt;type, access::write&gt; | RWTexture2DArray |
| texture_storage_3d&lt;image_storage_type&gt; | OpTypeImage 3D Sampled=2 | texture3d&lt;type, access::write&gt; | RWTexture3D|
# Short-circuting
## HLSL
TODO(dsinclair): Nested if's
## SPIR-V
TODO(dsinclair): Nested if's
# Storage classes
TODO(dsinclair): do ...
# Storage buffers
## HLSL
TODO(dsinclair): Rewriting of accessors to loads
# Loop blocks
## HLSL
TODO(dsinclair): Rewrite with bools
## MSL
TODO(dsinclair): Rewrite with bools
# Input / Output storage class
## HLSL
TODO(dsinclair): Structs and params
## MSL
TODO(dsinclair): Structs and params
# Discard
## HLSL
* `discard`
## MSL
* `discard_fragment()`
# Specialization constants
## HLSL
```
#ifndef WGSL_SPEC_CONSTANT_<id>
-- if default provided
#define WGSL_SPEC_CONSTANT_<id> default value
-- else
#error spec constant required for constant id
--
#endif
static const <type> <name> = WGSL_SPEC_CONSTANT_<id>
```
## MSL
`@function_constant(<id>)`