yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Bruce Mitchenerdocs: Reduce typo count (#5671)c3557978c

master
13.4 KiB282 linesraw

Slang Project Coding Conventions

Principles

This document attempts to establish conventions to be used in the Slang codebase. We have two goals for this convention.

The first goal is to make the code look relatively consistent so that it is easy to navigate and understand for contributors. Having varying styles across different modules, files, functions, or lines of code makes the overall design and intention of the codebase harder to follow.

The second goal is to minimize the scope complexity of diffs when multiple maintainers work together on the codebase. In the absence of an enforced style, developers tend to "clean up" code they encounter to match their personal preferences, and in so doing create additional diffs that increase the chances of merge conflicts and pain down the line.

Because the Slang codebase has passed through many hands and evolved without a pre-existing convention, these two goals can come into conflict. We encourage developers to err on the side of leaving well enough alone (favoring the second goal). Don't rewrite or refactor code to match these conventions unless you were already going to have to touch all of those lines of code anyway.

Note that external code that is incorporated into the project is excluded from all of these conventions.

Languages

C++

Most code in the Slang project is implemented in C++. We currently assume support for some C++11 idioms, but have explicitly avoided adding dependencies on later versions.

As a general rule, be skeptical of "modern C++" ideas unless they are clearly better to simpler alternatives. We are not quite in the realm of "Orthodox C++", but some of the same guidelines apply:

  • Don't use exceptions for non-fatal errors (and even then support a build flag to opt out of exceptions)
  • Don't use the built-in C++ RTTI system (home-grown is okay)
  • Don't use the C++ variants of C headers (e.g., <cstdio> instead of <stdio.h>)
  • Don't use the STL containers
  • Don't use iostreams

The compiler implementation does not follow some of these guidelines at present; that should not be taken as an excuse to further the proliferation of stuff like dynamic_cast. Do as we say, not as we do.

Some relatively recent C++ features that are okay to use:

  • Rvalue references for "move semantics," but only if you are implementing performance-critical containers or other code where this really matters.

  • auto on local variables, if the expected type is clear in context

  • Lambdas are allowed, but think carefully about whether just declaring a subroutine would also work.

  • Using >> to close multiple levels of templates, instead of > > (but did you really need all those templates?)

  • nullptr

  • enum class

  • Range-based for loops

  • override

  • Default member initializers in class/struct bodies

Templates are suitable in cases where they improve clarity and type safety. As a general rule, it is best when templated code is kept minimal, and forwards to a non-templated function that does the real work, to avoid code bloat.

Any use of template metaprogramming would need to prove itself exceptionally useful to pay for the increase in cognitive complexity. We don't want to be in the business of maintaining "clever" code.

As a general rule, const should be used sparingly and only with things that are logically "value types." If you find yourself having to const-qualify a lot of member function in type that you expect to be used as a heap-allocated object, then something has probably gone wrong.

As a general rule, default to making the implementation of a type public, and only encapsulate state or operations with private when you find that there are complex semantics or invariants that can't be provided without a heavier hand.

Slang

The Slang project codebase also includes .slang files implementing the Slang core module, as well as various test cases and examples. The conventions described here are thus the "official" recommendations for how users should format Slang code.

To the extent possible, we will try to apply the same basic conventions to both C++ and Slang. In places where we decide that the two languages merit different rules, we will point it out.

Files and Includes

File Names

All files and directories that are added to codebase should have names that contain only ASCII lower-case letters, digits, dots (.) and dashes (-). Operating systems still vary greatly in their handling of case sensitivity for file names, and non-ASCII code points are handled with even less consistency; sticking to a restricted subset of ASCII helps avoids some messy interactions between case-insensitive file systems and case-sensitive source-control systems like Git. As with all these conventions, files from external projects are exempted from these restrictions.

Naming of Source and Header Files

In general the C++ codebase should be organized around logical features/modules/subsystem, each of which has a single .h file and zero or more .cpp files to implement it.

If there is a single .cpp file, its name should match the header: e.g., parser.h and parser.cpp.

If there is more than one .cpp file, their names should start with the header name: e.g., parser.h and parser-decls.cpp and parser-exprs.cpp. If there are declarations that need to be shared by the .cpp files, but shouldn't appear in the public interface, then can go in a *-impl.h header (e.g., parser-impl.h).

Use best judgement when deciding what counts as a "feature." One class per file is almost always overkill, but the codebase currently leans too far in the other direction, with some oversized source files.

Headers

Every header file should have an include guard. Within the implementation we can use #pragma once, but exported API headers (slang.h) should use traditional #ifdef style guards (and they should be consumable as both C and C++).

A header should include or forward-declare everything it needs in order to compile. It is not up to the programmer who #includes a header to sort out the dependencies.

Avoid umbrella or "catch-all" headers.

Source Files

Every source file should start by including the header for its feature/module, before any other includes (this helps ensure that the header correctly includes its dependencies).

Functions that are only needed within that one source file can be marked static, but we should avoid using the same name for functions in different files (in order to support lumped/unified builds).

Includes

In general, includes should be grouped as follows:

  • First, the correspodning feature/module header, if we are in a source file
  • Next, any <>-enlosed includes for system/OS headers
  • Next, any ""-enclosed includes for external/third-part code that is stored in the project repository
  • Finally, any includes for other features in the project

Within each group, includes should be sorted alphabetically. If this breaks because of ordering issues for system/OS/third-party headers (e.g., <windows.h> must be included before <GL/GL.h>), then ideally those includes should be mediated by a Slang-project-internal header that features can include.

Namespaces

Favor fewer namespaces when possible. Small programs may not need any.

All standard module code that a Slang user might link against should go in the Slang namespace for now, to avoid any possibility of clashes in a static linking scenario. The public C API is obviously an exception to this.

Code Formatting

  • For C++ files, please format using clang-format; .clang-format files in the source tree define the style.
  • For CMake files, please format using gersemi
  • For shell scripts, please format using shfmt
  • For YAML files, please use prettier

The formatting for the codebase is overall specified by the extras/formatting.sh script.

If you open a pull request and the formatting is incorrect, you can comment /format and a bot will format your code for you.

Naming

Casing

Types should in general use UpperCamelCase. This includes structs, classes, enums and typedefs.

Values should in general use lowerCamelCase. This includes functions, methods, local variables, global variables, parameters, fields, etc.

Macros should in general use SCREAMING_SNAKE_CASE. It is important to prefix all macros (e.g., with SLANG_) to avoid collisions, since namespaces don't affect macros).

In names using camel case, acronyms and initialisms should appear eniterly in either upper or lower case (e.g., D3DThing d3dThing) and not be capitalized as if they were ordinary words (e.g., D3dThing d3dThing). Note that this also applies to uses of "ID" as an abbreviation for "identifier" (e.g., use nodeID instead of nodeId).

Prefixes

Prefixes based on types (e.g., p for pointers) should never be used.

Global variables should have a g prefix, e.g. gCounter. Non-const static class members can have an s prefix if that suits your fancy. Of course, both of these should be avoided, so this shouldn't come up often.

Constant data (in the sense of static const) should have a k prefix.

In contexts where "information hiding" is relevant/important, such as when a type has both public and private members, or just has certain operations/fields that are considered "implementation details" that most clients should not be using, an m_ prefix on member variables and a _ prefix on member functions is allowed (but not required).

In function parameter lists, an in, out, or io prefix can be added to a parameter name to indicate whether a pointer/reference/buffer is intended to be used for input, output, or both input and output. For example:

void copyData(void* outBuffer, void const* inBuffer, size_t size);

Result lookupThing(Key k, Thing& outThing);

void maybeAppendExtraNames(std::vector<Name>& ioNames);

Public C APIs will prefix all symbol names while following the casing convention (e.g. SlangModule, slangLoadModule, etc.).

Enums

C-style enum should use the following convention:

enum Color
{
    kColor_Red,
    kColor_Green,
    kColor_Blue,

    kColorCount,
};

When using enum class, drop the k and type name as prefix, but retain the UpperCamelCase tag names:

enum class Color
{
    Red,
    Green,
    Blue,

    Count,
};

When defining a set of flags, separate the type definition from the enum:

typedef unsigned int Axes;
enum
{
    kAxes_None = 0,

    kAxis_X = 1 << 0,
    kAxis_Y = 1 << 1,
    kAxis_Z = 1 << 2,

    kAxes_All = kAxis_X | kAxis_Y | kAxis_Z,
};

Note that the type name reflects the plural case, while the cases that represent individual bits are named with a singular prefix.

In public APIs, all enums should use the style of separating the type definition from the enum, and all cases should use SCREAMING_SNAKE_CASE:

typedef unsigned int SlangAxes;
enum
{
    SLANG_AXES_NONE = 0,

    SLANG_AXIS_X = 1 << 0,
    SLANG_AXIS_Y = 1 << 1,
    SLANG_AXIS_Z = 1 << 2,

    SLANG_AXES_ALL = SLANG_AXIS_X | SLANG_AXIS_Y | SLANG_AXIS_Z,
};

General

Names should default to the English language and US spellings, to match the dominant conventions of contemporary open-source projects.

Function names should either be named with action verbs (get, set, create, emit, parse, etc.) or read as questions (isEnabled, shouldEmit, etc.).

Whenever possible, compiler concepts should be named using the most widely-understood term available: e.g., we use Token over Lexeme, and Lexer over Scanner simply because they appear to be the more common names.

Avoid abbreviations and initialisms unless they are already widely established across the codebase; a longer name may be cumbersome to write in the moment, but the code will probably be read many more times than it is written, so clarity should be preferred. An important exception to this is common compiler concepts or techniques which may have laboriously long names: e.g., Static Single Assignment (SSA), Sparse Conditional Copy Propagation (SCCP), etc.

One gotcha particular to compiler front-ends is that almost every synonym for "type" has some kind of established technical meaning; most notably the term "kind" has a precise meaning that is relevant in our domain. It is common practice in C and C++ to define tagged union types with a selector field called a "type" or "kind," which does not usually match this technical definition. If a developer wants to avoid confusion, they are encouraged to use the term "flavor" instead of "type" or "kind" since this term (while a bit silly) is less commonly used in the literature.

Comments and Documentation

You probably know the drill: comments are good, but an out-of-date comment can be worse than no comment at all. Try to write comments that explain the "why" of your code more than the "what." When implementing a textbook algorithm or technique, it may help to imagine giving the reviewer of your code a brief tutorial on the topic.

In cases where comments would benefit from formatting, use Markdown syntax. We do not currently have a setup for extracting documentation from comments, but if we add one we will ensure that it works with Markdown.

When writing comments, please be aware that your words could be read by many people, from a variety of cultures and backgrounds. Default to a plain-spoken and professional tone and avoid using slang, idiom, profanity, etc.

1Slang Project Coding Conventions
2================================
3
4Principles
5----------
6
7This document attempts to establish conventions to be used in the Slang codebase.
8We have two goals for this convention.
9
10The first goal is to make the code look relatively consistent so that it is easy to navigate and understand for contributors.
11Having varying styles across different modules, files, functions, or lines of code makes the overall design and intention of the codebase harder to follow.
12
13The second goal is to minimize the scope complexity of diffs when multiple maintainers work together on the codebase.
14In the absence of an enforced style, developers tend to "clean up" code they encounter to match their personal preferences, and in so doing create additional diffs that increase the chances of merge conflicts and pain down the line.
15
16Because the Slang codebase has passed through many hands and evolved without a pre-existing convention, these two goals can come into conflict.
17We encourage developers to err on the side of leaving well enough alone (favoring the second goal).
18Don't rewrite or refactor code to match these conventions unless you were already going to have to touch all of those lines of code anyway.
19
20Note that external code that is incorporated into the project is excluded from all of these conventions.
21
22Languages
23---------
24
25### C++
26
27Most code in the Slang project is implemented in C++.
28We currently assume support for some C++11 idioms, but have explicitly avoided adding dependencies on later versions.
29
30As a general rule, be skeptical of "modern C++" ideas unless they are clearly better to simpler alternatives.
31We are not quite in the realm of "Orthodox C++", but some of the same guidelines apply:
32
33* Don't use exceptions for non-fatal errors (and even then support a build flag to opt out of exceptions)
34* Don't use the built-in C++ RTTI system (home-grown is okay)
35* Don't use the C++ variants of C headers (e.g., `<cstdio>` instead of `<stdio.h>`)
36* Don't use the STL containers
37* Don't use iostreams
38
39The compiler implementation does not follow some of these guidelines at present; that should not be taken as an excuse to further the proliferation of stuff like `dynamic_cast`.
40Do as we say, not as we do.
41
42Some relatively recent C++ features that are okay to use:
43
44* Rvalue references for "move semantics," but only if you are implementing performance-critical containers or other code where this really matters.
45
46* `auto` on local variables, if the expected type is clear in context
47
48* Lambdas are allowed, but think carefully about whether just declaring a subroutine would also work.
49
50* Using `>>` to close multiple levels of templates, instead of `> >` (but did you really need all those templates?)
51
52* `nullptr`
53
54* `enum class`
55
56* Range-based `for` loops
57
58* `override`
59
60* Default member initializers in `class`/`struct` bodies
61
62Templates are suitable in cases where they improve clarity and type safety.
63As a general rule, it is best when templated code is kept minimal, and forwards to a non-templated function that does the real work, to avoid code bloat.
64
65Any use of template metaprogramming would need to prove itself exceptionally useful to pay for the increase in cognitive complexity.
66We don't want to be in the business of maintaining "clever" code.
67
68As a general rule, `const` should be used sparingly and only with things that are logically "value types."
69If you find yourself having to `const`-qualify a lot of member function in type that you expect to be used as a heap-allocated object, then something has probably gone wrong.
70
71As a general rule, default to making the implementation of a type `public`, and only encapsulate state or operations with `private` when you find that there are complex semantics or invariants that can't be provided without a heavier hand.
72
73### Slang
74
75The Slang project codebase also includes `.slang` files implementing the Slang core module, as well as various test cases and examples.
76The conventions described here are thus the "official" recommendations for how users should format Slang code.
77
78To the extent possible, we will try to apply the same basic conventions to both C++ and Slang.
79In places where we decide that the two languages merit different rules, we will point it out.
80
81Files and Includes
82------------------
83
84### File Names
85
86All files and directories that are added to codebase should have names that contain only ASCII lower-case letters, digits, dots (`.`) and dashes (`-`).
87Operating systems still vary greatly in their handling of case sensitivity for file names, and non-ASCII code points are handled with even less consistency; sticking to a restricted subset of ASCII helps avoids some messy interactions between case-insensitive file systems and case-sensitive source-control systems like Git.
88As with all these conventions, files from external projects are exempted from these restrictions.
89
90### Naming of Source and Header Files
91
92In general the C++ codebase should be organized around logical features/modules/subsystem, each of which has a single `.h` file and zero or more `.cpp` files to implement it.
93
94If there is a single `.cpp` file, its name should match the header: e.g., `parser.h` and `parser.cpp`.
95
96If there is more than one `.cpp` file, their names should start with the header name: e.g., `parser.h` and `parser-decls.cpp` and `parser-exprs.cpp`.
97If there are declarations that need to be shared by the `.cpp` files, but shouldn't appear in the public interface, then can go in a `*-impl.h` header (e.g., `parser-impl.h`).
98
99Use best judgement when deciding what counts as a "feature." One class per file is almost always overkill, but the codebase currently leans too far in the other direction, with some oversized source files.
100
101### Headers
102
103Every header file should have an include guard.
104Within the implementation we can use `#pragma once`, but exported API headers (`slang.h`) should use traditional `#ifdef` style guards (and they should be consumable as both C and C++).
105
106A header should include or forward-declare everything it needs in order to compile.
107It is *not* up to the programmer who `#include`s a header to sort out the dependencies.
108
109Avoid umbrella or "catch-all" headers.
110
111### Source Files
112
113Every source file should start by including the header for its feature/module, before any other includes (this helps ensure that the header correctly includes its dependencies).
114
115Functions that are only needed within that one source file can be marked `static`, but we should avoid using the same name for functions in different files (in order to support lumped/unified builds).
116
117### Includes
118
119In general, includes should be grouped as follows:
120
121* First, the correspodning feature/module header, if we are in a source file
122* Next, any `<>`-enlosed includes for system/OS headers
123* Next, any `""`-enclosed includes for external/third-part code that is stored in the project repository
124* Finally, any includes for other features in the project
125
126Within each group, includes should be sorted alphabetically.
127If this breaks because of ordering issues for system/OS/third-party headers (e.g., `<windows.h>` must be included before `<GL/GL.h>`), then ideally those includes should be mediated by a Slang-project-internal header that features can include.
128
129Namespaces
130----------
131
132Favor fewer namespaces when possible.
133Small programs may not need any.
134
135All standard module code that a Slang user might link against should go in the `Slang` namespace for now, to avoid any possibility of clashes in a static linking scenario.
136The public C API is obviously an exception to this.
137
138
139Code Formatting
140------------------------------
141
142- For C++ files, please format using `clang-format`; `.clang-format` files in
143  the source tree define the style.
144- For CMake files, please format using `gersemi`
145- For shell scripts, please format using `shfmt`
146- For YAML files, please use `prettier`
147
148The formatting for the codebase is overall specified by the
149[`extras/formatting.sh`](./extras/formatting.sh) script.
150
151If you open a pull request and the formatting is incorrect, you can comment
152`/format` and a bot will format your code for you.
153
154Naming
155------
156
157### Casing
158
159Types should in general use `UpperCamelCase`. This includes `struct`s, `class`es, `enum`s and `typedef`s.
160
161Values should in general use `lowerCamelCase`. This includes functions, methods, local variables, global variables, parameters, fields, etc.
162
163Macros should in general use `SCREAMING_SNAKE_CASE`.
164It is important to prefix all macros (e.g., with `SLANG_`) to avoid collisions, since `namespace`s don't affect macros).
165
166In names using camel case, acronyms and initialisms should appear eniterly in either upper or lower case (e.g., `D3DThing d3dThing`) and not be capitalized as if they were ordinary words (e.g., `D3dThing d3dThing`).
167Note that this also applies to uses of "ID" as an abbreviation for "identifier" (e.g., use `nodeID` instead of `nodeId`).
168
169### Prefixes
170
171Prefixes based on types (e.g., `p` for pointers) should never be used.
172
173Global variables should have a `g` prefix, e.g. `gCounter`.
174Non-`const` `static` class members can have an `s` prefix if that suits your fancy.
175Of course, both of these should be avoided, so this shouldn't come up often.
176
177Constant data (in the sense of `static const`) should have a `k` prefix.
178
179In contexts where "information hiding" is relevant/important, such as when a type has both `public` and `private` members, or just has certain operations/fields that are considered "implementation details" that most clients should not be using, an `m_` prefix on member variables and a `_` prefix on member functions is allowed (but not required).
180
181In function parameter lists, an `in`, `out`, or `io` prefix can be added to a parameter name to indicate whether a pointer/reference/buffer is intended to be used for input, output, or both input and output.
182For example:
183
184```c++
185void copyData(void* outBuffer, void const* inBuffer, size_t size);
186
187Result lookupThing(Key k, Thing& outThing);
188
189void maybeAppendExtraNames(std::vector<Name>& ioNames);
190```
191
192Public C APIs will prefix all symbol names while following the casing convention (e.g. `SlangModule`, `slangLoadModule`, etc.).
193
194### Enums
195
196C-style `enum` should use the following convention:
197
198```c++
199enum Color
200{
201    kColor_Red,
202    kColor_Green,
203    kColor_Blue,
204
205    kColorCount,
206};
207```
208
209When using `enum class`, drop the `k` and type name as prefix, but retain the `UpperCamelCase` tag names:
210
211```c++
212enum class Color
213{
214    Red,
215    Green,
216    Blue,
217
218    Count,
219};
220```
221
222When defining a set of flags, separate the type definition from the `enum`:
223
224```c++
225typedef unsigned int Axes;
226enum
227{
228    kAxes_None = 0,
229
230    kAxis_X = 1 << 0,
231    kAxis_Y = 1 << 1,
232    kAxis_Z = 1 << 2,
233
234    kAxes_All = kAxis_X | kAxis_Y | kAxis_Z,
235};
236```
237
238Note that the type name reflects the plural case, while the cases that represent individual bits are named with a singular prefix.
239
240In public APIs, all `enum`s should use the style of separating the type definition from the `enum`, and all cases should use `SCREAMING_SNAKE_CASE`:
241
242```c++
243typedef unsigned int SlangAxes;
244enum
245{
246    SLANG_AXES_NONE = 0,
247
248    SLANG_AXIS_X = 1 << 0,
249    SLANG_AXIS_Y = 1 << 1,
250    SLANG_AXIS_Z = 1 << 2,
251
252    SLANG_AXES_ALL = SLANG_AXIS_X | SLANG_AXIS_Y | SLANG_AXIS_Z,
253};
254```
255
256### General
257
258Names should default to the English language and US spellings, to match the dominant conventions of contemporary open-source projects.
259
260Function names should either be named with action verbs (`get`, `set`, `create`, `emit`, `parse`, etc.) or read as questions (`isEnabled`, `shouldEmit`, etc.).
261
262Whenever possible, compiler concepts should be named using the most widely-understood term available: e.g., we use `Token` over `Lexeme`, and `Lexer` over `Scanner` simply because they appear to be the more common names.
263
264Avoid abbreviations and initialisms unless they are already widely established across the codebase; a longer name may be cumbersome to write in the moment, but the code will probably be read many more times than it is written, so clarity should be preferred.
265An important exception to this is common compiler concepts or techniques which may have laboriously long names: e.g., Static Single Assignment (SSA), Sparse Conditional Copy Propagation (SCCP), etc.
266
267One gotcha particular to compiler front-ends is that almost every synonym for "type" has some kind of established technical meaning; most notably the term "kind" has a precise meaning that is relevant in our domain.
268It is common practice in C and C++ to define tagged union types with a selector field called a "type" or "kind," which does not usually match this technical definition.
269If a developer wants to avoid confusion, they are encouraged to use the term "flavor" instead of "type" or "kind" since this term (while a bit silly) is less commonly used in the literature.
270
271Comments and Documentation
272--------------------------
273
274You probably know the drill: comments are good, but an out-of-date comment can be worse than no comment at all.
275Try to write comments that explain the "why" of your code more than the "what."
276When implementing a textbook algorithm or technique, it may help to imagine giving the reviewer of your code a brief tutorial on the topic.
277
278In cases where comments would benefit from formatting, use Markdown syntax.
279We do not currently have a setup for extracting documentation from comments, but if we add one we will ensure that it works with Markdown.
280
281When writing comments, please be aware that your words could be read by many people, from a variety of cultures and backgrounds.
282Default to a plain-spoken and professional tone and avoid using slang, idiom, profanity, etc.