| Commit message (Collapse) | Author | Age |
| ... | |
| |\ |
|
| | | |
|
| | | |
|
| | | |
|
| |/ |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is the first step towards supporting traditional object-oriented method definitions; the second step will be to allow `this` expressions to be implicit.
- Add a test case using explicit `this`, and expected output
- Update parsing logic for expressions so that it handled identifiers similarly to the declaration and statement logic: first try to parse using a syntax declaration looked up in the curent scope, and otherwise fall back to the ordinary `VarExpr` case.
* As long as I'm making that change: switch `true` and `false` to be parsed via the callback mechanism rather than be special-cased.
* This change will also help out if we ever wanted to add `super`/`base` expressions, `new`, `sizeof`/`alignof` or any other expression keywords.
- Add a `ThisExpr` node and register a parser callback for it.
- Add semantic checks for `ThisExpr`: basically just look upwards through scopes until we find either an aggregate type declaration or an `extension` declaration, and then use that as the type of the expression.
- TODO: eventually we need to guard against a `this` expression inside of a `static` member.
- The IR generation logic already handled creation of `this` parameters in function signatures; the missing piece was to register the appropriate parameter in the context, so that we can use it as the lowering of a `this` expression.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This change includes a lot of infrastructure work, but the main point is to allow code like the following:
```
// define an interface
interface Helper { float help(); }
// define a generic function that uses the interface
float test<T : Helper>( T t ) { return t.help(); }
// define a type that implements the interface
struct A : Helper { float help() { return 1.0 } }
// define an ordinary function that calls the
// generic function with a concrete type:
float doIt()
{
A a;
return test<A>(a);
}
```
Getting this to generate valid code involves a lot of steps. This change includes the initial version of all of these steps, but leaves a lot of gaps where more complete implementation is required.
The changes include:
- Member lookup on types has been centralized, and now handles the case where the type we are looking for a member in is a generic parameter (e.g., given `t.help()` we can now look up `help` in `Helper` by knowing that `t` is a `T` and `T` conforms to `Helper`).
- There is an obvious cleanup still to be done here where the same exact logic should be used to look up available "constructor" declarations inside a type when the type is used like a function.
- Add a notion of subtype constraint "wittnesses" to the type system. When a generic is declared as taking `<T : Helper>` it really takes two generic parameters: the type `T` and a proof that `T` conforms to `Helper`. The actual arguments to a generic will then include both the type argument and a suitable witness argument (both type-level values).
- As it stands right now, a witness wraps a `DeclRef` to the declaration that represents the appropriate subtype relationship. So if we have `struct A : Helper`, that `: Helper` part turns into an `InheritanceDecl` member, and a reference to that member can serve as a witness to the fact that `A` conforms to `Helper`.
- Make explicit generic application `G<A,B>` synthesize the additional arguments that represent conformances required by the generic.
- This does *not* yet deal with the case where a generic is implicitly specialized as part of an ordinary call `G(a,b)`
- A bug fix to not auto-specialize generics during lookup. The problem here was related to an attempted fix of an earlier issue.
During checking of a method nested in a generic type, we were running into problems where `DeclRefType::create()` was getting called on an un-specialized reference to `vector`, and this was leading to a crash when the code looked for the arguments for the generic. This was worked around by having name lookup automatically specialize any generics it runs into while going through lookup contexts.
That choice creates the problem that in a generic method like this:
```
void test<T>(T val) { ... }
```
any reference to `val` inside the body of `test` will end up getting specialized so that it is effectively `test<T>::val`, when that isn't really needed.
- Add front-end logic to check that when a type claims to conform to an interface it actually must provide the methods required by the interface. The checking process goes ahead and builds a front-end "witness table" that maps declarations in the interface being conformed to over to their concrete implementations for the type.
- At the moment the checking is completely broken and bad: it assumes that *any* member with the right name is an appropriate declaration to satisfy a requirement. That obviously needs to be fixed.
- Add an explicit operation to the IR for lookup of methods: `lookup_interface_method(w, r)` where `w` is a reference to the "witness" value and `r` is an `IRDeclRef` for the member we want to look up.
- Add an explicit notion of witness tables to the IR. These end up being the IR representation of an `InheritanceDecl` in a type, and they are generated by enumerating the members that satisfy the interface requirements (which were handily already enumerated by the front-end checking). The witness table is an explicit IR value, and so it will be referenced/used at the site where conformance is being exploited (e.g., as part of a `specialize` call), so it should be safe to eliminate witness tables that are unused (since they represent conformances that aren't actually exploited). Similarly, the entries in a witness table are uses of the functions that implement interface methods, and so keep those live.
- In order to implement the above, I did a bit of a cleanup pass on the IR representation so that there is an `IRUser` base that `IRInst` inherits from, so that we can have users of values that aren't instructions.
- One annoying thing is that because of how types and generics are handled in the IR, we needed a way to have a type-level `Val` that wraps an IR-level value: e.g., to allow an IR-level witness table to be used as one of the arguments for specialization of a generic. The design I chose here is to have a "proxy" `Val` subclass (`IRProxyVal`) that wraps an `IRValue*`. These should only ever appear as part of types and `DeclRef`s that are used by the IR.
- One annoying bit here is that an IR value might then have a use that is not manifest in the set of IR instructions, and instead only appears as part of a type somewhere.
- I'm not 100% happy with this design, but it seems like we'd have to tackle similar issues if/when we eventually allow functions to have `constexpr` or `@Constant` parameters
- Make generic specialization also propagate witness table arguments through to their use sites (this is mostly just the existing substitution machinery, once we have `IRProxyVal`), and then include logic to specialize `lookup_interface_method` instructions when their first operand is a concrete witness table.
All of this work allows a single limited test using generics with constraints to pass, but more work is needed to make the solution robust.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* Checkpoint: interface conformance work
- Add explicit definition of `saturate` for the GLSL target, which calls through to `clamp`
- Needed to add explicit initializer to `__BuiltinFloatingPointType` to allow initialization from a single `float`, so that the `saturate` implementation can be sure that it can initialize a `T` from `0.0` or `1.0`.
- This triggered errors in overload resolution, because the logic in place could not figure out that the `T` of the outer generic (`saturate<T>()`) conformed to the interface required by the callee.
At this point I have the call to the scalar `clamp()` getting past type-checking, but not the vector or matrix cases.
* More fixups for overload resolution inside generics
- Make sure value parameters are treated the same as type parameters: we only want to solve for the parameters of the generic actually being applied, and not accidentally generate constraints for outer generics (e.g., when checking the body of a generic function).
- Make sure that the diagnostics stuff uses the correct source manager when expanding the location of a builtin.
* Fixes for function redeclaration
- Handle case of redeclaring a generic function
- Enumerate siblings in the parent of the *generic* not the parent of the *function*
- Add logic to compare generic signatures
- When generic signatures match, specialize functions to compatible generic arguments before comparing the function signatures
- Fix redeclaration logic to *not* detect prefix/postifx operators as redeclarations of one another
- Build an explicit representation of function redeclaration groups
- First declaration is the "primary" and others are stored in a linked list
- Make overload resolution handle redeclared functions
- Only consider the primary declaration and skip others
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
We were accidentally parsing this:
(uint) a / b
as this:
(uint) ( a / b )
when it should be:
((uint) a) / b
This is a bug that seems to have been inherited from a long time ago. It has taken a while to bite anybody because the only class of expressions it would hit are multiplicative ones, and in many cases the difference in the cast order won't be noticed for values in a limited range.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* First attempt at a Linux build
- Fix up places where C++ idioms were written assuming lenient behavior of Microsoft's compiler
- Add a few more alternatives for platform-specific behavior where Windows was the only platform accounted for.
- Add a basic Makefile that can at least invoke our build, even if it isn't going good dependency tracking, etc.
- Build `libslang.so` and `slangc` that depends on it, using a relative `RPATH` to make the binary portable (I hope)
- Add an initial `.travis.yml` to see if we can trigger their build process.
* Fixup: const bug in `List::Sort`
I'm not clear why this gets picked up by the gcc *and* clang that Travis uses, but not the (newer) gcc I'm using on Ubuntu here, but I'm hoping it is just some missing `const` qualifiers.
* Fixup: reorder specialization of "class info"
Clang complains about things being specialized after being instantiated (implicilty), and I hope it is just the fact that I generate the class info for the roots of the hierarchy after the other cases. We'll see.
* Fixup: add `platform.cpp` to unified/lumped build
* Fixup: Windows uses `FreeLibrary`
and not `UnloadLibrary`
* Fixup: fix Windows project file to include new source file
This obviously points to the fact that we are going to need to be generating these files sooner or later.
|
| |
|
|
|
|
| |
The main interesting change here is around support for lowering of calls to "subscript" operations (what a C++ programmer would think of as `operator[]`).
An important infrastructure change here was to add an explicit AST-node representation for a "static member expression" which we use whenever a member is looked up in a type as opposed to a value. The implementation of this probably isn't robust yet, but it turns out to be important to be able to tell such cases apart.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The code previously had an enumerated type for "intrinsic" operations, and allowed functions to be marked `__intrinsic_op(...)` to indicate the operation they map to.
The nature of the IR meant that each of these intrinsic ops had to have a corresponding IR opcode, but the `enum` types weren't the same.
This change cleans things up a bit by deciding that the `__intrinsic_op(...)` modifier names an actual IR opcode, and so the `IntrinsicOp` enum is gone.
The biggest source of complexity here is that there are certain operations that need to be "intrinsic"-ish for the purposes of the current AST-based translation path, because we need them to round-trip from source to AST and back.
Right now this is being handled by defining a bunch of "pseudo-ops" which can be used in the `__intrinsic_op` modifier, but which are *not* meant to be represented in the IR.
Currently I don't actually handle this during IR generation.
In the long run, once we are using IR for everything that needs cross-compilation, we should be able to eliminate the pseudo-ops in favor of just having these be ordinary (inline) functions defined in the stdlib (e.g., the `+=` operator can just have a direct definition).
There was a second category of modifier that gets a little caught up in this, which is the `__intrinsic` modifier, which got used in two ways:
1. A function marked `__intrinsic(glsl, ...)` had what I call a "target intrinsic" modifier, which specified how to lower it for a specific target (e.g., GLSL).
2. A function just marked `__intrinsic` was supposed to be a marker for "this function shouldn't be emitted in the output, because the implementation is expected to be provided"
The latter category of function should really be an `__intrinsic_op`, so I translated all those uses. I added a tiny bit of sugar so that `__intrinsic_op` without an explicit opcode will look up an opcode based on the name of the function being called, so that an operation like `sin` can automatically be plumbed through to an equivalent IR op. (The first category is a stopgap for the AST-based cross-compilation, and will hopefully be replaced by something better as we get the IR-based path working).
Getting the switch from `__intrinsic` to `__intrinsic_op` working required shuffling around some code in `emit.cpp` that handles looking up those modifiers and emitting builtin operations appropriately during cross-compilation.
Depending on where we go with things, a possible extension of this approach is to allow multiple operands to `__intrinsic_op` so that the first specifies the opcode, and then the rest are literal arguments to specify "sub-ops." This could help us handle stuff like texture-fetch operations without an explosion in the number of opcodes. I still need to think about whether this is a good idea or not.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This gets us far enough that we can convert a single test case to use the IR, under the new `-use-ir` flag.
Getting this merged into mainline will at least ensure that we keep the IR path working in a minimal fashion, even when we have to add functionality the existing AST-based path
There is definitely some clutter here from keeping both IR-based and AST-based translation around, but I don't want to have a long-lived branch for the IR that gets further and further away from the `master` branch that is actually getting used and tested.
Summary of changes:
- Add pointer types and basic `load` operation to be able to handle variable declarations
- Add basic `call` instruction type
- Add simple address math for field reference in l-value
- Always add IR for referenced decls to global scope
- Add notion of "intrinsic" type modifier, which maps a type declaration directly to an IR opcode (plus optional literal operands to handle things like texture/sampler flavor)
- Improve printing of IR instructions, types, operands
- Add constant-buffer type to IR
- Allow any instruction to be detected as "should be folded into use sites" and use this to tag things of constant-buffer type
- Also add logic for implicit base on member expressions, to handle references to `cbuffer` members
- Add connection back to original decl to IR variables (including global shader parameters...)
- Use reflection name instead of true name when emitting HLSL from IR (so that we can match HLSL output)
- Make IR include decorations for type layout
- Re-use existing emit logic for HLSL semantics to output `register` semantics for IR-based code
- Make IR-based codegen be an option we can enable from the command line
- It still isn't on by default (it can barely manage a trivial shader), but it seems better to enable it always instead of putting it under an `#ifdef`
- Fix up how we check for intrinsic operations suring AST-based cross compilation so that adding new intrinsic ops for the IR won't break codegen.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Previously, there were a variety of rules in `check.cpp` to pick the conversion cost for various cases involving scalar, vector, and matrix types.
- The main problem of the previous approach is that any lowering pass would need to convert an arbitrary "type cast" node into the right low-level operation(s).
- The new approach is that a type conversion (implicit or explicit) always resolves as a call to a constructor/initializer for the destination type. This means that the existing rules around marking operations as builtins should work for lowering.
- The support this, the checking logic needs to perform lookup of intializers/constructors when asked to perform conversion between types. It does this by re-using the existing logic for lookup and overload resolution if/when a type was applied in an ordinary context.
- Next, we define a modifier that can be attached to constructors/initializers to mark them as suitable for implicit conversion, and associate them with the correct cost to be used when doing overload comparisons.
- We add the modifier to all the scalar-to-scalar cases in the stdlib, using the logic that previously existed in semantic checking.
- Next we add cases for general vector-to-scalar conversions that also convert type, using the same cost computation as above.
- This probably misses various cases, but at this point they can hopefully be added just in the stdlib.
- One gotcha here is that in lowering, we need to make sure to lower any kind of call expression to another call expression of the same AST node class, so that we don't lose information on what casts were implicit/hidden in teh source-to-source case.
Two notes for potential longer-term changes:
1. There is still some duplication between the type conversion declarations here and the "join" logic for types used for generic arguments. Ideally we'd eventually clean up the "join" logic to be based on convertability, but that isn't a high priority right now, as long as joins continue to pick the right type.
2. It is a bit gross to have to declare all the N^2 conversions for vector/matrix types to duplicate the cases for scalars. For the simple scalar-to-vector case, we might try to support multiple conversion "steps" where both a scalar-to-scalar and a scalar-to-vector step can be allowed (this could be tagged on the modifiers already introduced). That simple option doesn't scale to vector-to-vector element type conversions, though, where you'd really want to make it a generic with a constraint like:
vector<T,N> init<U>(vector<U,N> value) where T : ConvertibleFrom<U>;
Here the `ConvertibleFrom<U>` interface expresses the fact that a conforming type has an initializer that takes a `U`. What doesn't appear in this context is any notion of conversion costs. We'd need some kind of system for computing the conversion cost of the vector conversion from the cost of the `T` to `U` converion.
|
| |
|
|
|
|
| |
I changed the logic so that it might splice a new modifier into the existing linked list (not just at the end), but failed to account for the case where what we are splicing in isn't just a single modifier, but a whole *list* (which occurs when splicing in the shared modifiers themselves).
This change handles that case with a bit of annoying linked-list cleverness.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Fixes #171
Fixes #172
These two bugs related to bad logic in handling of splitting resource-containing `cbuffer` declarations.
- Issue #171 was the case where a `cbuffer` *only* had resource fields, in which case we crashed whenever referencing any field (some code was assuming there had to be non-resource fields)
- Issue #172 was a case where two fields were declared with a single declaration (`Texture2D a, b;`), and the logic we had for tracking resource-type fields was accidentally tagging *both* fields with a single modifier so that field `b` would get confused for `a` in some contexts, and attempts to access `b` would crash.
Both issues are now fixed, and regression tests have been added.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Fixes #23
Up to this point, the compiler has used the ordinary `String` type to represent declaration names, which means a bunch of lookup structures throughout the compiler were string-to-whatever maps, which can reduce efficiency.
It also means that things like the `Token` type end up carying a `String` by value and paying for things like reference-counting.
This change adds a `Name` type that is used to represent names of variables, types, macros, etc.
Names are cached and unique'd globally for a session, and the string-to-name mapping gets done during lexing.
From that point on, most mapping is from pointers, which should make all the various table lookups faster.
More importantly (possibly), this brings us one step closer to being able to pool-allocate the AST nodes.
|
| |
|
|
| |
This is in preparation for using `Name` as a type name.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Just like the previous change did for declaration keywords, this change uses the lexical environment to drive the lookup and dispatch of modifier parsing.
This allows us to easily add modifiers to Slang, even when they might conflict with identifiers used in user code (because the modifier names are no longer special keywords, but ordinary identifiers).
There was already some support for ideas like this with `__modifier` declarations (`ModifierDecl`) used to introduce some GLSL-specific keywords (so that they wouldn't pollute the namespace of HLSL files).
The new approach changes these to be actual `syntax` declarations (`SyntaxDecl`) with the same representation as those used to introduce declaration keywords.
Because many modifiers just introduce a single keyword that maps to a simple AST node (no further tokens/data), I modified the handling of syntax declarations so that they can take a user-data parameter, and this allows the common case ("just create an AST node of this type...") to be handled with minimal complications.
This also adds in a general-purpose string-based lookup path for AST node classes, that should support programmatic creation in more cases.
Statements are now the main case of keywords that need to be made table driven.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The existing parser code was doing string-based matching on the lookahead token to figure out how to parse a declaration, e.g.:
```
if(lookAhead == "struct") { /* do struct thing */ }
else if(lookAhead == "interface") { /* do interface thing * }
...
```
That approach has some annoying down-sides:
- It is slower than it needs to be
- It is annoying to deal with cases where the available declaration keywords might differ by language
- Most importantly, it is not possible for us to introduce "extended" keywords that the user can make use of, but which can be ignored by the user and treated as an ordinary identifier.
That last part is important. Suppose the user wanted to have a local variable named `import`, but we also had a Slang extension that added an `import` keyword. Then a line of code like `import += 1` would lead to a failure because we'd try to parse an import declaration, even when it is obvious that the user meant their local variable. This would mean that Slang can't parse existing user code that might clash with syntax extensions. This issue is the reason why we currently have keywords like `__import`.
A traditional solution in a compiler is to map keywords to distinct token codes as part of lexing, which eliminates the first conern (performance) because now we can dispatch with `switch`. It can also aleviate the second concern if we add/remove names from the string->code mapping based on language (the rest of the parsing logic doesn't have to know about keywords being added/removed).
The solution we go for here is more aggressive.
Instead of mapping keyword names to special token codes during lexing, we instead introduce logical "syntax declarations" into the AST, which are looked up using the ordinary scoping rules of the language.
Depending on what code is imported into the scope where parsing is going on, different keywords may then be visible.
This solves our last concern, since a user-defined variable that just happens to use the same name as a keyword is now allowed to shadow the imported declaration for syntax (this is akin to, e.g., Scheme where there really aren't any "keywords").
This also opens the door to the possibility of eventually allowing user to define their own syntax (again, like Scheme).
For now I'm only using this for the declaration keywords.
With this change it should be pretty easy to also add statement keywords in the same fashion.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fixes #24
So far the code has used a representation for source locations that is heavy-weight, but typical of research or hobby compilers: a `struct` type containing a line number and a (heap-allocated) string.
This is actually very convenient for debugging, but it means that any data structure that might contain a source location needs careful memory management (because of those strings) and has a tendency to bloat.
The new represnetation is that a source location is just a pointer-sized integer.
In the simplest mental model, you can think of this as just counting every byte of source text that is passed in, and using those to name locations.
Finding the path and line number that corresponds to a location involves a lookup step, but we can arrange to store all the files in an array sorted by their start locations, and do a binary search.
Finding line numbers inside a file is similarly fast (one you pay a one-time cost to build an array of starting offsets for lines).
More advanced compilers like clang actually go further and create a unique range of source locations to represent a file each time it gets included, so that they can track the include stack and reproduce it in diagnostic messages.
I'm not doing anything that clever here.
|
| |
|
|
|
|
|
|
|
|
| |
- `ExpressionSyntaxNode` becomes `Expr`
- `StatementSyntaxNode` becomes `Stmt`
- `StructSyntaxNode` becomes `StructDecl`
- `ProgramSyntaxNode` becomes `ModuleDecl`
- `ExpressionType` becomes `Type`
- Existing fields names `Type` become `type`
- There might be some collateral damage here if there were, e.g., `enum`s named `Type`, but I can live with that for now and fix those up as a I see them
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
There were two main places where global variables were used in the Slang implementation:
1. The "standard library" code was generated as a string at run-time, and stored in a global variable so that it could be amortized across compiles.
2. The representation of types uses some globals (well, class `static` members) to store common types (e.g., `void`) and to deal with memory lifetime for things like canonicalized types.
In each case the "simple" fix is to move the relevant state into the `Session` type that controlled their lifetime already (the `Session` destructor was already cleaning up these globals to avoid leaks).
For the standard library stuff this really was easy, but for the types it required threading through the `Session` a bit carefully.
One more case that I found: there was a function-`static` variable used to generate a unique ID for files output when dumping of intermediates is enabled (this is almost strictly a debugging option).
Rather than make this counter per-session (which would lead to different sessions on different threads clobbering the same few files), I went ahead and used an atomic in this case.
Note that the remaining case I had been worried about was any function-`static` counter that might be used in generating unique names.
It turns out that right now the parser doesn't use such a counter (even in cases where it probably should), and the lowering pass already uses a counter local to the pass (again, whether or not this is a good idea).
This change should be a major step toward allowing an application to use Slang in multiple threads, so long as each thread uses a distinct `SlangSession`. The case of using a single session across multiple threads is harder to support, and will require more careful implementation work.
|
| |
|
|
|
|
|
| |
The change is mostly about trying to make sure the compiler "fails safe" when it encounters an internal assumption that isn't met.
Most internal errors will now throw exceptions (yes, exceptions are evil, but this will work for now), and these get caught in `spCompile` so that they don't propagate to the user (they just see a message that compilation aborted due to an internal error).
Subsequent changes are going to need to work on diagnosing as many of these situations as possible, so that users can at least know what construct in their code was unexpected or unhandled by the compiler.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Fixes #122
- In cases with an explicit mip level being specified, there was a mistake in how the argument for setting the mip level in the GLSL code was constructed that led to a parse error in GLSL
- Also, that argument is a `uint` in HLSL and an `int` in GLSL, so an explicit cast was needed
- The GLSL functions here seem to require a newer GLSL (at least higher than `420`), so I had to add in a capability for builtins to specify a required GLSL version. For now I made these ones require `450`.
- Added a test case to confirm that our lowering works (for some definition of "works")
|
| |
|
|
|
|
|
|
|
|
|
| |
We generate implicit names for global-scope parameter blocks (including HLSL `cbuffer`s, since the "name" the user sees is really just for reflection purposes), but this had a few problems:
- We used the generated names for parameter-binding purposes
- Except for a GLSL block with an explicit name, in which case we'd use the internal name and not the reflection name for matching
- The generated named didn't match between GLSL and HLSL/Slang declarations
This change tries to fix both of these issues. I changed the name generation to try to make it identical between HLSL and GLSL (to the extent we can control it), just in case. But then I also went and changed the parameter-binding-generation logic to use the *reflection* name instead of the internal name when deciding if things are the "same" parameter.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The basic syntax is:
$for(i in Range(0,99))
{
/* stuff goes here */
}
Note that the exact form is very restrictive. All that you are allowed to change is `i`, `0`, `99` or `/* stuff goes here */`.
As a tiny bit of syntax sugar, the following should work:
$for(i in Range(99))
{
/* stuff goes here */
}
Note that the range given is half-open (C++ iterator `[begin,end)` style).
Both the beginning and end of the range must be compile-time constant expressions that Slang knows how to constant-fold.
The implementation will basically generate code for `/* stuff goes here */` N times, once for each value in the half-open range.
Each time, the variable `i` will be replaced with a different compile-time-constant expression.
While I was working on a test case for this, I also found that our build of glslang had an issue with resource limits, so I fixed that.
Clients will need to build a new glslang to use the fix.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Fixes #15
These are the modifiers like:
layout(local_size_x = 16) in;
Unlike the HLSL case, these don't get attache to the entry point function itself, so there is a bit more work involed in looking them up.
Just to make sure I didn't mess up the HLSL case, I went ahead and added two tests for this capability: one for GLSL and one for HLSL.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Fixes #12
- This was a latent issue, but the previous commit brought it to the front.
- As indicated in #12, I don't allocate a descriptor-table slot to the block
- Instead I allocate a `PushConstantBuffer`
- Unlike what #12 asks for, I don't use a different resource type for the contents of the block
- Pretty much all the logic is easiest if these continue to be just plain `Uniform` data
|
| |
|
|
|
|
| |
When cross-compiling, we need to detect when an intrinsic is used that required non-default GLSL capabilities and emit an appropriate `#extension ... : require` line.
I'm handling this by attaching a custom modifier to declarations that require an extension in order to be callable.
|
| |
|
|
|
|
| |
Fixes #75
In order to avoid cascaded errors, I went ahead and made the parser refuse to skip past a `}` in recovery mode. The problem with this is that we fail to make forward progress if we are stuck on a `}` (this happens if you have an extra `}` at the global scope.
|
| |
|
|
|
|
|
| |
If the user doesn't use any `import` declarations, there is no reason to parse their code at all, so having the option of falling back to `UnparsedStmt` can potentially save us some headaches down the road.
The new rule now is that if you have the "no checking" flag on, *and* the parser hasn't yet seen any `import` declarations, then it still used `UnparsedStmt` to avoid touching function bodies.
Otherwise, I go ahead and parse function bodies, and assume I can rewrite any code I can semantically understand.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
HLSL has the bad scoping behavior for `for` loops, and we need to respect that.
But, we need to have correct scoping for GLSL, and we'd like it for Slang.
We also need to ensure that `for` loops written in a "correct" language get the correct behavior when emitted as HLSL.
There was already code to handle this in the emit pass, but it was unfortunately using an `isRewrite` flag to try to tell if the HLSL behavior was wanted.
This doesn't work when the code being emitted might come from a mix of languages.
This change adds a distinct `UnscopedForStmt` syntax node type, and uses that when parsing HLSL input (bot not for other languages).
We make sure to preserve this node type through lowering, and then specialize our emit logic on this case.
With this, there are no more remaining uses of `isRewrite` in the emit logic, which is good because it didn't mean what I needed it to mean any more (since we now emit only a single module, that was merged during lowering).
|
| |
|
|
|
|
| |
The old approach used an `isRewriter` flag in the emit logic, but I kind of need that flag to go away.
Instead, I now how the semantic checking pass detect whether an implicitly-generated type cast is in rewriter code, and if so it uses the new `HiddenImplicitCastExpr` AST node.
The emit logic then looks for that specific node and eliminates it.
|
| |
|
|
|
|
|
|
|
|
|
| |
This is in anticipation of needing to have more complete knowledge to be able to handle user code that `import`s library functionality.
The big picture of this change is just to remove the `UnparsedStmt` class that was used to hold the bodies of user functions as opaque token streams, and thus to let the full parser and compiler loose on that code. That is the easy part, of course, and the hard part is all the fixes that this requires in the rest of the compielr to make this even remotely work.
Subsequent commit address a lot of other issues, so this particular commit mostly represents work-in-progress.
One detail is that this change puts a conditional around nearly every diagnostic message in `check.cpp` to suppress thing when in rewriter mode.
I have yet to check how that works out if there are errors in anything we actually need to understand for the purposes of generating reflection data.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- The big change here is the introduction of a "lowering" pass that takes an input AST from the semantic checker, and produces an output AST suitable for emitting. The intention is that he lowering pass is responsible for:
- Stripping out unused code (when we have enough information to do so), by only outputting declarations that are transitively references from an entry point
- When cross-compiling to GLSL, generating a suitable `void main()` entry point to wrap the user-written entry-point function
- (Eventually) legalizing types in the program, by scalarizing aggregate types that mix uniform and resource types
- (Eventually) instantiating generic declarations so that the resulting code only deals with fully specialized declarations
- (Eventually) de-sugaring OOP constructs into basic "structs and functions" form
- (Eventually) instantiating code that depends on interface types at the concrete types chosen
- It is clear that there is still a lot of work to be done there, to this change is really about getting infrastructure in place without breaking the existing test cases.
- One cleanup here is that we get rid of the idea of whole-translation-unit output, since that was specific to HLSL output, and there is really no strong reason for keeping it. Users should now just ask for the output for each entry point that they wanted to generate.
- The biggest source of complexity for the lowering process is that it needs to produce the same AST structure as the input, to deal with the complexity of the rewriter case. That is, we need the output to be able to reproduce the input exactly in the case where we are rewriting and nothing needs to change, so the output format needs at least the degrees of freedom of the input.
- As a result, we end up having to distinguish "rewriter" and "full" modes in both lowering and code-emit steps, so that we can react appropriately.
- Generating a GLSL `main()` also adds a lot of complexity. Right now I'm using the simplest approach, where we always output the Slang/HLSL entry point as an ordinary function (as written) and then emit a simple GLSL `main()` to call it. I generate globals for all the shader inputs/outputs (these need to be scalarized and have explicit `location`s attached), and then collect these into the `struct` types of the original parameters as needed.
- This approach will start to have some major down-sides once we have to deal with "arrayed" input/output
- A long-term question here is how to replace entry-point parameter types with scalarized and/or "transposed" versions, while still letting the original code work as written (including copying those inputs to temporary arrays)
- Split `BlockStatementSyntaxNode` into:
- `BlockStmt` which just provides a scope around a `body` statement
- `SeqStmt` which just allows multiple statements to be treated as one
- Change how we emit `for` loops, to deal with the case where the initialization part might expand into multiple statements
- Basically `for(A;B;C) {D}` becomes `{A; for(;B;C) {D}}`, so we can handle arbitrary statements for `A`
- As an additional wrinkle, when we are rewriting HLSL, we just generate `A; for(;B;C) {D}` to deal with the broken scoping there
- This change is needed because the lowering pass was sometimes expanding the original initialization statement `A` into a block `{A}`. Certainly if it declared multiple variables we'd need to handle it, and this seemed the easiest way
- A more significant challenge for lowering would come if/when we ever wanted to support true short-circuiting behavior for `&&` and `||`
- For right now I'm not changing the behavior of the "rewriter" mode, so we still have `UnparsedStmt` instances being generated, but it is clear that eventually we need to parse *all* input, even if we can't type-check 100% of it. This is required so that we can rewrite user code that might refer to a shader input with interface type.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- `RefPtr` no longer tries to have distinct cases for interal-vs-external reference counts. Instead we always require an internal reference count.
- Types the used `RefPtr` but weren't `RefObject` were made to inherit `RefObject`
- The `ReferenceCounted` base class was removed, so that only `RefObject` remains
- Implicit conversion from `RefPtr<T>` to `T*` added
- This created some complicates for other types that relied on implicit conversions, so this isn't a net cleanup right now
- The main type that got messed up by the above was `String`, which previously held a `RefPtr<char, ...>`. This change thus *also* includes a major overhaul of `String`:
- `String` now holds all its data via indirection, using a `StringRepresentation` that is a `RefObject`. This object holds a length, capacity, and directly stores the character data in its allocation. This means that `sizeof(String)==sizeof(void*)`
- It is now possible to directly mutate a `String` by appending to its representation (we just need to ensure it has a reference count of `1`, possibly by cloning it). This means that `StringBuilder` is now basically just an idomatic use of `String`
- A couple operations that just return sub-ranges of a `String` now return `StringSlice` to avoid allocation when it isn't needed. This required more work.
- Indices into strings changed from `int` to `UInt` (which is pointer-sized). This had a bunch of follow-on changes because the value `-1` sometimes needs to be special-cased in code that uses indices. Further cleanups are probably needed here.
|
| |
|
|
|
|
|
| |
The lexer was creating an `unsigned long long` value, and then the AST was storing it in an `int`.
This change makes both use a `long long`.
This is obviously still a stopgap until I can get arbitrary precisions in here.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Add logic to extract the value and suffix from a numeric literal
- This duplicates some of the lexing logic, but this is hard to avoid without redundant runtime work
- Note that I'm not using and stdlib string-to-number code. This should be more robust once it is working, but it is obviously error prone in the near term. The main up-sides to this are:
- We can handle binary integer literals
- We can handle hexadecimal floating-point literals without stdlib support
- We can hypothetically support digit separators, if we ever wanted
- The parser looks at the suffix characters sliced off by the lexer, and tries to pick a type to use for a literal
- It uses `NULL` if there is no suffix, to avoid some nasty order dependencies where the stdlib might need to parse a number before it has seen the definition of `int`
- Right now I only handle a few cases, so there may be bugs lurking here
- The emit logic needs to handle the fact that a literal node in the AST might have a non-default type attached.
- Right now I just quickly check for the most likely types, and emit the literal with a matching suffix. This doesn't seem robust if any source language supports a suffix for a type where a target has no corresponding suffix. In the long term some amount of casting is probably required.
|
| |
|
|
|
| |
These had a typo (`Literial`), so they needed a fix eventually.
I also went ahead and made things a bit more verbose (`IntegerLiteral`, `FloatingPointLiteral`) because these names don't get used often enough for the brevity to pay off.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
The code:
__import foo.bar;
will try to import from a file matching "foo/bar.slang".
I also went ahead and allowed a raw string literal in and import:
__import "foo/bar";
(In the latter case, an explicit `/` must be used instead of `.`)
|
| |
|
|
|
|
|
| |
If module `A.slang` contains `__exported __import B;` then any declarations from `B.slang` will be visible to any client code that does `__import A;`.
This allows a user to make a single "umbrella" file that encompases a bunch of code files.
Note that this really only affects scoping during Slang compilation/checking; at code generation time everything always gets emitted as raw HLSL/GLSL so that names will be visible whether we want them to be or not.
|
| |
|
|
|
|
|
|
|
| |
String literals can be used as part of attributes, but we lacked an actual AST representation for them.
This change adds basic parsing for string literals, as well as emit logic for them.
I also included a fix for parsing of chained right-associative operators.
To test these fixes, I've re-enabled one of the HLSL tests I disabled a while back. It would be good to go through and see how many of those we can re-enable now.
|
| |
|
|
|
|
| |
Right now `#import` only differs from `#include` in that it takes a string literal for a file name instead of a raw identifier (to which `.slang` gets appended).
The next step is to make `#import` respect preprocessor state, while `__import` doesn't.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The catch with these operations is that they return a vector based on the scalar of the element type of the texture.
That is, given `Texture2D<float> t` the operation `t.GatherRed(...)` should return a `float4`.
The ideal way to solve this would use associated types, but we aren't there yet, so I am using extension declarations.
An extension can "capture" the identity of the element type, like so:
__generic<T, let N : int> __extension Texture2D<vector<T,N> > { ... }
That extension will match `Texture2D<float3>` and correctly capture `T == float`, so that we can use it in other operations.
Getting this working required a bunch of changes:
- Actually emit the relevant extension declarations in the stdlib
- Fix the parser to be able to parse `Texture2D<vector<T,N> >` (that is, a nested generic app).
- I actually went ahead and significantly overhauled the expression parser while I was there, because I just couldn't deal with the existing code any longer.
- Added support for general-case lookup to look through `__extension` declarations. I had logic in place to special-case this for looking up "constructors" but hadn't done anything for general member lookup yet.
- This required some annoying holes to be punched through the layers, because lookup might need to invoke semantic analysis to ensure that an extension has been checked.
- There is some first-pass code trying to support looking up a `typedef` nested inside the `vector` type. This is a nice idea in principle, but the problem is that the `Texture2D<T>` definition would be looking up `T.Element` and not `float4.Element`, and that means we'd need machinery for doing lookup *through* interface conformances for a type parameter like `T`
The big gotcha here is that none of this logic applies to `Texture2D<float>` (the original case I mentioned) because I am matching vector types and not scalars.
Matching scalars *should* be as easy as:
__generic<T : __BuitlinScalarType> __extension Texture2D<T> { ... }
But I'd need to confirm that interface constraints like that actually work, or else that extension would *also* apply to `Texture2D<float4>` and break everything.
|
| |
|
|
| |
This was just missing logic in the parser.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The main user-visible change here is that instead of `spAddTranslationUnitEntryPoint` we have `spAddEntryPoint`, to reflect that the list of entry points is "global" to a compile request.
As a result, `spGetEntryPointSource` now only needs the entry point index, and not the translation unit index.
There are a bunch more behind-the-scenes changes, though, reflecting a streamlining of the concepts related to compilation into a smaller number of classes.
Now there is:
- `Session` (unchanged) to manage the lifetimes of shared stuff like the stdlib
- `CompileRequest` (merges in `CompileOptions`) to handle all the lifetime related to a single invocation of the compiler
- `TranslationUnitRequest` (merges `TranslationUnitOptions`, `CompileUnit`) to represent a single translation unit ("module") that the user is trying to compile. This is a single file for HLSL/GLSL, but can be multiple files for Slang.
- `EntryPointRequest` (merges `EntryPointOption` and a bit of `EntryPointResult`) to track a single entry point that the user is asking to compile (that entry point always comes from a single translation unit)
A lot of functions used to take some combination of these and end up with really long signatures.
I've given most of the objects "parent" pointers so that they can get back to all the context they need, so most functions don't need as many parameters.
It may eventually be important to tease these apart again, in particular:
- The code-generation side of things (the `*Result` types) might need to be pulled out in case we want to codegen multiple times from the same AST
- Similarly, the layout stuff may also need to be pulled out, in case we want to lay things out multiple times with different rules.
|
| |
|
|
| |
That is, even if hte user specified the `-no-checking` option (or the equivalent via API), we still want/need to apply full semantic checks to Slang code, so that cross-compilation will be possible.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The basic idea of this change is that user code can just write:
#include "foo.h"
and then if `foo.h` gets found in a list of registered directories for "auto-import," then it actually gets interpreted as if the user had writte, more or less:
__import foo;
That is, the code in `foo.h` will be treated as Slang, and will be fully parsed and checked (no matter what the source language had been), and the scoping rules will be those of `__import` instead of `#include`.
This is a really big hammer, and I could imagine it smashing fingers if used poorly.
I'm not sure this feature will pan out, but we need to try things to know.
One big piece of that that I'll likely keep in either case is an overhaul of command-line options parsing for `slangc`. In particular, this logic has been moved into the core `slang` library (so that users can just pass options in via the API), and it is all done on UTF-8 strings rather than wide strings (which was always going to be Windows-specific).
|
| |
|
|
|
|
| |
When in rewriter mode, the emit logic will never see function applications inside function bodies, but it *will* see function application expressions at global scope, and some of these expressions might be unchecked.
The challenge here is that even simple math operations now show up as function calls, so we need a bit of special-case logic to detect unchecked calls and then emit them using the syntax they were written with (e.g., use infix syntax if they were written as an infix expression).
|