From 11111e5733b189127dc2c4934d67693b9bc6e764 Mon Sep 17 00:00:00 2001 From: Yong He Date: Wed, 6 Dec 2023 12:05:07 -0800 Subject: Support visibility control and default to `internal`. (#3380) * Support visibility control and default to `internal`. * Fix wip. * Fixes. * Fix. * Fix test. * Add legacy language detection and compatibility for existing code. * Add doc. --------- Co-authored-by: Yong He --- docs/user-guide/04-interfaces-generics.md | 736 ------------ docs/user-guide/04-modules-and-access-control.md | 169 +++ docs/user-guide/05-compiling.md | 497 -------- docs/user-guide/05-interfaces-generics.md | 736 ++++++++++++ docs/user-guide/06-compiling.md | 497 ++++++++ docs/user-guide/06-targets.md | 366 ------ docs/user-guide/07-autodiff.md | 757 ------------ docs/user-guide/07-targets.md | 366 ++++++ docs/user-guide/08-autodiff.md | 757 ++++++++++++ docs/user-guide/toc.html | 72 +- examples/cpu-com-example/shader.slang | 8 +- source/slang/diff.meta.slang | 2 +- source/slang/glsl.meta.slang | 394 +++--- source/slang/slang-ast-decl.h | 9 + source/slang/slang-ast-modifier.h | 5 +- source/slang/slang-ast-support-types.h | 8 +- source/slang/slang-check-conversion.cpp | 2 +- source/slang/slang-check-decl.cpp | 100 +- source/slang/slang-check-expr.cpp | 463 ++++--- source/slang/slang-check-impl.h | 54 +- source/slang/slang-check-modifier.cpp | 84 +- source/slang/slang-check-overload.cpp | 62 +- source/slang/slang-diagnostic-defs.h | 9 + source/slang/slang-emit-c-like.cpp | 4 +- source/slang/slang-ir-autodiff.cpp | 5 - source/slang/slang-ir-dll-export.cpp | 6 +- .../slang/slang-ir-generics-lowering-context.cpp | 9 +- source/slang/slang-ir-link.cpp | 9 +- source/slang/slang-ir-lower-generic-type.cpp | 4 +- source/slang/slang-ir-pytorch-cpp-binding.cpp | 10 +- source/slang/slang-language-server-completion.cpp | 2 +- source/slang/slang-lookup.cpp | 16 +- source/slang/slang-lookup.h | 5 +- source/slang/slang-lower-to-ir.cpp | 41 +- source/slang/slang-parser.cpp | 29 +- source/slang/slang-syntax.cpp | 11 + source/slang/slang-syntax.h | 1 + tests/autodiff/bsdf/bsdf-auto-rev.slang | 3 +- tests/autodiff/bsdf/bsdf-sample.slang | 5 +- tests/autodiff/cuda-kernel-export.slang | 2 +- tests/autodiff/material/DiffuseMaterial.slang | 8 +- .../material/DiffuseMaterialInstance.slang | 12 +- tests/autodiff/material/GlossyMaterial.slang | 8 +- .../autodiff/material/GlossyMaterialInstance.slang | 18 +- tests/autodiff/material/IBSDF.slang | 4 +- tests/autodiff/material/IMaterial.slang | 6 +- tests/autodiff/material/IMaterialInstance.slang | 4 +- tests/autodiff/material/MaterialSystem.slang | 2 +- tests/autodiff/material2/DiffuseMaterial.slang | 10 +- .../material2/DiffuseMaterialInstance.slang | 6 +- tests/autodiff/material2/GlossyMaterial.slang | 14 +- .../material2/GlossyMaterialInstance.slang | 6 +- tests/autodiff/material2/IBSDF.slang | 8 +- tests/autodiff/material2/IMaterial.slang | 6 +- tests/autodiff/material2/IMaterialInstance.slang | 8 +- tests/autodiff/material2/MaterialSystem.slang | 2 +- tests/autodiff/material2/MxLayeredMaterial.slang | 32 +- .../material2/MxLayeredMaterialInstance.slang | 12 +- tests/autodiff/material2/MxWeights.slang | 14 +- tests/bindings/multi-file-defines.h | 3 +- tests/bindings/multi-file-shared.slang | 28 +- tests/bugs/gl-33-ext.slang | 6 +- tests/bugs/interface-lvalue.slang | 2 +- tests/bugs/split-nested-types.slang | 14 +- tests/compute/array-existential-parameter.slang | 2 +- tests/compute/dynamic-dispatch-11.slang | 2 +- tests/compute/dynamic-dispatch-12.slang | 8 +- tests/compute/dynamic-dispatch-13.slang | 4 +- tests/compute/dynamic-dispatch-14.slang | 10 +- tests/compute/dynamic-dispatch-15.slang | 10 +- .../dynamic-dispatch-bindless-texture.slang | 2 +- tests/compute/interface-assoc-type-param.slang | 4 +- tests/compute/interface-func-param-in-struct.slang | 4 +- .../interface-param-partial-specialize.slang | 4 +- tests/cpu-program/class-com.slang | 6 +- tests/cpu-program/class.slang | 4 +- tests/cpu-program/cpu-hello-world.slang | 2 +- tests/cpu-program/gfx-smoke.slang | 4 +- tests/cpu-program/pointer-basics.slang | 4 +- tests/cpu-program/pointer-deref.slang | 4 +- tests/current-bugs/resource-dynamic-dispatch.slang | 6 +- tests/diagnostics/extension-visibility-a.slang | 12 +- tests/diagnostics/extension-visibility-b.slang | 4 +- tests/diagnostics/extension-visibility-c.slang | 2 +- .../extension-visibility.slang.expected | 6 +- .../internal-visibility/that-module-impl.slang | 17 + .../internal-visibility/that-module.slang | 25 + .../internal-visibility/this-module.slang | 26 + tests/diagnostics/private-visibility.slang | 44 + tests/diagnostics/visibility.slang | 23 + tests/front-end/import-exported-b.slang | 2 +- tests/front-end/import-subdir-search-path.slang | 2 +- tests/front-end/raw-string-literal.slang | 4 +- tests/front-end/subdir/import-subdir-a.slang | 2 +- tests/ir/string-literal-module.slang | 2 +- .../extensions/extension-import-helper.slang | 10 +- .../struct-generic-value-param-import.slang | 8 +- .../inheritance/struct-inheritance-imported.slang | 8 +- tests/library/library.slang | 4 +- tests/reflection/multi-file-defines.h | 3 +- tests/reflection/multi-file-shared.slang | 28 +- tests/reflection/reflect-imported-code.slang | 12 +- tests/vkray/callable-shared.slang | 6 +- tools/gfx-unit-test/shader-cache-tests.cpp | 4 +- tools/gfx/gfx.slang | 1264 ++++++++++---------- tools/gfx/slang.slang | 126 +- .../unit-test-com-host-callable.slang | 16 +- .../unit-test-translation-unit-import.cpp | 4 +- 108 files changed, 4536 insertions(+), 3756 deletions(-) delete mode 100644 docs/user-guide/04-interfaces-generics.md create mode 100644 docs/user-guide/04-modules-and-access-control.md delete mode 100644 docs/user-guide/05-compiling.md create mode 100644 docs/user-guide/05-interfaces-generics.md create mode 100644 docs/user-guide/06-compiling.md delete mode 100644 docs/user-guide/06-targets.md delete mode 100644 docs/user-guide/07-autodiff.md create mode 100644 docs/user-guide/07-targets.md create mode 100644 docs/user-guide/08-autodiff.md create mode 100644 tests/diagnostics/internal-visibility/that-module-impl.slang create mode 100644 tests/diagnostics/internal-visibility/that-module.slang create mode 100644 tests/diagnostics/internal-visibility/this-module.slang create mode 100644 tests/diagnostics/private-visibility.slang create mode 100644 tests/diagnostics/visibility.slang diff --git a/docs/user-guide/04-interfaces-generics.md b/docs/user-guide/04-interfaces-generics.md deleted file mode 100644 index 94a08714e..000000000 --- a/docs/user-guide/04-interfaces-generics.md +++ /dev/null @@ -1,736 +0,0 @@ ---- -layout: user-guide ---- - -Interfaces and Generics -=========================== - -This chapter covers two interrelated Slang language features: interfaces and generics. We will talk about what they are, how do they relate to similar features in other languages, how are they parsed and translated by the compiler, and show examples on how these features simplifies and modularizes shader code. - -Interfaces ----------- - -Interfaces are used to define the methods and services a type should provide. You can define a interface as the following example: -```csharp -interface IFoo -{ - int myMethod(float arg); -} -``` - -Slang's syntax for defining interfaces are similar to `interface`s in C# and `protocol`s in Swift. In this example, the `IFoo` interface establishes a contract that any type conforming to this interface must provide a method named `myMethod` that accepts a `float` argument and returns an `int` value. - -A `struct` type may declare its conformance to an `interface` via the following syntax: -```csharp -struct MyType : IFoo -{ - int myMethod(float arg) - { - return (int)arg + 1; - } -} -``` -By declaring the conformance to `IFoo`, the definition of `MyType` must include a method named `myMethod` with a matching signature to that defined in the `IFoo` interface to satisfy the declared conformance. If a type misses any methods required by the interface, the Slang compiler will generate an error message. - -A `struct` type may declare multiple interface conformances: -```csharp -interface IBar { uint myMethod2(uint2 x); } - -struct MyType : IFoo, IBar -{ - int myMethod(float arg) {...} - uint myMethod2(uint2 x) {...} -} -``` -In this case, the definition of `MyType` must satisfy the requirements from both the `IFoo` and `IBar` interfaces by providing both the `myMethod` and `myMethod2` methods. - -Generics ---------------------- - -Generics can be used to eliminate duplicate code for shared logic that operates on different types. The following example shows how to define a generic method in Slang. - -```csharp -int myGenericMethod(T arg) -{ - return arg.myMethod(1.0); -} -``` - -The above listing defines a generic method named `myGenericMethod`, which accepts an argument that can be of any type `T` as long as `T` conforms to the `IFoo` interface. The `T` here is called a _generic type parameter_, and it is associated with an _type constraint_ that any type represented by `T` must conform to the interface `IFoo`. - -The following listing shows how to invoke a generic method: -```csharp -MyType obj; -int a = myGenericMethod(obj); // OK, explicit type argument -int b = myGenericMethod(obj); // OK, automatic type deduction -``` - -You may explicitly specify the concrete type to used for the generic type argument, by providing the types in angular brackets after the method name, or leave it to the compiler to automatically deduce the type from the argument list. - -> #### Note #### -> Slang currently does not support partial type argument list deduction. -> For example if you have a generic method that accepts two type arguments: -> ``` -> void g(T a, U b) {...} -> ``` -> You may either call this method with no explicit type arguments: -> ``` -> MyType a, b; -> g(a, b); -> ``` -> Or with explicit arguments for both generic type parameters: -> ``` -> g(a,b); -> ``` -> If you only provide first type argument, Slang will generate an error: -> ``` -> g(a,b); // error, does not work today. -> ``` -> We plan to support such use in a future version. - - -Note that it is important to associate a generic type parameter with a type constraint. In the above example, although the definition of `myGenericMethod` is agnostic of the concrete type `T` will stand for, knowing that `T` conforms to `IFoo` allows the compiler to type-check and pre-compile `myGenericMethod` without needing to substitute `T` with any concrete types first. Similar to languages like C#, Rust, Swift and Java, leaving out the type constraint declaration on type parameter `T` will result in a compile error at the line calling `arg.myMethod` since the compiler cannot verify that `arg` has a member named `myMethod` without any knowledge on `T`. This is a major difference of Slang's generics compared to _templates_ in C++. - -While C++ templates are a powerful language mechanism, Slang has followed the path of many other modern programming languages to adopt the more structural and restricted generics feature instead. This enables the Slang compiler to perform type checking early to give more readable error messages, and to speed-up compilation by reusing a lot of work for different instantiations of `myGenericMethod`. - - -Supported Constructs in Interface Definitions ------------------------------------------------------ - -Slang supports many other constructs in addition to ordinary methods as a part of an interface definition. - -### Properties - -```csharp -interface IFoo -{ - property int count {get; set;} -} -``` -The above listing declares that any conforming type must define a property named `count` with both a `getter` and a `setter` method. - -### Generic Methods - -```csharp -interface IFoo -{ - int compute(T val); -} -``` -The above listing declares that any conforming type must define a generic method named `compute` that has one generic type parameter conforming to the `IBar` interface. - -### Static Methods - -```csharp -interface IFoo -{ - static int compute(int val); -}; -``` - -The above listing declares that any conforming type must define a static method named `compute`. This allows the following generic method to pass type-checking: -```csharp -void f() -{ - T.compute(5); // OK, T has a static method `compute`. -} -``` - -### Static Constants - -You can define static constant requirements in an interface. The constants can be accessed in places where a compile-time constant is needed. -```csharp -interface IMyValue -{ - static const int value; -} -struct MyObject2 : IMyValue -{ - static const int value = 2; -} -struct GetValuePlus1 -{ - static const int value = T.value + 1; -} - -static const int result = GetValuePlus1.value; // result == 3 -``` - -### `This` Type - -You may use a special keyword `This` in interface definitions to refer to the type that is conforming to the interface. The following examples demonstrate a use of `This` type: -```csharp -interface IComparable -{ - int comparesTo(This other); -} -struct MyObject : IComparable -{ - int val; - int comparesTo(MyObject other) - { - return val < other.val ? -1 : 1; - } -} -``` -In this example, the `IComparable` interface declares that any conforming type must provide a `comparesTo` method that performs a comparison between an object to another object of the same type. The `MyObject` type satisfies this requirement by providing a `comparesTo` method that accepts a `MyObject` typed argument, since in the scope of `MyObject`, `This` type is equivalent to `MyObject`. - -### Initializers - -Consider a generic method that wants to create and initialize a new instance of generic type `T`: -```csharp -void f() -{ - T obj = /*a newly initialized T*/ -} -``` -One way to implement this is to introduce a static method requirement in `IFoo`: -```csharp -interface IFoo -{ - static This create(); -} -``` -With this interface definition, we can define `f` as following: -```csharp -void f() -{ - T obj = T.create(); -} -``` - -This solution works just fine, but it would be nicer if you can just write: -```csharp -T obj = T(); -``` -Or simply -```csharp -T obj; -``` -And let the compiler invoke the default initializer defined in the type. -To enable this, you can include an initializer requirement in the interface definition: -```csharp -interface IFoo -{ - __init(); -} -``` - -Initializers with parameters are supported as well. For example: -```csharp -interface IFoo -{ - __init(int a, int b); -} -void g() -{ - T obj = {1, 2}; // OK, invoking the initializer on T. -} -``` - -Associated Types -------------------------- - -When writing code using interfaces and generics, there are some situations where the an interface method needs to return an object whose type is implementation-dependent. For example, consider the following `IFloatContainer` interface that represents a container of `float` values: -```csharp -// Represents a container of float values. -interface IFloatContainer -{ - // Returns the number of elements in this container. - uint getCount(); - // Returns an iterator representing the start of the container. - Iterator begin(); - // Returns an iterator representing the end of the container. - Iterator end(); - // Return the element at the location represented by `iter`. - float getElementAt(Iterator iter); -} -``` -An implementation of the `IFloatContainer` interface may use different types of iterators. For example, an implementation that is simply an array of `float`s can expose `Iterator` as a simple integer index: -```csharp -struct ArrayFloatContainer : IFloatContainer -{ - float content[10]; - uint getCount() { return 10; } - uint begin() { return 0; } - uint end() { return 10; } - float getElementAt(uint iter) { return content[iter]; } -} -``` -On the other hand, an implementation that uses multiple buffers as the backing storage may use a more complex type to locate an element: -```csharp -// Exposes values in two `StructuredBuffer`s as a single container. -struct MultiArrayFloatContainer : IFloatContainer -{ - StructuredBuffer firstBuffer; - StructuredBuffer secondBuffer; - uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); } - - // `uint2.x` indicates which buffer, `uint2.y` indicates the index within the buffer. - uint2 begin() { return uint2(0,0); } - uint2 end() { return uint2 (1, getBufferSize(secondBuffer)); } - float getElementAt(uint2 iter) - { - if (iter.x == 0) return firstBuffer[iter.y]; - else return secondBuffer[iter.y]; - } -} -``` - -Ideally, a generic function that wishes to enumerate values in a `IFloatContainer` shouldn't need to care about the implementation details on what the concrete type of `Iterator` is, and we would like to be able to write the following: -```csharp -float sum(T container) -{ - float result = 0.0f; - for (T.Iterator iter = container.begin(); iter != container.end(); iter=iter.next()) - { - float val = container.getElementAt(iter); - result += val; - } - return result; -} -``` -Here the `sum` function simply wants to access all the elements and sum them up. The details of what the `Iterator` type actually is does not matter to the definition of `sum`. - -The problem is that the `IFloatContainer` interface definition requires methods like `begin()`, `end()` and `getElementAt()` to refer to a iterator type that is implementation dependent. How should the signature of these methods be defined in the interface? The answer is to use _associated types_. - -In addition to constructs listed in the previous section, Slang also supports defining associated types in an `interface` definition. An associated type can be defined as following. -```csharp -// The interface for an iterator type. -interface IIterator -{ - // An iterator needs to know how to move to the next element. - This next(); -} - -interface IFloatContainer -{ - // Requires an implementation to define a typed named `Iterator` that - // conforms to the `IIterator` interface. - associatedtype Iterator : IIterator; - - // Returns the number of elements in this container. - uint getCount(); - // Returns an iterator representing the start of the container. - Iterator begin(); - // Returns an iterator representing the end of the container. - Iterator end(); - // Return the element at the location represented by `iter`. - float getElementAt(Iterator iter); -}; -``` - -This `associatedtype` definition in `IFloatContainer` requires that all types conforming to this interface must also define a type in its scope named `Iterator`, and this iterator type must conform to the `IIterator` interface. An implementation to the `IFloatContainer` interface by using either a `typedef` declaration or a `struct` definition inside its scope to satisfy the associated type requirement. For example, the `ArrayFloatContainer` can be implemented as following: -```csharp -struct ArrayIterator : IIterator -{ - uint index; - __init(int x) { index = x; } - ArrayIterator next() - { - return ArrayIterator(index + 1); - } -} -struct ArrayFloatContainer : IFloatContainer -{ - float content[10]; - - // Specify that the associated `Iterator` type is `ArrayIterator`. - typedef ArrayIterator Iterator; - - Iterator getCount() { return 10; } - Iterator begin() { return ArrayIterator(0); } - Iterator end() { return ArrayIterator(10); } - float getElementAt(Iterator iter) { return content[iter.index]; } -} -``` - -Alternatively, you may also define the `Iterator` type directly inside a `struct` implementation, as in the following definition for `MultiArrayFloatContainer`: -```csharp -// Exposes values in two `StructuredBuffer`s as a single container. -struct MultiArrayFloatContainer : IFloatContainer -{ - // Represents an iterator of this container - struct Iterator : IIterator - { - // `index.x` indicates which buffer the element is located in. - // `index.y` indicates which the index of the element inside the buffer. - uint2 index; - - // We also need to keep a size of the first buffer so we know when to - // switch to the second buffer. - uint firstBufferSize; - - // Implementation of IIterator.next() - Iterator next() - { - Iterator result; - result.index.x = index.x; - result.index.y = index.y + 1; - // If we are at the end of the first buffer, - // move to the head of the second buffer - if (result.index.x == 0 && result.index.y == firstBufferSize) - { - result.index = uint2(1, 0); - } - return result; - } - } - - StructuredBuffer firstBuffer; - StructuredBuffer secondBuffer; - uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); } - - Iterator begin() - { - Iterator iter; - iter.index = uint2(0, 0); - iter.firstBufferSize = getBufferSize(firstBuffer); - return iter; - } - Iterator end() - { - Iterator iter; - iter.index = uint2(1, getBufferSize(secondBuffer)); - iter.firstBufferSize = 0; - return iter; - } - float getElementAt(Iterator iter) - { - if (ite.indexr.x == 0) return firstBuffer[iter.index.y]; - else return secondBuffer[iter.index.y]; - } -} -``` - -In summary, an `asssociatedtype` requirement in an interface is similar to other types of requirements: a method requirement means that an implementation must provide a method matching the interface signature, while an `associatedtype` requirement means that an implementation must provide a type in its scope with the matching name and interface constraint. In general, when defining an interface that is producing and consuming an object whose actual type is implementation-dependent, the type of this object can often be modeled as an associated type in the interface. - -### Comparison to the C++ Approach -Readers who are familiar with C++ could easily relate the `Iterator` example in previous subsection to the implementation of STL. In C++, the `sum` function can be easily written with templates: -```C++ -template -float sum(const TContainer& container) -{ - float result = 0.0f; - // Assumes `TContainer` has a type `Iterator` that supports `operator++`. - for (TContainer::Iterator iter = container.begin(); iter != container.end(); ++iter) - { - result += container.getElementAt(iter); - } - return result; -} -``` - -A C++ programmer can implement `ArrayFloatContainer` as following: -```C++ -struct ArrayFloatContainer -{ - float content[10]; - - typedef uint32_t Iterator; - - Iterator getCount() { return 10; } - Iterator begin() { return 0; } - Iterator end() { return 10; } - float getElementAt(Iterator iter) { return content[iter]; } -}; -``` -Because C++ does not require a template function to define _constraints_ on the templated type, there are no interfaces or inheritances involved in the definition of `ArrayFloatContainer`. However `ArrayFloatContainer` still needs to define what its `Iterator` type is, so the `sum` function can be successfully specialized with an `ArrayFloatContainer`. - -Note that the biggest difference between C++ templates and generics is that templates are not type-checked prior to specialization, and therefore the code that consumes a templated type (`TContainer` in this example) can simply assume `container` has a method named `getElementAt`, and the `TContainer` scope provides a type definition for `TContainer::Iterator`. Compiler error only arises when the programmer is attempting to specialize the `sum` function with a type that does not meet these assumptions. Contrarily, Slang requires all possible uses of a generic type be declared through an interface. By stating that `TContainer:IContainer` in the generics declaration, the Slang compiler can verify that `container.getElementAt` is calling a valid function. Similarily, the interface also tells the compiler that `TContainer.Iterator` is a valid type and enables the compiler to fully type check the `sum` function without specializing it first. - -### Similarity to Swift and Rust - -Slang's `associatedtype` shares the same semantic meaning with `associatedtype` in a Swift `protocol` or `type` in a Rust `trait`, except that Slang currently does not support the more general `where` clause in these languages. C# does not have an equivalent to `associatedtype`, and programmers need to resort to generic interfaces to achieve similar goals. - -Generic Value Parameters -------------------------------- - -So far we have demonstrated generics with _type parameters_. Additionally, Slang also supports generic _value_ parameters. -The following listing shows an example of generic value parameters. -```csharp -struct Array -{ - T arrayContent[N]; -} -``` -In this example, the `Array` type has a generic type parameter, `T`, that is used as the element type of the `arrayContent` array, and a generic value parameter `N` of integer type. - -Note that the builtin `vector` type also has an generic value parameter `N`. - -> #### Note #### -> The only type of generic value parameters are `int`, `uint` and `bool`. `float` and -> other types cannot be used in a generic value parameter. Computations in a type -> expression are supported as long as they can be evaluated at compile time. For example, -`vector` is allowed and considered equivalent to `vector`. - - -Interface-typed Values -------------------------------- - -So far we have been using interfaces as constraints to generic type parameters. For example, the following listing defines a generic function with a type parameter `TTransform` constrained by interface `ITransform`: - -```csharp -interface ITransform -{ - int compute(MyObject obj); -} - -// Defining a generic method: -int apply(TTransform transform, MyObject object) -{ - return transform.compute(object); -} -``` - -While Slang's syntax for defining generic methods bears similarity to generics in C#/Java and templates in C++ and should be easy to users who are familiar with these languages, codebases that make heavy use of generics can quickly become verbose and difficult to read. To reduce the amount of boilerplate, Slang supports an alternate way to define the `apply` method by using the interface type `ITransform` as parameter type directly: - -```csharp -// A method that is equivalent to `apply` but uses simpler syntax: -int apply_simple(ITransform transform, MyObject object) -{ - return transform.compute(object); -} -``` - -Instead of defining a generic type parameter `TTransform` and a method parameter `transform` that has `TTransform` type, you can simply define the same `apply` function like a normal method, with a `transform` parameter whose type is an interface. From the Slang compiler's view, `apply` and `apply_simple` will be compiled to the same target code. - -In addition to parameters, Slang allows variables, and function return values to have an interface type as well: -```csharp -ITransform test(ITransform arg) -{ - ITransform v = arg; - return v; -} -``` - -### Restrictions and Caveats - -The Slang compiler always attempts to determine the actual type of an interface-typed value at compile time and specialize the code with the actual type. As long as the compiler can successfully determine the actual type, code that uses interface-typed values are equivalent to code written in the generics syntax. However, when interface types are used in function return values, the compiler will not be able to trivially propagate type information. For example: -```csharp -ITransform getTransform(int x) -{ - if (x == 0) - { - Type1Transform rs = {}; - return rs; - } - else - { - Type2Transform rs = {}; - return rs; - } -} -``` -In this example, the actual type of the return value is dependent on the value of `x`, which may not be known at compile time. This means that the concrete type of the return value at invocation sites of `getTransform` may not be statically determinable. When the Slang compiler cannot infer the concrete type of an interface-type value, it will generate code that performs a dynamic dispatch based on the concrete type of the value at runtime, which may introduce performance overhead. Note that this behavior applies to function return values in the form of `out` parameters as well: - -```csharp -void getTransform(int x, out ITransform transform) -{ - if (x == 0) - { - Type1Transform rs = {}; - transform = rs; - } - else - { - Type2Transform rs = {}; - transform = rs; - } -} -``` -This `getTransform` definition can also result in dynamic dispatch code since the type of `transform` may not be statically determinable. - -When the compiler is generating dynamic dispatch code for interface-typed values, it requires the concrete type of the interface-typed value to be free of any opaque-typed fields (e.g. resources and buffer types). A compiler error will generated upon such attempts: -```csharp -struct MyTransform : ITransform -{ - StructuredBuffer buffer; - int compute(MyObject obj) - { - return buffer[0]; - } -} - -ITransform getTransform(int x) -{ - MyTransform rs; - // Error: cannot use an opaque value as an interface-typed return value. - return rs; -} -``` - -Assigning different values to a mutable interface-typed variable also undermines the compiler's ability to statically determine the type of the variable, and is not supported by the Slang compiler today: -```csharp -void test(int x) -{ - ITransform t = Type1Transform(); - // Do something ... - // Assign a different type of transform to `t`: - // (Not supported by Slang today) - t = Type2Transform(); - // Do something else... -} -``` - -In general, if the use of interface-typed values is restricted to function parameters only, then the all code that involves interface-typed values will be compiled the same way as if the code is written using standard generics syntax. - - -Extending a Type with Additional Interface Conformances ------------------------------ -In the previous chapter, we introduced the `extension` feature that lets you define new members to an existing type in a separate location outside the original definition of the type. - -`extensions` can be used to make an existing type conform to additional interfaces. Suppose we have an interface `IFoo` and a type `MyObject` that implements the interface: - -```csharp -interface IFoo -{ - int foo(); -}; - -struct MyObject : IFoo -{ - int foo() { return 0; } -} -``` - -Now we introduce another interface, `IBar`: -```csharp -interface IBar -{ - float bar(); -} -``` - -We can define an `extension` to make `MyObject` conform to `IBar` as well: -```csharp -extension MyObject : IBar -{ - float bar() { return 1.0f } -} -``` - -With this extension, we can use `MyObject` in places that expects an `IBar` as well: -```csharp -void use(IBar b) -{ - b.bar(); -} - -void test() -{ - MyObject obj; - use(obj); // OK, `MyObject` is extended to conform to `IBar`. -} -``` - -You may define more than one interface conformances in a single `extension`: -```csharp -interface IBar2 -{ - float bar2(); -} -extension MyObject : IBar, IBar2 -{ - float bar() { return 1.0f } - float bar2() { return 2.0f } -} -``` - -`is` and `as` Operator ----------------------------- - -You can use `is` operator to test if an interface-typed value is of a specific concrete type, and use `as` operator to downcast the value into a specific type. -The `as` operator returns an `Optional` that is not `none` if the downcast succeeds. - -```csharp -interface IFoo -{ - int foo(); -} -struct MyImpl : IFoo -{ - int foo() { return 0; } -} -void test(IFoo foo) -{ - bool t = foo is MyImpl; // true - Optional optV = foo as MyImpl; - if (t == (optV != none)) - printf("success"); - else - printf("fail"); -} -void main() -{ - MyImpl v; - test(v); -} -// Result: -// "success" -``` - - -Extensions to Interfaces ------------------------------ - -In addtion to extending ordinary types, you can define extensions on interfaces as well: -```csharp -// An example interface. -interface IFoo -{ - int foo(); -} - -// Extending `IFoo` with a new method requirement -// with a default implementation. -extension IFoo -{ - int bar() { return 0; } -} - -int use(IFoo foo) -{ - // With the extension, all uses of `IFoo` typed values - // can assume there is a `bar` method. - return foo.bar(); -} -``` - -Although the syntax of above listing suggests that we are extending an interface with additional requirements, this interpretation does not make logical sense in many ways. Consider a type `MyType` that exists before the extension is defined: -```csharp -struct MyType : IFoo -{ - int foo() { return 0; } -} -``` - -If we extend the `IFoo` with new requirements, the existing `MyType` definition would become invalid since `MyType` no longer provides implementations to all interface requirements. Instead, what an `extension` on an interface `IFoo` means is that for all types that conforms to the `IFoo` interface and does not have a `bar` method defined, add a `bar` method defined in this extension to that type so that all `IFoo` typed values have a `bar` method defined. If a type already defines a matching `bar` method, then the existing method will always override the default method provided in the extension: - -```csharp -interface IFoo -{ - int foo(); -} -struct MyFoo1 : IFoo -{ - int foo() { return 0; } -} -extension IFoo -{ - int bar() { return 0; } -} -struct MyFoo2 : IFoo -{ - int foo() { return 0; } - int bar() { return 1; } -} -void test() -{ - MyFoo1 f1; - MyFoo2 f2; - int a = f1.bar(); // a == 0, calling the method in the extension. - int b = f2.bar(); // b == 1, calling the existing method in `MyFoo2`. -} -``` -This feature is similar to extension traits in Rust. diff --git a/docs/user-guide/04-modules-and-access-control.md b/docs/user-guide/04-modules-and-access-control.md new file mode 100644 index 000000000..5beb2b197 --- /dev/null +++ b/docs/user-guide/04-modules-and-access-control.md @@ -0,0 +1,169 @@ + +--- +layout: user-guide +--- + +Modules and Access Control +=========================== + +While the preprocessor `#include`s is still supported, Slang provides a _module_ system for software engineering benefits such as clean expression of sub-component boundaries and dependencies, hiding implementation details, and providing a path towards true separate compilation. + + +### Defining a Module + +A module in Slang comprises one or more files. A module must have one and only one primary file that is used as the source-of-truth to uniquely identify the module. The primary file must start with `module` declaration. For example, the following code defines a module named `scene`: + +``` +// scene.slang + +module scene; + +// ... +``` + +A module can contain more than one file. The additional files are pulled into the module with the `__include` syntax: + +``` +// scene.slang + +module scene; + +__include "scene-helpers"; + +``` +``` +// scene-helpers.slang + +implementing scene; +// ... +``` + +The files being included into a module must start with `implementing ` declaration. + +Note that the `__include` syntax here has a different meaning than the preprocessor `#include`. `__include` has the following semantics: +1. The preprocessor state at which a file inclusion does not apply to the file being included, and the preprocessor state after parsing the included file will not be visible to the outer "includer" file. For example, `#define`s before a `__include` is not visible to the included file, and `#define`s in the included file is not visible to the file that includes it. +2. A file will be included into the current module exactly once, no matter how many times a `__include` of that file is encountered. +3. Circular `__include`s are allowed, given (2). +4. All files that become part of a module via `__include` can access all other entities defined in the same module, regardless the order of `__include`s. + +This means that the following code is valid: + +``` +// a.slang +implementing m; +void f_a() {} + +// b.slang +implementing "m"; // alternate syntax. +__include a; // pulls in `a` to module `m`. +void f_b() { f_a(); } + +// c.slang +implementing "m.slang"; // alternate syntax. +// OK to use f_a and f_b because they are part of module `m`, even +// if we are not including `a` and `b` here. +void f_c() { f_a(); f_b(); } + +// m.slang +module m; +__include m; // OK, a file including itself is allowed and has no effect. +__include "b"; // Pulls in file b (alternate syntax), and transitively pulls in file a. +__include "c.slang"; // Pulls in file c, specifying the full file name. +void test() { f_a(); f_b(); f_c(); } +``` + +Note that both `module`, `implementing` and `__include` support two flavors of syntax to refer to a module or a file: either via +normal identifier tokens or via string literals. For example, the following flavors are equivalent and will resolve to the same file: +``` +__include dir.file_name; // `file_name` is translated to "file-name". +__include "dir/file-name.slang"; +__include "dir/file-name"; +``` + +> #### Note #### +> When using the identifier token syntax, Slang will translate any underscores(`_`) to hyphenators("-") to obtain the file name. + +### Importing a Module + +At the global scope of a Slang file, you can use the `import` keyword to import another module by name: + +```hlsl +// MyShader.slang + +import YourLibrary; +``` + +This `import` declaration will cause the compiler to look for a module named `YourLibrary` and make its declarations visible in the current scope. Similar to `__include`, `import` also supports both the identifier-token and the file-name string syntax. + +You can only `import` a primary source file of a module. For example, given: +``` +// m.slang +module m; +__include helper; + +// helper.slang +implementing m; +// ... +``` +It is only valid for the user code to `import m`. Attempting to `import helper` will result a compile-time error. + +Multiple `import`s of the same module from different input files will only cause the module to be loaded once (there is no need for "include guards" or `#pragma once`). +Note that preprocessor definitions in the current file will not affect the compilation of `import`ed code, and the preprocessor definitions in the imported code is not visible to the current file. + +> #### Note #### +> Future versions of the Slang system will support loading of modules from pre-compiled binaries instead of source code. +> The same `import` keyword will continue to work in that case. + +### Access Control + +Slang supports access control modifiers: `public`, `internal` and `private`. The module boundary plays an important role in access control. + +`public` symbols are accessible everywhere: from within the different types, different files or different modules. + +`private` symbols are only visible to other symbols in the same type. The following example shows the scope of `private` visibility: +```csharp +struct MyType +{ + private int member; + + int f() { member = 5; } // OK. + + struct ChildType + { + int g(MyType t) + { + return t.member; // OK. + } + } +} + +void outerFunc(MyType t) +{ + t.member = 2; // Error, `member` is not visible here. +} +``` + +`internal` symbols are visible throughout the same module, regardless if it is referenced from the same type or same file. But they are not visible to other modules. The following example shows the scope of `internal` visibility: + +```csharp +// a.slang +module a; +__include b; +internal void f() { f_b(); } // OK, f_b defined in the same module. + +// b.slang +implementing a; +internal void f_b(); // Defines f_b in module a so they can within the module. +public void publicFunc(); + +// m.slang +module m; +import a; +void main() +{ + f(); // Error, f is not visible here. + publicFunc(); // OK. +} +``` + +`internal` is the default visibility if no other access modifiers are specified. diff --git a/docs/user-guide/05-compiling.md b/docs/user-guide/05-compiling.md deleted file mode 100644 index 5c19175d6..000000000 --- a/docs/user-guide/05-compiling.md +++ /dev/null @@ -1,497 +0,0 @@ ---- -layout: user-guide ---- - -Compiling Code with Slang -========================= - -This chapter presents the ways that the Slang system supports compiling and composing shader code. -We will start with a discussion of the mental model that Slang uses for compilation. -Next we will cover the command-line Slang compiler, `slangc`, and how to use it to perform offline compilation. -Finally we will discuss the Slang compilation API, which can be used to integrate Slang compilation into an application at runtime, or to build custom tools that implement application-specific compilation policy. - -Concepts --------- - -For simple scenarios it may be enough to think of a shader compiler as a box where source code goes in and compiled kernels come out. -Most real-time graphics applications end up needing more control over shader compilation, and/or more information about the results of compilation. -In order to make use of the services provided by the Slang compilation system, it is useful to start with a clear model of the concepts that are involved in compilation. - -### Source Units - -At the finest granularity, code is fed to the compiler in _source units_ which are most often stored as files on disk or strings of text in memory. -The compilation model largely does not care whether source units have been authored by human programmers or automatically assembled by other tools. - -If multiple source units are specified as part of the same compile, they will be preprocessed and parsed independently. -However, a source unit might contain `#include` directives, so that the preprocessed text of that source unit includes the content of other files. -Note that the `#include`d files do not become additional source units; they are just part of the text of a source unit that was fed to the compiler. - -### Translation Units and Modules - -Source units (such as files) are grouped into _translation units_, and each translation unit will produce a single _module_ when compiled. - -While the source units are all preprocessed and parsed independently, semantic checking is applied to a translation unit as a whole. -One source file in a translation unit may freely refer to declarations in another translation unit without any need for forward declarations. For example: - -```hlsl -// A.slang - -float getFactor() { return 10.0; } -``` - -```hlsl -// B.slang - -float scaleValue(float value) -{ - return value * getFactor(); -} -``` - -In this example, the `scaleValue()` function in `B.slang` can freely refer to the `getFactor()` function in `A.slang` because they are part of the same translation unit. - -It is allowed, and indeed common, for a translation unit to contain only a single source unit. -For example, when adapting an existing codebase with many `.hlsl` files, it is appropriate to compile each `.hlsl` file as its own translation unit. -A modernized codebase might decide to compile multiple `.slang` files in a single directory as a single translation unit. - -The result of compiling a translation unit is a module in Slang's internal intermediate representation (IR). - -### Entry Points - -A translation unit / module may contain zero or more entry points. -Slang supports two models for identifying entry points when compiling. - -#### Entry Point Attributes - -By default, the compiler wll scan a translation unit for function declarations marked with the `[shader(...)]` attribute; each such function will be identified as an entry point in the module. -Developers are encouraged to use this model because it makes directly documents intention and makes source code less dependent on external compiler configuration options. - -#### Explicit Entry Point Options - -For compatibility with existing code, the Slang compiler also supports explicit specification of entry point functions using configuration optiosn external to shader source code. -When these options are used the compiler will *ignore* all `[shader(...)]` attributes and only use the explicitly-specified entry points intead. - -### Shader Parameters - -A translation unit / module may contain zero or more global shader parameters. -Similarly, each entry point may define zero or more entry-point `uniform` shader parameters. - -The shader parameters of a module or entry point are significant because they describe the interface between host application code and GPU code. -It is important that both the application and generated GPU kernel code agree on how parameters are laid out in memory and/or how they are assigned to particular API-defined registers, locations, or other "slots." - -### Targets - -Within the Slang system a _target_ represents a particular platform and set of capabilities that output code can be generated for. -A target includes information such as: - -* The _format_ that code should be generated in: SPIR-V, DXIL, etc. - -* A _profile_ that specifies a general feature/capability level for the target: D3D Shader Model 5.1, GLSL version 4.60, etc. - -* Optional _capabilities_ that should be assumed available on the target: for example, specific Vulkan GLSL extensions - -* Options that impact code generation: floating-point strictness, level of debug information to generate, etc. - -Slang supports compiling for multiple targets in the same compilation session. -When using multiple targets at a time, it is important to understand the distinction between the _front-end_ of the compiler, and the _back-end_: - -* The compiler front-end comprises preprocessing, parsing, and semantic checking. The front-end runs once for each translation unit and its results are shared across all targets. - -* The compiler back-end generates output code, and thus runs once per target. - -> #### Note #### -> Because front-end actions, including preprocessing, only run once, across all targets, the Slang compiler does not automatically provide any target-specific preprocessor `#define`s that can be used for preprocessor conditionals. -> Applications that need target-specific `#define`s should always compile for one target at a time, and set up their per-target preprocessor state manually. - -### Layout - -While the front-end of the compiler determines what the shader parameters of a module or entry point are, the _layout_ for those parameters is dependent on a particular compilation target. -A `Texture2D` might consume a `t` register for Direct3D, a `binding` for Vulkan, or just plain bytes for CUDA. - -The details of layout in Slang will come in a later chapter. -For the purposes of the compilation model it is important to note that the layout computed for shader parameters depends on: - -* What modules and entry points are being used together; these define which parameters are relevant. - -* Some well-defined ordering of those parameters; this defines which parameters should be laid out before which others. - -* The rules and constraints that the target imposes on layout. - -An important design choice in Slang is give the user of the compiler control over these choices. - -### Composition - -The user of the Slang compiler communicates the modules and entry points that will be used together, as well as their relative order, using a system for _composition_. - -A _component type_ is a unit of shader code composition; both modules and entry points are examples of component types. -A _composite_ component type is formed from a list of other component types (for example, one module and two entry points) and can be used to define a unit of shader code that is meant to be used together. - -Once a programmer has formed a composite of all the code they intend to use together, they can query the layout of the shader parameters in that composite, or request kernel code generation for its entry points. - -### Kernels - -A _kernel_ is generated code for an entry point. -The same entry point can be used to generate many different kernels. -First, and entry point can be compiled for different targets, resulting in different kernels in the appropriate format for each target. -Second, different compositions of shader code can result in different layouts, which leads to different kernels being required. - -Command-Line Compilation with `slangc` --------------------------------------- - -The `slangc` tool, included in binary distributions of Slang, is a command-line compiler that can handle most simple compilation tasks. -`slangc` is intended to be usable as a replacement for tools like `fxc` and `dxc`, and covers most of the same use cases. - -### Example - -Here we will repeat the example used in the [Getting Started](01-get-started.md) chapter. -Given the following Slang code: - -```hlsl -// hello-world.slang -StructuredBuffer buffer0; -StructuredBuffer buffer1; -RWStructuredBuffer result; - -[shader("compute")] -[numthreads(1,1,1)] -void computeMain(uint3 threadId : SV_DispatchThreadID) -{ - uint index = threadId.x; - result[index] = buffer0[index] + buffer1[index]; -} -``` - -we can compile the `computeMain()` entry point to SPIR-V using the following command line: - -```bat -slangc hello-world.slang -entry computeMain -target spirv -o hello-world.spv -``` - -### Source Files and Translation Units - -The `hello-world.slang` argument here is specifying an input file. -Each input file specified on the command line will be a distinct source unit during compilation. -Slang supports multiple file-name extensions for input files, but the most common ones will be `.hlsl` for existing HLSL code, and `.slang` for files written specifically for Slang. - -If multiple source files are passed to `slangc`, they will be grouped into translation units using the following rules: - -* If there are any `.slang` files, then all of them will be grouped into a single translation unit - -* Each `.hlsl` file will be grouped into a distinct translation unit of its own - -### Entry Points - -When using `slangc`, you will typically want to identify which entry point(s) you intend to compile. -The `-entry computeMain` option selects an entry point to be compiled to output code in this invocation of `slangc`. - -Because the `computeMain()` entry point in this example has a `[shader(...)]` attribute, the compiler is able to deduce that it should be compiled for the `compute` stage. -In code that does not use `[shader(...)]` attributes, a `-entry` option should be followed by a `-stage` option to specify the stage of the entry point: - -```bat -slangc hello-world.slang -entry computeMain -stage compute -o hello-world.spv -``` - -### Targets - -Our example uses the option `-target spirv` to introduce a compilation target; in this case, code will be generated as SPIR-V. -The argument of a `-target` option specified the format to use for the target; common values are `dxbc`, `dxil`, and `spirv`. - -Additional options for a target can be specified after the `-target` option. -For example, a `-profile` option can be used to specify a profile that should be used. -Slang provides two main kinds of profiles for use with `slangc`: - -* Direct3D "Shader Model" profiles have names like `sm_5_1` and `sm_6_3` - -* GLSL versions can be used as profile with names like `glsl_430` and `glsl_460` - -### Kernels - -A `-o` option indicates that kernel code should be written to a file on disk. -In our example, the SPIR-V kernel code for the `computeMain()` entry point will be written to the file `hello-world.spv`. - -### Working with Multiples - -It is possible to use `slangc` with multiple input files, entry points, or targets. -In these cases, the ordering of arguments on the command line becomes significant. - -When an option modifies or relates to another command-line argument, it implicitly applies to the most recent relevant argument. -For example: - -* If there are multiple input files, then an `-entry` option applies to the preceding input file - -* If there are multiple entry points, then a `-stage` option applies to the preceding `-entry` option - -* If there are multiple targets, then a `-profile` option applies to the preceding `-target` option - -Kernel `-o` options are the most complicated case, because they depend on both a target and entry point. -A `-o` option applies to the preceding entry point, and the compiler will try to apply it to a matching target based on its file extension. -For example, a `.spv` output file will be matched to a `-target spriv`. - -The compiler makes a best effort to support complicated cases with multiple files, entry points, and targets. -Users with very complicated compilation requirements will probably be better off using multiple `slangc` invocations or migrating to the compilation API. - -### Additional Options - -The main other options are: - -* `-D` or `-D=` can be used to introduce preprocessor macros. - -* `-I` or `-I ` can be used to introduce a _search path_ to be used when resolving `#include` directives and `import` declarations. - -* `-g` can be used to enable inclusion of debug information in output files (where possible and implemented) - -* `-O` can be used to control optimization levels when the Slang compiler invokes downstream code generator - -### Convenience Features - -The `slangc` compiler provides a few conveniences for command-line compilation: - -* Most options can appear out of order when they are unambiguous. For example, if there is only a single translation unit a `-entry` option can appear before or after any file. - -* A `-target` option can be left out if it can be inferred from the only `-o` option present. For example, `-o hello-world.spv` already implies `-target spriv`. - -* If a `-o` option is left out then kernel code will be written to the standard output. This output can be piped to a file, or can be printed to a console. In the latter case, the compiler will automatically disassemble binary formats for printing. - -### Limitations - -The `slangc` tool is meant to serve the needs of many developers, including those who are currently using `fxc`, `dxc`, or similar tools. -However, some applications will benefit from deeper integration of the Slang compiler into application-specific code and workflows. -Notable features that Slang supports which cannot be accessed from `slangc` include: - -* Slang can provide _reflection_ information about shader parameters and their layouts for particular targets; this information is not currently output by `slangc`. - -* Slang allows applications to control the way that shader modules and entry points are composed (which in turn influences their layout); `slangc` currently implements a single default policy for how to generate a composition of shader code. - -Applications that more control over compilation are encouraged to use the C++ compilation API described in the next section. - -Using the Compilation API -------------------------- - -The C++ API provided by Slang is meant to provide more complete control over compilation for applications that need it. -The additional level of control means that some tasks require more individual steps than they would when using a one-size-fits-all tool like `slangc`. - -### "COM-lite" Components - -Many parts of the Slang C++ API use interfaces that follow the design of COM (the Component Object Model). -Some key Slang interfaces are binary-compatible with existing COM interfaces. -However, the Slang API does not depend on any runtime aspects of the COM system, even on Windows; the Slang system can be seen as a "COM-lite" API. - -The `ISlangUnknown` interface is equivalent to (and binary-compatible with) the standard COM `IUnknown`. -Application code is expected to correctly maintain the reference counts of `ISlangUnknown` objects returned from API calls; the `SlangComPtr` "smart pointer" type is provided as an optional convenience for applications that want to use it. - -Many Slang API calls return `SlangResult` values; this type is equivalent to (and binary-compatible with) the standard COM `HRESULT` type. -As a matter of convention, Slang API calls return a zero value (`SLANG_OK`) on success, and a negative value on errors. - -### Creating a Global Session - -A Slang _global session_ uses the interface `slang::IGlobalSession` and it represents a connection from an application to a particular implementation of the Slang API. -A global session is created using the function `slang::createGlobalSession()`: - -```c++ -SlangComPtr globalSession; -slang::createGlobalSession(globalSession.writeRef()); -``` - -When a global session is created, the Slang system will load its internal representation of the _standard library_ that the compiler provides to user code. -The standard library can take a significant amount of time to load, so applications are advised to use a single global session if possible, rather than creating and then disposing of one for each compile. - -> #### Note #### -> Currently, the global session type is *not* thread-safe. -> Applications that wish to compile on multiple threads will need to ensure that each concurrent thread compiles with a distinct global session. - -### Creating a Session - -A _session_ uses the interface `slang::ISession`, and represents a scope for compilation with a consistent set of compiler options. -In particular, all compilation with a single session will share: - -* A list of enabled compilation targets (with their options) - -* A list of search paths (for `#include` and `import`) - -* A list of pre-defined macros - -In addition, a session provides a scope for the loading and re-use of modules. -If two pieces of code compiled in a session both `import` the same module, then that module will only be loaded and compiled once. - -To create a session, use the `IGlobalSession::createSession()` method: - -```c++ -SessionDesc sessionDesc; -/* ... fill in `sessionDesc` ... */ -SlangComPtr session; -globalSession->createSession(sessionDesc, session.writeRef()); -``` - -#### Targets - -The `SessionDesc::targets` array can be used to describe the list of targets that the application wants to support in a session. -Often, this will consist of a single target. - -Each target is described with a `TargetDesc` which includes options to control code generation for the target. -The most important fields of the `TargetDesc` are the `format` and `profile`; most others can be left at their default values. - -The `format` field should be set to one of the values from the `SlangCompileTarget` enumeration. -For example: - -```c++ -TargetDesc targetDesc; -targetDesc.format = SLANG_FORMAT_SPIRV; -``` - -The `profile` field must be set with the ID of one of the profiles supported by the Slang compiler. -The exact numeric value of the different profiles is not currently stable across compiler versions, so applications should look up a chosen profile using `IGlobalSession::findProfile`. -For example: - -```c++ -targetDesc.profile = globalSession->findProfile("glsl_450"); -``` - -Once the chosen `TargetDesc`s have been initialized, they can be attached to the `SessionDesc`: - -```c++ -sessionDesc.targets = &targetDesc; -sessionDesc.targetCount = 1; -``` - -#### Search Paths - -The search paths on a session provide the paths where the compiler will look when trying to resolve a `#include` directive or `import` declaration. -The search paths can be set in the `SessionDesc` as an array of `const char*`: - -```c++ -const char* searchPaths[] = { "myapp/shaders/" }; -sessionDesc.searchPaths = searchPaths; -sessionDesc.searchPathCount = 1; -``` - -#### Pre-Defined Macros - -The pre-defined macros in a session will be visible at the start of each source unit that is compiled, including source units loaded via `import`. -Each pre-defined macro is described with a `PreprocessorMacroDesc`, which has `name` and `value` fields: - -```c++ -PreprocessorMacroDesc fancyFlag = { "ENABLE_FANCY_FEATURE", "1" }; -sessionDesc.preprocessorMacros = &fancyFlag; -sessionDesc.preprocessorMacroCount = 1; -``` - -### Loading a Module - -The simplest way to load code into a session is with `ISession::loadModule()`: - -```c++ -SlangComPtr module = session->loadModule("MyShaders"); -``` - -Executing `loadModule("MyShaders")` in host C++ code is similar to using `import MyShaders` in Slang code. -The session will search for a matching module (usually in a file called `MyShaders.slang`) and will load and compile it (if it hasn't been done already). - -Note that `loadModule()` does not provide any ways to customize the compiler configuration for that specific module. -The preprocessor environment, search paths, and targets will always be those specified for the session. - -### Capturing Diagnostic Output - -Compilers produce various kinds of _diagnostic_ output when compiling code. -This includes not only error messages when compilation fails, but also warnings and other helpful messages that may be produced even for successful compiles. - -Many operations in Slang, such as `ISession::loadModule()` can optionally produce a _blob_ of diagnostic output. -For example: - -```c++ -SlangComPtr diagnostics; -SlangComPtr module = session->loadModule("MyShaders", diagnostics.writeRef()); -``` - -In this example, if any diagnostic messages were produced when loading `MyShaders`, then the `diagnostics` pointer will be set to a blob that contains the textual content of those diagnostics. - -The content of a blob can be accessed with `getBufferPointer()`, and the size of the content can be accessed with `getBufferSize()`. -Diagnostic blobs produces by the Slang compiler are always null-terminated, so that they can be used with C-style sting APIs: - -```c++ -if(diagnostics) -{ - fprintf(stderr, "%s\n", (const char*) diagnostics->getBufferPointer()); -} -``` - -> #### Note #### -> The `slang::IBlob` interface is binary-compatible with the `ID3D10Blob` and `ID3DBlob` interfaces used by some Direct3D compilation APIs. - -### Entry Points - -When using `loadModule()` applications should ensure that entry points in their shader code are always marked with appropriate `[shader(...)]` attributes. -For example, if `MyShaders.slang` contained: - -```hlsl -[shader("compute")] -void myComputeMain(...) { ... } -``` - -then the Slang system will automatically detect and validate this entry point as part of a `loadModule("MyShaders")` call. - -After a module has been loaded, the application can look up entry points in that module using `IModule::findEntryPointByName()`: - -```c++ -SlangComPtr computeEntryPoint; -module->findEntryPointByName("myComputeMain", computeEntryPoint.writeRef()); -``` - -### Composition - -An application might load any number of modules with `loadModule()`, and those modules might contain any number of entry points. -Before GPU kernel code can be generated it is first necessary to decide which pieces of GPU code will be used together. - -Both `slang::IModule` and `slang::IEntryPoint` inherit from `slang::IComponentType`, because both can be used as components when composing a shader program. -A composition can be created with `ISession::createCompositeComponentType()`: - -```c++ -IComponentType* components[] = { module, entryPoint }; -SlangComPtr program; -session->createCompositeComponentType(components, 2, program.writeRef()); -``` - -As discussed earlier in this chapter, the composition operation serves two important purposes. -First, it establishes which code is part of a compiled shader program and which is not. -Second, it established an ordering for the code in a program, which can be used for layout. - -### Layout and Reflection - -Some applications need to perform reflection on shader parameters and their layout, whether at runtime or as part of an offline compilation tool. -The Slang API allows layout to be queried on any `IComponentType` using `getLayout()`: - -```c++ -slang::ProgramLayout* layout = program->getLayout(); -``` - -> #### Note #### -> In the current Slang API, the `ProgramLayout` type is not reference-counted. -> Currently, the lifetime of a `ProgramLayout` is tied to the `IComponentType` that returned it. -> An application must ensure that it retains the given `IComponentType` for as long as it uses the `ProgramLayout`. - -Note that because both `IModule` and `IEntryPoint` inherit from `IComponentType`, they can also be queried for their layouts individually. -The layout for a module comprises just its global-scope parameters. -The layout for an entry point comprises just its entry-point parameters (both `uniform` and varying). - -The details of how Slang computes layout, what guarantees it makes, and how to inspect the reflection information will be discussed in a later chapter. - -Because the layout computed for shader parameters may depend on the compilation target, the `getLayout()` method actually takes a `targetIndex` parameter that is the zero-based index of the target for which layout information is being queried. -This parameter defaults to zero as a convenience for the common case where applications use only a single compilation target at runtime. - -### Kernel Code - -Given a composed `IComponentType`, an application can extract kernel code for one of its entry points using `IComponentType::getEntryPointCode()`: - -```c++ -int entryPointIndex = 0; // only one entry point -int targetIndex = 0; // only one target -SlangComPtr kernelBlob; -program->getEntryPointCode( - entryPointIndex, - targetIndex, - kernelBlob.writeRef(), - diagnostics.writeRef()); -``` - -Any diagnostic messages related to back-end code generation (for example, if the chosen entry point requires features not available on the chosen target) will be written to `diagnostics`. -The `kernelBlob` output is a `slang::IBlob` that can be used to access the generated code (whether binary or textual). -In many cases `kernelBlob->getBufferPointer()` can be passed directly to the appropriate graphics API to load kernel code onto a GPU. diff --git a/docs/user-guide/05-interfaces-generics.md b/docs/user-guide/05-interfaces-generics.md new file mode 100644 index 000000000..94a08714e --- /dev/null +++ b/docs/user-guide/05-interfaces-generics.md @@ -0,0 +1,736 @@ +--- +layout: user-guide +--- + +Interfaces and Generics +=========================== + +This chapter covers two interrelated Slang language features: interfaces and generics. We will talk about what they are, how do they relate to similar features in other languages, how are they parsed and translated by the compiler, and show examples on how these features simplifies and modularizes shader code. + +Interfaces +---------- + +Interfaces are used to define the methods and services a type should provide. You can define a interface as the following example: +```csharp +interface IFoo +{ + int myMethod(float arg); +} +``` + +Slang's syntax for defining interfaces are similar to `interface`s in C# and `protocol`s in Swift. In this example, the `IFoo` interface establishes a contract that any type conforming to this interface must provide a method named `myMethod` that accepts a `float` argument and returns an `int` value. + +A `struct` type may declare its conformance to an `interface` via the following syntax: +```csharp +struct MyType : IFoo +{ + int myMethod(float arg) + { + return (int)arg + 1; + } +} +``` +By declaring the conformance to `IFoo`, the definition of `MyType` must include a method named `myMethod` with a matching signature to that defined in the `IFoo` interface to satisfy the declared conformance. If a type misses any methods required by the interface, the Slang compiler will generate an error message. + +A `struct` type may declare multiple interface conformances: +```csharp +interface IBar { uint myMethod2(uint2 x); } + +struct MyType : IFoo, IBar +{ + int myMethod(float arg) {...} + uint myMethod2(uint2 x) {...} +} +``` +In this case, the definition of `MyType` must satisfy the requirements from both the `IFoo` and `IBar` interfaces by providing both the `myMethod` and `myMethod2` methods. + +Generics +--------------------- + +Generics can be used to eliminate duplicate code for shared logic that operates on different types. The following example shows how to define a generic method in Slang. + +```csharp +int myGenericMethod(T arg) +{ + return arg.myMethod(1.0); +} +``` + +The above listing defines a generic method named `myGenericMethod`, which accepts an argument that can be of any type `T` as long as `T` conforms to the `IFoo` interface. The `T` here is called a _generic type parameter_, and it is associated with an _type constraint_ that any type represented by `T` must conform to the interface `IFoo`. + +The following listing shows how to invoke a generic method: +```csharp +MyType obj; +int a = myGenericMethod(obj); // OK, explicit type argument +int b = myGenericMethod(obj); // OK, automatic type deduction +``` + +You may explicitly specify the concrete type to used for the generic type argument, by providing the types in angular brackets after the method name, or leave it to the compiler to automatically deduce the type from the argument list. + +> #### Note #### +> Slang currently does not support partial type argument list deduction. +> For example if you have a generic method that accepts two type arguments: +> ``` +> void g(T a, U b) {...} +> ``` +> You may either call this method with no explicit type arguments: +> ``` +> MyType a, b; +> g(a, b); +> ``` +> Or with explicit arguments for both generic type parameters: +> ``` +> g(a,b); +> ``` +> If you only provide first type argument, Slang will generate an error: +> ``` +> g(a,b); // error, does not work today. +> ``` +> We plan to support such use in a future version. + + +Note that it is important to associate a generic type parameter with a type constraint. In the above example, although the definition of `myGenericMethod` is agnostic of the concrete type `T` will stand for, knowing that `T` conforms to `IFoo` allows the compiler to type-check and pre-compile `myGenericMethod` without needing to substitute `T` with any concrete types first. Similar to languages like C#, Rust, Swift and Java, leaving out the type constraint declaration on type parameter `T` will result in a compile error at the line calling `arg.myMethod` since the compiler cannot verify that `arg` has a member named `myMethod` without any knowledge on `T`. This is a major difference of Slang's generics compared to _templates_ in C++. + +While C++ templates are a powerful language mechanism, Slang has followed the path of many other modern programming languages to adopt the more structural and restricted generics feature instead. This enables the Slang compiler to perform type checking early to give more readable error messages, and to speed-up compilation by reusing a lot of work for different instantiations of `myGenericMethod`. + + +Supported Constructs in Interface Definitions +----------------------------------------------------- + +Slang supports many other constructs in addition to ordinary methods as a part of an interface definition. + +### Properties + +```csharp +interface IFoo +{ + property int count {get; set;} +} +``` +The above listing declares that any conforming type must define a property named `count` with both a `getter` and a `setter` method. + +### Generic Methods + +```csharp +interface IFoo +{ + int compute(T val); +} +``` +The above listing declares that any conforming type must define a generic method named `compute` that has one generic type parameter conforming to the `IBar` interface. + +### Static Methods + +```csharp +interface IFoo +{ + static int compute(int val); +}; +``` + +The above listing declares that any conforming type must define a static method named `compute`. This allows the following generic method to pass type-checking: +```csharp +void f() +{ + T.compute(5); // OK, T has a static method `compute`. +} +``` + +### Static Constants + +You can define static constant requirements in an interface. The constants can be accessed in places where a compile-time constant is needed. +```csharp +interface IMyValue +{ + static const int value; +} +struct MyObject2 : IMyValue +{ + static const int value = 2; +} +struct GetValuePlus1 +{ + static const int value = T.value + 1; +} + +static const int result = GetValuePlus1.value; // result == 3 +``` + +### `This` Type + +You may use a special keyword `This` in interface definitions to refer to the type that is conforming to the interface. The following examples demonstrate a use of `This` type: +```csharp +interface IComparable +{ + int comparesTo(This other); +} +struct MyObject : IComparable +{ + int val; + int comparesTo(MyObject other) + { + return val < other.val ? -1 : 1; + } +} +``` +In this example, the `IComparable` interface declares that any conforming type must provide a `comparesTo` method that performs a comparison between an object to another object of the same type. The `MyObject` type satisfies this requirement by providing a `comparesTo` method that accepts a `MyObject` typed argument, since in the scope of `MyObject`, `This` type is equivalent to `MyObject`. + +### Initializers + +Consider a generic method that wants to create and initialize a new instance of generic type `T`: +```csharp +void f() +{ + T obj = /*a newly initialized T*/ +} +``` +One way to implement this is to introduce a static method requirement in `IFoo`: +```csharp +interface IFoo +{ + static This create(); +} +``` +With this interface definition, we can define `f` as following: +```csharp +void f() +{ + T obj = T.create(); +} +``` + +This solution works just fine, but it would be nicer if you can just write: +```csharp +T obj = T(); +``` +Or simply +```csharp +T obj; +``` +And let the compiler invoke the default initializer defined in the type. +To enable this, you can include an initializer requirement in the interface definition: +```csharp +interface IFoo +{ + __init(); +} +``` + +Initializers with parameters are supported as well. For example: +```csharp +interface IFoo +{ + __init(int a, int b); +} +void g() +{ + T obj = {1, 2}; // OK, invoking the initializer on T. +} +``` + +Associated Types +------------------------- + +When writing code using interfaces and generics, there are some situations where the an interface method needs to return an object whose type is implementation-dependent. For example, consider the following `IFloatContainer` interface that represents a container of `float` values: +```csharp +// Represents a container of float values. +interface IFloatContainer +{ + // Returns the number of elements in this container. + uint getCount(); + // Returns an iterator representing the start of the container. + Iterator begin(); + // Returns an iterator representing the end of the container. + Iterator end(); + // Return the element at the location represented by `iter`. + float getElementAt(Iterator iter); +} +``` +An implementation of the `IFloatContainer` interface may use different types of iterators. For example, an implementation that is simply an array of `float`s can expose `Iterator` as a simple integer index: +```csharp +struct ArrayFloatContainer : IFloatContainer +{ + float content[10]; + uint getCount() { return 10; } + uint begin() { return 0; } + uint end() { return 10; } + float getElementAt(uint iter) { return content[iter]; } +} +``` +On the other hand, an implementation that uses multiple buffers as the backing storage may use a more complex type to locate an element: +```csharp +// Exposes values in two `StructuredBuffer`s as a single container. +struct MultiArrayFloatContainer : IFloatContainer +{ + StructuredBuffer firstBuffer; + StructuredBuffer secondBuffer; + uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); } + + // `uint2.x` indicates which buffer, `uint2.y` indicates the index within the buffer. + uint2 begin() { return uint2(0,0); } + uint2 end() { return uint2 (1, getBufferSize(secondBuffer)); } + float getElementAt(uint2 iter) + { + if (iter.x == 0) return firstBuffer[iter.y]; + else return secondBuffer[iter.y]; + } +} +``` + +Ideally, a generic function that wishes to enumerate values in a `IFloatContainer` shouldn't need to care about the implementation details on what the concrete type of `Iterator` is, and we would like to be able to write the following: +```csharp +float sum(T container) +{ + float result = 0.0f; + for (T.Iterator iter = container.begin(); iter != container.end(); iter=iter.next()) + { + float val = container.getElementAt(iter); + result += val; + } + return result; +} +``` +Here the `sum` function simply wants to access all the elements and sum them up. The details of what the `Iterator` type actually is does not matter to the definition of `sum`. + +The problem is that the `IFloatContainer` interface definition requires methods like `begin()`, `end()` and `getElementAt()` to refer to a iterator type that is implementation dependent. How should the signature of these methods be defined in the interface? The answer is to use _associated types_. + +In addition to constructs listed in the previous section, Slang also supports defining associated types in an `interface` definition. An associated type can be defined as following. +```csharp +// The interface for an iterator type. +interface IIterator +{ + // An iterator needs to know how to move to the next element. + This next(); +} + +interface IFloatContainer +{ + // Requires an implementation to define a typed named `Iterator` that + // conforms to the `IIterator` interface. + associatedtype Iterator : IIterator; + + // Returns the number of elements in this container. + uint getCount(); + // Returns an iterator representing the start of the container. + Iterator begin(); + // Returns an iterator representing the end of the container. + Iterator end(); + // Return the element at the location represented by `iter`. + float getElementAt(Iterator iter); +}; +``` + +This `associatedtype` definition in `IFloatContainer` requires that all types conforming to this interface must also define a type in its scope named `Iterator`, and this iterator type must conform to the `IIterator` interface. An implementation to the `IFloatContainer` interface by using either a `typedef` declaration or a `struct` definition inside its scope to satisfy the associated type requirement. For example, the `ArrayFloatContainer` can be implemented as following: +```csharp +struct ArrayIterator : IIterator +{ + uint index; + __init(int x) { index = x; } + ArrayIterator next() + { + return ArrayIterator(index + 1); + } +} +struct ArrayFloatContainer : IFloatContainer +{ + float content[10]; + + // Specify that the associated `Iterator` type is `ArrayIterator`. + typedef ArrayIterator Iterator; + + Iterator getCount() { return 10; } + Iterator begin() { return ArrayIterator(0); } + Iterator end() { return ArrayIterator(10); } + float getElementAt(Iterator iter) { return content[iter.index]; } +} +``` + +Alternatively, you may also define the `Iterator` type directly inside a `struct` implementation, as in the following definition for `MultiArrayFloatContainer`: +```csharp +// Exposes values in two `StructuredBuffer`s as a single container. +struct MultiArrayFloatContainer : IFloatContainer +{ + // Represents an iterator of this container + struct Iterator : IIterator + { + // `index.x` indicates which buffer the element is located in. + // `index.y` indicates which the index of the element inside the buffer. + uint2 index; + + // We also need to keep a size of the first buffer so we know when to + // switch to the second buffer. + uint firstBufferSize; + + // Implementation of IIterator.next() + Iterator next() + { + Iterator result; + result.index.x = index.x; + result.index.y = index.y + 1; + // If we are at the end of the first buffer, + // move to the head of the second buffer + if (result.index.x == 0 && result.index.y == firstBufferSize) + { + result.index = uint2(1, 0); + } + return result; + } + } + + StructuredBuffer firstBuffer; + StructuredBuffer secondBuffer; + uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); } + + Iterator begin() + { + Iterator iter; + iter.index = uint2(0, 0); + iter.firstBufferSize = getBufferSize(firstBuffer); + return iter; + } + Iterator end() + { + Iterator iter; + iter.index = uint2(1, getBufferSize(secondBuffer)); + iter.firstBufferSize = 0; + return iter; + } + float getElementAt(Iterator iter) + { + if (ite.indexr.x == 0) return firstBuffer[iter.index.y]; + else return secondBuffer[iter.index.y]; + } +} +``` + +In summary, an `asssociatedtype` requirement in an interface is similar to other types of requirements: a method requirement means that an implementation must provide a method matching the interface signature, while an `associatedtype` requirement means that an implementation must provide a type in its scope with the matching name and interface constraint. In general, when defining an interface that is producing and consuming an object whose actual type is implementation-dependent, the type of this object can often be modeled as an associated type in the interface. + +### Comparison to the C++ Approach +Readers who are familiar with C++ could easily relate the `Iterator` example in previous subsection to the implementation of STL. In C++, the `sum` function can be easily written with templates: +```C++ +template +float sum(const TContainer& container) +{ + float result = 0.0f; + // Assumes `TContainer` has a type `Iterator` that supports `operator++`. + for (TContainer::Iterator iter = container.begin(); iter != container.end(); ++iter) + { + result += container.getElementAt(iter); + } + return result; +} +``` + +A C++ programmer can implement `ArrayFloatContainer` as following: +```C++ +struct ArrayFloatContainer +{ + float content[10]; + + typedef uint32_t Iterator; + + Iterator getCount() { return 10; } + Iterator begin() { return 0; } + Iterator end() { return 10; } + float getElementAt(Iterator iter) { return content[iter]; } +}; +``` +Because C++ does not require a template function to define _constraints_ on the templated type, there are no interfaces or inheritances involved in the definition of `ArrayFloatContainer`. However `ArrayFloatContainer` still needs to define what its `Iterator` type is, so the `sum` function can be successfully specialized with an `ArrayFloatContainer`. + +Note that the biggest difference between C++ templates and generics is that templates are not type-checked prior to specialization, and therefore the code that consumes a templated type (`TContainer` in this example) can simply assume `container` has a method named `getElementAt`, and the `TContainer` scope provides a type definition for `TContainer::Iterator`. Compiler error only arises when the programmer is attempting to specialize the `sum` function with a type that does not meet these assumptions. Contrarily, Slang requires all possible uses of a generic type be declared through an interface. By stating that `TContainer:IContainer` in the generics declaration, the Slang compiler can verify that `container.getElementAt` is calling a valid function. Similarily, the interface also tells the compiler that `TContainer.Iterator` is a valid type and enables the compiler to fully type check the `sum` function without specializing it first. + +### Similarity to Swift and Rust + +Slang's `associatedtype` shares the same semantic meaning with `associatedtype` in a Swift `protocol` or `type` in a Rust `trait`, except that Slang currently does not support the more general `where` clause in these languages. C# does not have an equivalent to `associatedtype`, and programmers need to resort to generic interfaces to achieve similar goals. + +Generic Value Parameters +------------------------------- + +So far we have demonstrated generics with _type parameters_. Additionally, Slang also supports generic _value_ parameters. +The following listing shows an example of generic value parameters. +```csharp +struct Array +{ + T arrayContent[N]; +} +``` +In this example, the `Array` type has a generic type parameter, `T`, that is used as the element type of the `arrayContent` array, and a generic value parameter `N` of integer type. + +Note that the builtin `vector` type also has an generic value parameter `N`. + +> #### Note #### +> The only type of generic value parameters are `int`, `uint` and `bool`. `float` and +> other types cannot be used in a generic value parameter. Computations in a type +> expression are supported as long as they can be evaluated at compile time. For example, +`vector` is allowed and considered equivalent to `vector`. + + +Interface-typed Values +------------------------------- + +So far we have been using interfaces as constraints to generic type parameters. For example, the following listing defines a generic function with a type parameter `TTransform` constrained by interface `ITransform`: + +```csharp +interface ITransform +{ + int compute(MyObject obj); +} + +// Defining a generic method: +int apply(TTransform transform, MyObject object) +{ + return transform.compute(object); +} +``` + +While Slang's syntax for defining generic methods bears similarity to generics in C#/Java and templates in C++ and should be easy to users who are familiar with these languages, codebases that make heavy use of generics can quickly become verbose and difficult to read. To reduce the amount of boilerplate, Slang supports an alternate way to define the `apply` method by using the interface type `ITransform` as parameter type directly: + +```csharp +// A method that is equivalent to `apply` but uses simpler syntax: +int apply_simple(ITransform transform, MyObject object) +{ + return transform.compute(object); +} +``` + +Instead of defining a generic type parameter `TTransform` and a method parameter `transform` that has `TTransform` type, you can simply define the same `apply` function like a normal method, with a `transform` parameter whose type is an interface. From the Slang compiler's view, `apply` and `apply_simple` will be compiled to the same target code. + +In addition to parameters, Slang allows variables, and function return values to have an interface type as well: +```csharp +ITransform test(ITransform arg) +{ + ITransform v = arg; + return v; +} +``` + +### Restrictions and Caveats + +The Slang compiler always attempts to determine the actual type of an interface-typed value at compile time and specialize the code with the actual type. As long as the compiler can successfully determine the actual type, code that uses interface-typed values are equivalent to code written in the generics syntax. However, when interface types are used in function return values, the compiler will not be able to trivially propagate type information. For example: +```csharp +ITransform getTransform(int x) +{ + if (x == 0) + { + Type1Transform rs = {}; + return rs; + } + else + { + Type2Transform rs = {}; + return rs; + } +} +``` +In this example, the actual type of the return value is dependent on the value of `x`, which may not be known at compile time. This means that the concrete type of the return value at invocation sites of `getTransform` may not be statically determinable. When the Slang compiler cannot infer the concrete type of an interface-type value, it will generate code that performs a dynamic dispatch based on the concrete type of the value at runtime, which may introduce performance overhead. Note that this behavior applies to function return values in the form of `out` parameters as well: + +```csharp +void getTransform(int x, out ITransform transform) +{ + if (x == 0) + { + Type1Transform rs = {}; + transform = rs; + } + else + { + Type2Transform rs = {}; + transform = rs; + } +} +``` +This `getTransform` definition can also result in dynamic dispatch code since the type of `transform` may not be statically determinable. + +When the compiler is generating dynamic dispatch code for interface-typed values, it requires the concrete type of the interface-typed value to be free of any opaque-typed fields (e.g. resources and buffer types). A compiler error will generated upon such attempts: +```csharp +struct MyTransform : ITransform +{ + StructuredBuffer buffer; + int compute(MyObject obj) + { + return buffer[0]; + } +} + +ITransform getTransform(int x) +{ + MyTransform rs; + // Error: cannot use an opaque value as an interface-typed return value. + return rs; +} +``` + +Assigning different values to a mutable interface-typed variable also undermines the compiler's ability to statically determine the type of the variable, and is not supported by the Slang compiler today: +```csharp +void test(int x) +{ + ITransform t = Type1Transform(); + // Do something ... + // Assign a different type of transform to `t`: + // (Not supported by Slang today) + t = Type2Transform(); + // Do something else... +} +``` + +In general, if the use of interface-typed values is restricted to function parameters only, then the all code that involves interface-typed values will be compiled the same way as if the code is written using standard generics syntax. + + +Extending a Type with Additional Interface Conformances +----------------------------- +In the previous chapter, we introduced the `extension` feature that lets you define new members to an existing type in a separate location outside the original definition of the type. + +`extensions` can be used to make an existing type conform to additional interfaces. Suppose we have an interface `IFoo` and a type `MyObject` that implements the interface: + +```csharp +interface IFoo +{ + int foo(); +}; + +struct MyObject : IFoo +{ + int foo() { return 0; } +} +``` + +Now we introduce another interface, `IBar`: +```csharp +interface IBar +{ + float bar(); +} +``` + +We can define an `extension` to make `MyObject` conform to `IBar` as well: +```csharp +extension MyObject : IBar +{ + float bar() { return 1.0f } +} +``` + +With this extension, we can use `MyObject` in places that expects an `IBar` as well: +```csharp +void use(IBar b) +{ + b.bar(); +} + +void test() +{ + MyObject obj; + use(obj); // OK, `MyObject` is extended to conform to `IBar`. +} +``` + +You may define more than one interface conformances in a single `extension`: +```csharp +interface IBar2 +{ + float bar2(); +} +extension MyObject : IBar, IBar2 +{ + float bar() { return 1.0f } + float bar2() { return 2.0f } +} +``` + +`is` and `as` Operator +---------------------------- + +You can use `is` operator to test if an interface-typed value is of a specific concrete type, and use `as` operator to downcast the value into a specific type. +The `as` operator returns an `Optional` that is not `none` if the downcast succeeds. + +```csharp +interface IFoo +{ + int foo(); +} +struct MyImpl : IFoo +{ + int foo() { return 0; } +} +void test(IFoo foo) +{ + bool t = foo is MyImpl; // true + Optional optV = foo as MyImpl; + if (t == (optV != none)) + printf("success"); + else + printf("fail"); +} +void main() +{ + MyImpl v; + test(v); +} +// Result: +// "success" +``` + + +Extensions to Interfaces +----------------------------- + +In addtion to extending ordinary types, you can define extensions on interfaces as well: +```csharp +// An example interface. +interface IFoo +{ + int foo(); +} + +// Extending `IFoo` with a new method requirement +// with a default implementation. +extension IFoo +{ + int bar() { return 0; } +} + +int use(IFoo foo) +{ + // With the extension, all uses of `IFoo` typed values + // can assume there is a `bar` method. + return foo.bar(); +} +``` + +Although the syntax of above listing suggests that we are extending an interface with additional requirements, this interpretation does not make logical sense in many ways. Consider a type `MyType` that exists before the extension is defined: +```csharp +struct MyType : IFoo +{ + int foo() { return 0; } +} +``` + +If we extend the `IFoo` with new requirements, the existing `MyType` definition would become invalid since `MyType` no longer provides implementations to all interface requirements. Instead, what an `extension` on an interface `IFoo` means is that for all types that conforms to the `IFoo` interface and does not have a `bar` method defined, add a `bar` method defined in this extension to that type so that all `IFoo` typed values have a `bar` method defined. If a type already defines a matching `bar` method, then the existing method will always override the default method provided in the extension: + +```csharp +interface IFoo +{ + int foo(); +} +struct MyFoo1 : IFoo +{ + int foo() { return 0; } +} +extension IFoo +{ + int bar() { return 0; } +} +struct MyFoo2 : IFoo +{ + int foo() { return 0; } + int bar() { return 1; } +} +void test() +{ + MyFoo1 f1; + MyFoo2 f2; + int a = f1.bar(); // a == 0, calling the method in the extension. + int b = f2.bar(); // b == 1, calling the existing method in `MyFoo2`. +} +``` +This feature is similar to extension traits in Rust. diff --git a/docs/user-guide/06-compiling.md b/docs/user-guide/06-compiling.md new file mode 100644 index 000000000..5c19175d6 --- /dev/null +++ b/docs/user-guide/06-compiling.md @@ -0,0 +1,497 @@ +--- +layout: user-guide +--- + +Compiling Code with Slang +========================= + +This chapter presents the ways that the Slang system supports compiling and composing shader code. +We will start with a discussion of the mental model that Slang uses for compilation. +Next we will cover the command-line Slang compiler, `slangc`, and how to use it to perform offline compilation. +Finally we will discuss the Slang compilation API, which can be used to integrate Slang compilation into an application at runtime, or to build custom tools that implement application-specific compilation policy. + +Concepts +-------- + +For simple scenarios it may be enough to think of a shader compiler as a box where source code goes in and compiled kernels come out. +Most real-time graphics applications end up needing more control over shader compilation, and/or more information about the results of compilation. +In order to make use of the services provided by the Slang compilation system, it is useful to start with a clear model of the concepts that are involved in compilation. + +### Source Units + +At the finest granularity, code is fed to the compiler in _source units_ which are most often stored as files on disk or strings of text in memory. +The compilation model largely does not care whether source units have been authored by human programmers or automatically assembled by other tools. + +If multiple source units are specified as part of the same compile, they will be preprocessed and parsed independently. +However, a source unit might contain `#include` directives, so that the preprocessed text of that source unit includes the content of other files. +Note that the `#include`d files do not become additional source units; they are just part of the text of a source unit that was fed to the compiler. + +### Translation Units and Modules + +Source units (such as files) are grouped into _translation units_, and each translation unit will produce a single _module_ when compiled. + +While the source units are all preprocessed and parsed independently, semantic checking is applied to a translation unit as a whole. +One source file in a translation unit may freely refer to declarations in another translation unit without any need for forward declarations. For example: + +```hlsl +// A.slang + +float getFactor() { return 10.0; } +``` + +```hlsl +// B.slang + +float scaleValue(float value) +{ + return value * getFactor(); +} +``` + +In this example, the `scaleValue()` function in `B.slang` can freely refer to the `getFactor()` function in `A.slang` because they are part of the same translation unit. + +It is allowed, and indeed common, for a translation unit to contain only a single source unit. +For example, when adapting an existing codebase with many `.hlsl` files, it is appropriate to compile each `.hlsl` file as its own translation unit. +A modernized codebase might decide to compile multiple `.slang` files in a single directory as a single translation unit. + +The result of compiling a translation unit is a module in Slang's internal intermediate representation (IR). + +### Entry Points + +A translation unit / module may contain zero or more entry points. +Slang supports two models for identifying entry points when compiling. + +#### Entry Point Attributes + +By default, the compiler wll scan a translation unit for function declarations marked with the `[shader(...)]` attribute; each such function will be identified as an entry point in the module. +Developers are encouraged to use this model because it makes directly documents intention and makes source code less dependent on external compiler configuration options. + +#### Explicit Entry Point Options + +For compatibility with existing code, the Slang compiler also supports explicit specification of entry point functions using configuration optiosn external to shader source code. +When these options are used the compiler will *ignore* all `[shader(...)]` attributes and only use the explicitly-specified entry points intead. + +### Shader Parameters + +A translation unit / module may contain zero or more global shader parameters. +Similarly, each entry point may define zero or more entry-point `uniform` shader parameters. + +The shader parameters of a module or entry point are significant because they describe the interface between host application code and GPU code. +It is important that both the application and generated GPU kernel code agree on how parameters are laid out in memory and/or how they are assigned to particular API-defined registers, locations, or other "slots." + +### Targets + +Within the Slang system a _target_ represents a particular platform and set of capabilities that output code can be generated for. +A target includes information such as: + +* The _format_ that code should be generated in: SPIR-V, DXIL, etc. + +* A _profile_ that specifies a general feature/capability level for the target: D3D Shader Model 5.1, GLSL version 4.60, etc. + +* Optional _capabilities_ that should be assumed available on the target: for example, specific Vulkan GLSL extensions + +* Options that impact code generation: floating-point strictness, level of debug information to generate, etc. + +Slang supports compiling for multiple targets in the same compilation session. +When using multiple targets at a time, it is important to understand the distinction between the _front-end_ of the compiler, and the _back-end_: + +* The compiler front-end comprises preprocessing, parsing, and semantic checking. The front-end runs once for each translation unit and its results are shared across all targets. + +* The compiler back-end generates output code, and thus runs once per target. + +> #### Note #### +> Because front-end actions, including preprocessing, only run once, across all targets, the Slang compiler does not automatically provide any target-specific preprocessor `#define`s that can be used for preprocessor conditionals. +> Applications that need target-specific `#define`s should always compile for one target at a time, and set up their per-target preprocessor state manually. + +### Layout + +While the front-end of the compiler determines what the shader parameters of a module or entry point are, the _layout_ for those parameters is dependent on a particular compilation target. +A `Texture2D` might consume a `t` register for Direct3D, a `binding` for Vulkan, or just plain bytes for CUDA. + +The details of layout in Slang will come in a later chapter. +For the purposes of the compilation model it is important to note that the layout computed for shader parameters depends on: + +* What modules and entry points are being used together; these define which parameters are relevant. + +* Some well-defined ordering of those parameters; this defines which parameters should be laid out before which others. + +* The rules and constraints that the target imposes on layout. + +An important design choice in Slang is give the user of the compiler control over these choices. + +### Composition + +The user of the Slang compiler communicates the modules and entry points that will be used together, as well as their relative order, using a system for _composition_. + +A _component type_ is a unit of shader code composition; both modules and entry points are examples of component types. +A _composite_ component type is formed from a list of other component types (for example, one module and two entry points) and can be used to define a unit of shader code that is meant to be used together. + +Once a programmer has formed a composite of all the code they intend to use together, they can query the layout of the shader parameters in that composite, or request kernel code generation for its entry points. + +### Kernels + +A _kernel_ is generated code for an entry point. +The same entry point can be used to generate many different kernels. +First, and entry point can be compiled for different targets, resulting in different kernels in the appropriate format for each target. +Second, different compositions of shader code can result in different layouts, which leads to different kernels being required. + +Command-Line Compilation with `slangc` +-------------------------------------- + +The `slangc` tool, included in binary distributions of Slang, is a command-line compiler that can handle most simple compilation tasks. +`slangc` is intended to be usable as a replacement for tools like `fxc` and `dxc`, and covers most of the same use cases. + +### Example + +Here we will repeat the example used in the [Getting Started](01-get-started.md) chapter. +Given the following Slang code: + +```hlsl +// hello-world.slang +StructuredBuffer buffer0; +StructuredBuffer buffer1; +RWStructuredBuffer result; + +[shader("compute")] +[numthreads(1,1,1)] +void computeMain(uint3 threadId : SV_DispatchThreadID) +{ + uint index = threadId.x; + result[index] = buffer0[index] + buffer1[index]; +} +``` + +we can compile the `computeMain()` entry point to SPIR-V using the following command line: + +```bat +slangc hello-world.slang -entry computeMain -target spirv -o hello-world.spv +``` + +### Source Files and Translation Units + +The `hello-world.slang` argument here is specifying an input file. +Each input file specified on the command line will be a distinct source unit during compilation. +Slang supports multiple file-name extensions for input files, but the most common ones will be `.hlsl` for existing HLSL code, and `.slang` for files written specifically for Slang. + +If multiple source files are passed to `slangc`, they will be grouped into translation units using the following rules: + +* If there are any `.slang` files, then all of them will be grouped into a single translation unit + +* Each `.hlsl` file will be grouped into a distinct translation unit of its own + +### Entry Points + +When using `slangc`, you will typically want to identify which entry point(s) you intend to compile. +The `-entry computeMain` option selects an entry point to be compiled to output code in this invocation of `slangc`. + +Because the `computeMain()` entry point in this example has a `[shader(...)]` attribute, the compiler is able to deduce that it should be compiled for the `compute` stage. +In code that does not use `[shader(...)]` attributes, a `-entry` option should be followed by a `-stage` option to specify the stage of the entry point: + +```bat +slangc hello-world.slang -entry computeMain -stage compute -o hello-world.spv +``` + +### Targets + +Our example uses the option `-target spirv` to introduce a compilation target; in this case, code will be generated as SPIR-V. +The argument of a `-target` option specified the format to use for the target; common values are `dxbc`, `dxil`, and `spirv`. + +Additional options for a target can be specified after the `-target` option. +For example, a `-profile` option can be used to specify a profile that should be used. +Slang provides two main kinds of profiles for use with `slangc`: + +* Direct3D "Shader Model" profiles have names like `sm_5_1` and `sm_6_3` + +* GLSL versions can be used as profile with names like `glsl_430` and `glsl_460` + +### Kernels + +A `-o` option indicates that kernel code should be written to a file on disk. +In our example, the SPIR-V kernel code for the `computeMain()` entry point will be written to the file `hello-world.spv`. + +### Working with Multiples + +It is possible to use `slangc` with multiple input files, entry points, or targets. +In these cases, the ordering of arguments on the command line becomes significant. + +When an option modifies or relates to another command-line argument, it implicitly applies to the most recent relevant argument. +For example: + +* If there are multiple input files, then an `-entry` option applies to the preceding input file + +* If there are multiple entry points, then a `-stage` option applies to the preceding `-entry` option + +* If there are multiple targets, then a `-profile` option applies to the preceding `-target` option + +Kernel `-o` options are the most complicated case, because they depend on both a target and entry point. +A `-o` option applies to the preceding entry point, and the compiler will try to apply it to a matching target based on its file extension. +For example, a `.spv` output file will be matched to a `-target spriv`. + +The compiler makes a best effort to support complicated cases with multiple files, entry points, and targets. +Users with very complicated compilation requirements will probably be better off using multiple `slangc` invocations or migrating to the compilation API. + +### Additional Options + +The main other options are: + +* `-D` or `-D=` can be used to introduce preprocessor macros. + +* `-I` or `-I ` can be used to introduce a _search path_ to be used when resolving `#include` directives and `import` declarations. + +* `-g` can be used to enable inclusion of debug information in output files (where possible and implemented) + +* `-O` can be used to control optimization levels when the Slang compiler invokes downstream code generator + +### Convenience Features + +The `slangc` compiler provides a few conveniences for command-line compilation: + +* Most options can appear out of order when they are unambiguous. For example, if there is only a single translation unit a `-entry` option can appear before or after any file. + +* A `-target` option can be left out if it can be inferred from the only `-o` option present. For example, `-o hello-world.spv` already implies `-target spriv`. + +* If a `-o` option is left out then kernel code will be written to the standard output. This output can be piped to a file, or can be printed to a console. In the latter case, the compiler will automatically disassemble binary formats for printing. + +### Limitations + +The `slangc` tool is meant to serve the needs of many developers, including those who are currently using `fxc`, `dxc`, or similar tools. +However, some applications will benefit from deeper integration of the Slang compiler into application-specific code and workflows. +Notable features that Slang supports which cannot be accessed from `slangc` include: + +* Slang can provide _reflection_ information about shader parameters and their layouts for particular targets; this information is not currently output by `slangc`. + +* Slang allows applications to control the way that shader modules and entry points are composed (which in turn influences their layout); `slangc` currently implements a single default policy for how to generate a composition of shader code. + +Applications that more control over compilation are encouraged to use the C++ compilation API described in the next section. + +Using the Compilation API +------------------------- + +The C++ API provided by Slang is meant to provide more complete control over compilation for applications that need it. +The additional level of control means that some tasks require more individual steps than they would when using a one-size-fits-all tool like `slangc`. + +### "COM-lite" Components + +Many parts of the Slang C++ API use interfaces that follow the design of COM (the Component Object Model). +Some key Slang interfaces are binary-compatible with existing COM interfaces. +However, the Slang API does not depend on any runtime aspects of the COM system, even on Windows; the Slang system can be seen as a "COM-lite" API. + +The `ISlangUnknown` interface is equivalent to (and binary-compatible with) the standard COM `IUnknown`. +Application code is expected to correctly maintain the reference counts of `ISlangUnknown` objects returned from API calls; the `SlangComPtr` "smart pointer" type is provided as an optional convenience for applications that want to use it. + +Many Slang API calls return `SlangResult` values; this type is equivalent to (and binary-compatible with) the standard COM `HRESULT` type. +As a matter of convention, Slang API calls return a zero value (`SLANG_OK`) on success, and a negative value on errors. + +### Creating a Global Session + +A Slang _global session_ uses the interface `slang::IGlobalSession` and it represents a connection from an application to a particular implementation of the Slang API. +A global session is created using the function `slang::createGlobalSession()`: + +```c++ +SlangComPtr globalSession; +slang::createGlobalSession(globalSession.writeRef()); +``` + +When a global session is created, the Slang system will load its internal representation of the _standard library_ that the compiler provides to user code. +The standard library can take a significant amount of time to load, so applications are advised to use a single global session if possible, rather than creating and then disposing of one for each compile. + +> #### Note #### +> Currently, the global session type is *not* thread-safe. +> Applications that wish to compile on multiple threads will need to ensure that each concurrent thread compiles with a distinct global session. + +### Creating a Session + +A _session_ uses the interface `slang::ISession`, and represents a scope for compilation with a consistent set of compiler options. +In particular, all compilation with a single session will share: + +* A list of enabled compilation targets (with their options) + +* A list of search paths (for `#include` and `import`) + +* A list of pre-defined macros + +In addition, a session provides a scope for the loading and re-use of modules. +If two pieces of code compiled in a session both `import` the same module, then that module will only be loaded and compiled once. + +To create a session, use the `IGlobalSession::createSession()` method: + +```c++ +SessionDesc sessionDesc; +/* ... fill in `sessionDesc` ... */ +SlangComPtr session; +globalSession->createSession(sessionDesc, session.writeRef()); +``` + +#### Targets + +The `SessionDesc::targets` array can be used to describe the list of targets that the application wants to support in a session. +Often, this will consist of a single target. + +Each target is described with a `TargetDesc` which includes options to control code generation for the target. +The most important fields of the `TargetDesc` are the `format` and `profile`; most others can be left at their default values. + +The `format` field should be set to one of the values from the `SlangCompileTarget` enumeration. +For example: + +```c++ +TargetDesc targetDesc; +targetDesc.format = SLANG_FORMAT_SPIRV; +``` + +The `profile` field must be set with the ID of one of the profiles supported by the Slang compiler. +The exact numeric value of the different profiles is not currently stable across compiler versions, so applications should look up a chosen profile using `IGlobalSession::findProfile`. +For example: + +```c++ +targetDesc.profile = globalSession->findProfile("glsl_450"); +``` + +Once the chosen `TargetDesc`s have been initialized, they can be attached to the `SessionDesc`: + +```c++ +sessionDesc.targets = &targetDesc; +sessionDesc.targetCount = 1; +``` + +#### Search Paths + +The search paths on a session provide the paths where the compiler will look when trying to resolve a `#include` directive or `import` declaration. +The search paths can be set in the `SessionDesc` as an array of `const char*`: + +```c++ +const char* searchPaths[] = { "myapp/shaders/" }; +sessionDesc.searchPaths = searchPaths; +sessionDesc.searchPathCount = 1; +``` + +#### Pre-Defined Macros + +The pre-defined macros in a session will be visible at the start of each source unit that is compiled, including source units loaded via `import`. +Each pre-defined macro is described with a `PreprocessorMacroDesc`, which has `name` and `value` fields: + +```c++ +PreprocessorMacroDesc fancyFlag = { "ENABLE_FANCY_FEATURE", "1" }; +sessionDesc.preprocessorMacros = &fancyFlag; +sessionDesc.preprocessorMacroCount = 1; +``` + +### Loading a Module + +The simplest way to load code into a session is with `ISession::loadModule()`: + +```c++ +SlangComPtr module = session->loadModule("MyShaders"); +``` + +Executing `loadModule("MyShaders")` in host C++ code is similar to using `import MyShaders` in Slang code. +The session will search for a matching module (usually in a file called `MyShaders.slang`) and will load and compile it (if it hasn't been done already). + +Note that `loadModule()` does not provide any ways to customize the compiler configuration for that specific module. +The preprocessor environment, search paths, and targets will always be those specified for the session. + +### Capturing Diagnostic Output + +Compilers produce various kinds of _diagnostic_ output when compiling code. +This includes not only error messages when compilation fails, but also warnings and other helpful messages that may be produced even for successful compiles. + +Many operations in Slang, such as `ISession::loadModule()` can optionally produce a _blob_ of diagnostic output. +For example: + +```c++ +SlangComPtr diagnostics; +SlangComPtr module = session->loadModule("MyShaders", diagnostics.writeRef()); +``` + +In this example, if any diagnostic messages were produced when loading `MyShaders`, then the `diagnostics` pointer will be set to a blob that contains the textual content of those diagnostics. + +The content of a blob can be accessed with `getBufferPointer()`, and the size of the content can be accessed with `getBufferSize()`. +Diagnostic blobs produces by the Slang compiler are always null-terminated, so that they can be used with C-style sting APIs: + +```c++ +if(diagnostics) +{ + fprintf(stderr, "%s\n", (const char*) diagnostics->getBufferPointer()); +} +``` + +> #### Note #### +> The `slang::IBlob` interface is binary-compatible with the `ID3D10Blob` and `ID3DBlob` interfaces used by some Direct3D compilation APIs. + +### Entry Points + +When using `loadModule()` applications should ensure that entry points in their shader code are always marked with appropriate `[shader(...)]` attributes. +For example, if `MyShaders.slang` contained: + +```hlsl +[shader("compute")] +void myComputeMain(...) { ... } +``` + +then the Slang system will automatically detect and validate this entry point as part of a `loadModule("MyShaders")` call. + +After a module has been loaded, the application can look up entry points in that module using `IModule::findEntryPointByName()`: + +```c++ +SlangComPtr computeEntryPoint; +module->findEntryPointByName("myComputeMain", computeEntryPoint.writeRef()); +``` + +### Composition + +An application might load any number of modules with `loadModule()`, and those modules might contain any number of entry points. +Before GPU kernel code can be generated it is first necessary to decide which pieces of GPU code will be used together. + +Both `slang::IModule` and `slang::IEntryPoint` inherit from `slang::IComponentType`, because both can be used as components when composing a shader program. +A composition can be created with `ISession::createCompositeComponentType()`: + +```c++ +IComponentType* components[] = { module, entryPoint }; +SlangComPtr program; +session->createCompositeComponentType(components, 2, program.writeRef()); +``` + +As discussed earlier in this chapter, the composition operation serves two important purposes. +First, it establishes which code is part of a compiled shader program and which is not. +Second, it established an ordering for the code in a program, which can be used for layout. + +### Layout and Reflection + +Some applications need to perform reflection on shader parameters and their layout, whether at runtime or as part of an offline compilation tool. +The Slang API allows layout to be queried on any `IComponentType` using `getLayout()`: + +```c++ +slang::ProgramLayout* layout = program->getLayout(); +``` + +> #### Note #### +> In the current Slang API, the `ProgramLayout` type is not reference-counted. +> Currently, the lifetime of a `ProgramLayout` is tied to the `IComponentType` that returned it. +> An application must ensure that it retains the given `IComponentType` for as long as it uses the `ProgramLayout`. + +Note that because both `IModule` and `IEntryPoint` inherit from `IComponentType`, they can also be queried for their layouts individually. +The layout for a module comprises just its global-scope parameters. +The layout for an entry point comprises just its entry-point parameters (both `uniform` and varying). + +The details of how Slang computes layout, what guarantees it makes, and how to inspect the reflection information will be discussed in a later chapter. + +Because the layout computed for shader parameters may depend on the compilation target, the `getLayout()` method actually takes a `targetIndex` parameter that is the zero-based index of the target for which layout information is being queried. +This parameter defaults to zero as a convenience for the common case where applications use only a single compilation target at runtime. + +### Kernel Code + +Given a composed `IComponentType`, an application can extract kernel code for one of its entry points using `IComponentType::getEntryPointCode()`: + +```c++ +int entryPointIndex = 0; // only one entry point +int targetIndex = 0; // only one target +SlangComPtr kernelBlob; +program->getEntryPointCode( + entryPointIndex, + targetIndex, + kernelBlob.writeRef(), + diagnostics.writeRef()); +``` + +Any diagnostic messages related to back-end code generation (for example, if the chosen entry point requires features not available on the chosen target) will be written to `diagnostics`. +The `kernelBlob` output is a `slang::IBlob` that can be used to access the generated code (whether binary or textual). +In many cases `kernelBlob->getBufferPointer()` can be passed directly to the appropriate graphics API to load kernel code onto a GPU. diff --git a/docs/user-guide/06-targets.md b/docs/user-guide/06-targets.md deleted file mode 100644 index d8d71c9e7..000000000 --- a/docs/user-guide/06-targets.md +++ /dev/null @@ -1,366 +0,0 @@ ---- -layout: user-guide ---- - -Supported Compilation Targets -============================ - -This chapter provides a brief overview of the compilation targets supported by Slang, and their different capabilities. - -Background and Terminology --------------------------- - -### Code Formats - -When Slang compiles for a target platform one of the most important distinctions is the _format_ of code for that platform. -For a native CPU target, the format is typically the executable machine-code format for the processor family (for example, x86-64). -In contrast, GPUs are typically programmed through APIs that abstract over multiple GPU processor families and versions. -GPU APIs usually define an _intermediate language_ that sits between a high-level-language compiler like Slang and GPU-specific compilers that live in drivers for the API. - -### Pipelines and Stages - -GPU code execution occurs in the context of a _pipeline_. -A pipeline comprises one or more _stages_ and dataflow connections between them. -Some stages are _programmable_ and run a user-defined _kernel_ that has been compiled from a language like Slang, while others are _fixed-function_ and can only be configured, rather than programmed, by the user. -Slang supports three different pipelines. - -#### Rasterization - -The _rasterization_ pipeline is the original GPU rendering pipeline. -On current GPUs, the simplest rasterization pipelines have two programmable stages: a `vertex` stage and a `fragment` stage. -The rasterization pipeline is named after its most important fixed-function stage: the rasterizer, which determines the pixels covered by a geometric primitive, and emits _fragments_ covering those pixels, to be shaded. - -#### Compute - -The _compute_ pipeline is a simple pipeline with only one stage: a programmable `compute` stage. -As a result of being a single-stage pipeline the compute pipeline doesn't need to deal with many issues around inter-stage dataflow that other pipelines do. - -#### Ray Tracing - -A _ray tracing_ pipeline has multiple stages pertaining to the life cycle of a ray being traced through a scene of geometric primitives. -These can include an `intersection` stage to compute whether a ray intersects a geometry primitive, a `miss` stage that runs when a ray does not intersect any geometric object in a scene, etc. - -Note that some platforms support types and operations related to ray tracing that can run outside of the context of a dedicated ray tracing pipeline. -Just as applications can do computation outside of the dedicated compute pipeline, the use of ray tracing does not necessarily mean that a ray tracing pipeline is being used. - -### Shader Parameter Bindings - -The kernels that execute within a pipeline typically has access to four different kinds of data: - -* _Varying inputs_ coming from the system or from a preceding pipeline stage - -* _Varying outputs_ which will be passed along to the system or to a following pipeline stage - -* _Temporaries_ which are scratch memory or registers used by each invocation of the kernel and then dismissed on exit. - -* _Shader parameters_ (sometimes also called _uniform parameters_), which provide access to data from outside the pipeline dataflow - -The first three of these kinds of data are largely handled by the implementation of a pipeline. -In contrast, an application programmer typically needs to manually prepare shader parameters, using the appropriate mechanisms and rules for each target platform. - -On platforms that provide a CPU-like "flat" memory model with a single virtual address space, and where any kind of data can be stored at any address, passing shader parameters can be almost trivial. -Current graphics APIs provide far more complicated and less uniform mechanisms for passing shader parameters. - -A high-level language compiler like Slang handles the task of _binding_ each user-defined shader parameter to one or more of the parameter-passing resources defined by a target platform. -For example, the Slang compiler might bindg a global `Texture2D` parameter called `gDiffuse` to the `t1` register defined by the Direct3D 11 API. - -An application is responsible for passing the argument data for a parameter using the using the corresponding platform-specific resource it was bound to. -For example, an application should set the texture they want to use for `gDiffuse` to the `t1` register using Direct3D 11 API calls. - -#### Slots - -Historically, most graphics APIs have used a model where shader parameters are passed using a number of API-defined _slots_. -Each slot can store a single argument value of an allowed type. -Depending on the platform slots might be called "registers," "locations," "bindings," "texture units," or other similar names. - -Slots almost exclusively use opaque types: textures, buffers, etc. -On platforms that use slots for passing shader parameters, value of ordinary types like `float` or `int` need to be stored into a buffer, and then that buffer is passed via an appropriate slot. - -Although many graphics APIs use slots as an abstraction, the details vary greatly across APIs. -Different APIs define different kinds of slots, and the types of arguments that may be stored in those slots vary. -For example, one API might use two different kinds of slots for textures and buffers, while another uses a single kind of slot for both. -On some APIs each pipeline stage gets is own dedicated slots, while on others slots are shared across all stages in a pipeline. - -#### Blocks - -Newer graphics APIs typically provide a system for grouping related shader parameters into re-usable _blocks_. -Blocks might be referred to as "descriptor tables," "descriptor sets," or "argument buffers." -Each block comprises one or more slots (often called "descriptors") that can be used to bind textures, buffers, etc. - -Blocks are in turn set into appropriate slots provided by a pipeline. -Because a block can contain many different slots for textures or buffers, switching a pipeline argument from one block to another can effectively swap out a large number of shader parameters in one operation. -Thus, while blocks introduce a level of indirection to parameter setting, then can also enable greater efficiency when parameters are grouped into blocks according to frequency of change. - -#### Root Constants - -Most recent graphics APIs also allow for a small amount of ordinary data (meaning types like `float` and `int` but not opaque types like buffers or textures) to be passed to the pipeline as _root constants_ (also called "push constants"). - -Using root constants can eliminate some overheads from passing parameters of ordinary types via buffers. -Passing a single `float` using a root constant rather than a buffer obviously eliminates a level of indirection. -More importantly, though, using a root constant can avoid application code having to allocate and manage the lifetime of a buffer in a concurrent CPU/GPU program. - -Direct3D 11 ------------ - -Direct3D 11 (D3D11) is a older graphics API, but remains popular because it is much simpler to learn and use than some more recent APIs. -In this section we will give an overview of the relevant features of D3D11 when used as a target platform for Slang. -Subsequent sections about other APIs may describe them by comparison to D3D11. - -D3D11 kernels must be compiled to the DirectX Bytecode (DXBC) intermediate language. -A DXBC binary includes a hash/checksum computed using an undocumented algorithm, and the runtime API rejects kernels without a valid checksum. -The only supported way to generate DXBC is by compiling HLSL using the fxc compiler. - -### Pipelines - -D3D11 exposes two pipelines: rasterization and compute. - -The D3D11 rasterization pipeline can include up to five programmable stages, although some of them are optional: - -* The `vertex` stage (VS) transforms vertex data loaded from memory - -* The optional `hull` stage (HS) typically sets up and computes desired tessellation levels for a higher-order primitive - -* The optional `domain` stage (DS) evaluates a higher-order surface at domain locations chosen by a fixed-function tessellator - -* The optional `geometry` stage (GS) receives as input a primitive and can produce zero or more new primitives as output - -* The `fragment` stage transforms fragments produced by the fixed-function rasterizer, determining the values for those fragments that will be merged with values in zero or more render targets. The fragment stage is sometimes called a "pixel" stage (PS), even when it does not process pixels. - -### Parameter Passing - -Shader parameters are passed to each D3D11 stage via slots. -Each stage has its own slots of the following types: - -* **Constant buffers** are used for passing relatively small (4KB or less) amounts of data that will be read by GPU code. Constant bufers are passed via `b` registers. - -* **Shader resource views** (SRVs) include most textures, buffers, and other opaque resource types thare are read or sampled by GPU code. SRVs use `t` registers. - -* **Unordered access views** (UAVs) include textures, buffers, and other opaque resource types used for write or read-write operations in GPU code. UAVs use `u` registers. - -* **Samplers** are used to pass opaque texture-sampling stage, and use `s` registers. - -In addition, the D3D11 pipeline provides _vertex buffer_ slots and a single _index buffer_ slot to be used as the source vertex and index data that defines primitives. -User-defined varying vertex shader inputs are bound to _vertex attribute_ slots (referred to as "input elements" in D3D11) which define how data from vertex buffers should be fetched to provide values for vertex attributes. - -The D3D11 rasterization pipeline also provides a mechanism for specifying _render target views_ (RTVs) and _depth-stencil views_ (DSVs) that provide the backing storage for the pixels in a framebuffer. -User-defined fragment shader varying outputs (with `SV_Target` binding semantics) are bound to RTV slots. - -One notable detail of the D3D11 API is that the slots for fragment-stage UAVs and RTVs overlap. -For example, a fragment kernel cannot use both `u0` and `SV_Target0` at once. - -Direct3D 12 ------------ - -Direct3D 12 (D3D12) is the current major version of the Direct2D API. - -D3D12 kernels must be compiled to the DirectX Intermediate Language (DXIL). -DXIL is a layered encoding based off of LLVM bitcode; it introduces additional formatting rules and constraints which are loosely documented. -A DXIL binary may be signed, and the runtime API only accepts appropriately signed binaries (unless a developer mode is enabled on the host machine). -A DXIL validator `dxil.dll` is included in SDK releases, and this validator can sign binaries that pass validation. -While DXIL can in principle be generated from multiple compiler front-ends, support for other compilers is not prioritized. - -### Pipelines - -D3D12 includes rasterization and compute pipelines similar to those in D3D11. -Revisions to D3D12 have added additional stages to the rasterization pipeline, as well as a ray-tracing pipeline. - -#### Mesh Shaders - -> #### Note ### -> The Slang system does not currently support mesh shaders. - -The D3D12 rasterization pipeline provides alternative geometry processing stages that may be used as an alternative to the `vertex`, `hull`, `domain`, and `geometry` stages: - -* The `mesh` stage runs groups of threads which are responsible cooperating to produce both the vertex and index data for a _meshlet_ a bounded-size chunk of geometry. - -* The optional `amplification` stage precedes the mesh stage and is responsible for determining how many mesh shader invocations should be run. - -Compared to the D3D11 pipeline without tesselllation (hull and domain shaders), a mesh shader is kind of like a combined/generalized vertex and geometry shader. - -Compared to the D3D11 pipeline with tessellation, an amplification shader is kind of like a combined/generalized vertex and hull shader, while a mesh shader is kind of like a combined/generalized domain and geometry shader. - -#### Ray Tracing - -The DirectX Ray Tracing (DXR) feature added a ray tracing pipeline to D3D12. -The D3D12 ray tracing pipeline exposes the following programmable stages: - -* The ray generation (`raygeneration`) stage is similar to a compute stage, but can trace zero or more rays and make use of the results of those traces. - -* The `intersection` stage runs kernels to compute whether a ray intersects a user-defined primitive type. The system also includes a default intersector that handles triangle meshes. - -* The so-called any-hit (`anyhit`) stage runs on _candidate_ hits where a ray has intersected some geometry, but the hit must be either accepted or rejected by application logic. Note that the any-hit stage does not necessarily run on *all* hits, because configuration options on both scene geometry and rays can lead to these checks being bypassed. - -* The closest-hit (`closesthit`) stage runs a single _accepted_ hit for a ray; under typical circumstances this will be the closest hit to the origin of the ray. A typical closest-hit shader might compute the apparent color of a surface, similar to a typical fragment shader. - -* The `miss` stage runs for rays that do not find or accept any hits in a scene. A typical miss shader might return a background color or sample an environment map. - -* The `callable` stage allows user-defined kernels to be invoked like subroutines in the context of the ray tracing pipeline. - -Compared to existing rasterization and compute pipelines, an important difference in the design of the D3D12 ray tracing pipeline is that multiple kernels can be loaded into the pipeline for each of the programming stages. -The specific closest-hit, miss, or other kernel that runs for a given hit or ray is determined by indexing into an appropriate _shader table_, which is effectively an array of kernels. -The indexing into a shader table can depend on many factors including the type of ray, the type of geometry hit, etc. - -Note that DXR version 1.1 adds ray tracing types and operations that can be used outside of the dedicated ray tracing pipeline. -These new mechanisms have less visible impact for a programmer using or integrating Slang. - - -### Parameter Passing - -The mechanisms for parameter passing in D3D12 differ greatly from D3D11. -Most opaque types (texture, resources, samplers) must be set into blocks (D3D12 calls blocks "descriptor tables"). -Each pipeline supports a fixed amount of storage for "root parameters," and allows those root parameters to be configured as root constants, slots for blocks, or slots for a limited number of opaque types (primarily just flat buffers). - -Shader parameters are still grouped and bound to registers as in D3D11; for example, a `Texture2D` parameter is considered as an SRV and uses a `t` register. -D3D12 additionally associates binds shader parameters to "spaces" which are expressed similarly to registers (e.g., `space2`), but represent an orthogonal "axis" of binding. - -While shader parameters are bound registers and spaces, those registers and spaces do not directly correspond to slots provided by the D3D12 API the way registers do in D3D11. -Instead, the configuration of the root parameters and the correspondence of registers/spaces to root parameters, blocks, and/or slots are defined by a _pipeline layout_ that D3D12 calls a "root signature." - -Unlike D3D11, all of the stages in a D3D12 pipeline share the same root parameters. -A D3D12 pipeline layout can specify that certain root parameters or certain slots within blocks will only be accessed by a subset of stages, and can map the *same* register/space pair to different parameters/blocks/slots as long as this is done for disjoint subset of stages. - -#### Ray Tracing Specifics - -The D3D12 ray tracing pipeline adds a new mechanism for passing shader parameters. -In addition to allowing shader parameters to be passed to the entire pipeline via root parameters, each shader table entry provides storage space for passing argument data specific to that entry. - -Similar to the use of a pipline layout (root signature) to configure the use of root parameters, each kernel used within shader entries must be configured with a "local root signature" that defines how the storage space in the shader table entry is to be used. -Shader parameters are still bound to registers and spaces as for non-ray-tracing code, and the local root signature simply allows those same registers/spaces to be associated with locations in a shader table entry. - -One important detail is that some shader table entries are associated with a kernel for a single stage (e.g., a single miss shader), while other shader table entries are associated with a "hit group" consisting of up to one each of an intersection, any-hit, and closest-hit kernel. -Because multiple kernels in a hit group share the same shader table entry, they also share the configured slots in that entry for binding root constants, blocks, etc. - -Vulkan ------- - -Vulkan is a cross-platform GPU API for graphics and compute with a detailed specification produced by a multi-vendor standards body. -In contrast with OpenGL, Vulkan focuses on providing explicit control over as many aspects of GPU work as possible. -In contrast with OpenCL, Vulkan focuses first and foremost on the needs of real-time graphics developers. - -Vulkan requires kernels to be compiled to the SPIR-V intermediate language. -SPIR-V is a simple and extensible binary program format with a detailed specification; it is largely unrelated to earlier "SPIR" formats that were LLVM-based and loosely specified. -The SPIR-V format does not require signing or hashing, and is explicitly designed to allow many different tools to produce and manipulate the format. -Drivers that consume SPIR-V are expected to perform validation at load time. -Some choices in the SPIR-V encoding are heavily influenced by specific design choices in the GLSL language, and may require non-GLSL compilers to transform code to match GLSL idioms. - -### Pipelines - -Vulkan includes rasterization, compute, and ray tracing pipelines with the same set of stages as described for D3D12 above. - -### Parameter Passing - -Like D3D12, Vulkan uses blocks (called "descriptor sets") to organize groups of bindings for opaque types (textures, buffers, samplers). -Similar to D3D12, a Vulkan pipeline supports a limited number of slots for passing blocks to the pipeline, and these slots are shared across all stages. -Vulkan also supports a limited number of bytes reserved for passing root constants (called "push constants"). -Vulkan uses pipeline layouts to describe configurations of usage for blocks and root constants. - -High-level-language shader parameters are bound to a combination of a "binding" and a "set" for Vulkan, which are superficially similar to the registers and spaces of D3D12. -Unlike D3D12, however, bindings and sets in Vulkan directly correspond to the API-provided parameter-passing mechanism. -The set index of a parameter indicates the zero-based index of a slot where a block must be passed, and the binding index is the zero-based index of a particular opaque value set into the block. -A shader parameter that will be passed using root constants (rather than via blocks) must be bound to a root-constant offset as part of compilation. - -Unlike D3D12, where SRVs, UAVs, etc. use distinct classes of registers, all opaque-type shader parameters use the same index space of bindings. -That is, a buffer and a texture both using `binding=2` in `set=3` for Vulkan will alias the same slot in the same block. - -The Vulkan ray tracing pipeline also uses a shader table, and also forms hit groups similar to D3D12. -Unlike D3D12, each shader table entry in Vulkan can only be used to pass ordinary values (akin to root constants), and cannot be configured for binding of opaque types or blocks. - -OpenGL ------- - -> #### Note #### -> Slang has only limited support for compiling code for OpenGL. - -OpenGL has existed for many years, and predates programmable GPU pipelines of the kind this chapter discusses; we will focus solely on use of OpenGL as an API for programmable GPU pipelines. - -OpenGL is a cross-platform GPU API for graphics and compute with a detailed specification produced by a multi-vendor standard body. -In contrast with Vulkan, OpenGL provides many convenience and safety features that can simplify GPU programming. - -OpenGL allows kernels to be loaded as SPIR-V binaries, vendor-specific binaries, or using GLSL source code. -Loading shaders as GLSL source code is the most widely supported of these options, such that GLSL is the _de facto_ intermediate language of OpenGL. - -### Pipelines - -OpenGL supports rasterization and compute pipelines with the same stages as described for D3D11. -The OpenGL rasterization pipeline also supports the same mesh shader stages that are supported by D3D12. - -### Parameter Passing - -OpenGL uses slots for binding. -There are distinct kinds of slots for buffers and textures/images, and each set of slots is shared by all pipeline stages. - -High-level-language shader parameters are bounding to a "binding" index for OpenGL. -The binding index of a parameter is the zero-based index of the slot (of the appropriate kind) that must be used to pass an argument value. - -Note that while OpenGL and Vulkan both use binding indices for shader parameters like textures, the semantics of those are different because OpenGL uses distinct slots for passing buffers and textures. -For OpenGL is is legal to have a texture that uses `binding=2` and a buffer that uses `binding=2` in the same kernel, because those are indices of distinct kinds of slots, while this scenario would typically be invalid for Vulkan. - -CUDA and OptiX --------------- - -> #### Note #### -> Slang support for OptiX is a work in progress. - -CUDA C/C++ is a language for expressing heterogeneous CPU and GPU code with a simple interface to invoking GPU compute work. -OptiX is a ray tracing API that uses CUDA C++ as the language for expressing shader code. -We focus here on OptiX version 7 and up. - -CUDA and OptiX allow kernels to be loaded as GPU-specific binaries, or using the PTX intermediate language. - - -### Pipelines - -CUDA supports a compute pipeline that is similar to D3D12 or Vulkan, with additional features. - -OptiX introduced the style of ray tracing pipeline adopted by D3D12 and Vulkan, and thus uses the same basic stages. - -The CUDA system does not currently expose a rasterization pipeline. - -### Parameter Passing - -Unlike most of the GPU APIs discussed so far, CUDA supports a "flat" memory model with a single virtual address space for all GPU data. -Textures, buffers, etc. are not opaque types, but can instead sit in the same memory as ordinary data like `float`s or `int`s. - -With a flat memory model, a distinct notion of a slot or block is not needed. -A slot is just an ordinary memory location that happens to be used to store a value of texture, buffer, or other resource type. -A block is just an ordinary memory buffer that happens to be filled with values of texture/buffer/etc. type. - -CUDA provides two parameter-passing mechanisms for the compute pipeline. -First, when invoking a compute kernel, the application passes a limited number of bytes of parameter data that act as root constants. -Second, each loaded module of GPU code may contain pre-allocated "constant memory" storage which can be initialized from the host and then read by GPU code. -Because types like blocks or textures are not special in CUDA, either of these mechanisms can be utilized to pass any kind of data including references to pointer-based data structures stored in the GPU virtual address space. -The use of "slots" or "blocks" or "root constants" is a matter of application policy instead of API mechanism. - -OptiX supports use of constant memory storage for ray tracing pipelines, where all the stages in a ray tracing pipeline share that storage. -OptiX uses a shader table for managing kernels and hit groups, and allows kernels to access the bytes of their shader table entry via a pointer. -Similar to the compute pipeline, application code can layer many different policies on top of these mechanisms. - -CPU Compute ------------ - -> #### Note #### -> Slang's support for CPU compute is functional, but not feature- or performance-complete. -> Backwards-incompatible changes to this target may come in future versions of Slang. - -For the purposes of Slang, different CPU-based host platforms are largely the same. -All support binary code in a native machine-code format. -All CPU platforms Slang supports use a flat memory model with a single virtual address space, where any data type can be stored at any virtual address. - -Note that this section consider CPU-based platforms only as targets for kernel compilation; using a CPU as a target for scalar "host" code is an advanced target beyond the scope of this document. - -### Pipelines - -Slang's CPU compute target supports only a compute pipeline. - -### Parameter Passing - -Because CPU target support flexible pointer-based addressing and large low-latency caches, a compute kernel can simply be passed a small fixed number of pointers and be relied upon to load parameter values of any types via indirection through those pointers. - -Summary -------- - -This chapter has reviewed the main target platforms supported by the Slang compiler and runtime system. -A key point to take away is that there is great variation in the capabilities of these systems. -Even superficially similar graphics APIs have complicated differences in their parameter-passing mechanisms that must be accounted for by application programmers and GPU compilers. - -In the next chapter, we will discuss how the Slang compiler adapts to the different capabilities and rules of these platforms when laying out shader parameters in memory and then binding those parameters to the mechanisms defined by each platform. diff --git a/docs/user-guide/07-autodiff.md b/docs/user-guide/07-autodiff.md deleted file mode 100644 index a2a06b64b..000000000 --- a/docs/user-guide/07-autodiff.md +++ /dev/null @@ -1,757 +0,0 @@ ---- -layout: user-guide ---- - -# Automatic Differentiation - -Neural networks and other machine learning techniques are becoming an increasingly popular way to solve many difficult problems in modern visual computing systems. However, to take advantage of these techniques, developers often need to reimplement many existing system components in a differentiable form to allow computing the derivatives of a function, or to propagate the derivative of a result backwards to each parameter. Slang provides built-in auto differentiation features to support developers adding differentiability to their existing code with as little effort as possible. In this chapter, we provide an overview of the auto differentiation features, followed by a detailed description on the new syntax and rules. - -## Using Automatic Differentiation in Slang - -In this section, we walk through the steps to compute forward-derivative from input, and backward propagate the derivative from output to input. - -### Forward Differentiation - -Suppose the user has already written a function that computes some mathematic term: - -```csharp -float myFunc(float a, float x) -{ - return a * x * x; -} -``` - -The user can make this function *forward-differentiable* by adding a `[ForwardDerivative]` attribute: -```csharp -[ForwardDifferentiable] -float myFunc(float a, float x) -{ - return a * x * x; -} -``` - -This allows the function to be used in the `fwd_diff` operator, which is a higher order operation that takes in a forward-differentiable function and returns the forward-derivative of the function. - -The expression `fwd_diff(myFunc)` will have the following signature: -```csharp -DifferentialPair myFunc_fwd_derivative(DifferentialPair a, DifferentialPair x); -``` - -Where `DifferentialPair` is a built-in type that encodes both the primal(original) value and the derivative value of a term. -To use this function to compute the derivative of `myFunc` with regard to `x`, the user can call the forward-derivative function by supplying the derivative value of `x` with `1.0` and the derivative value of `a` with `0.0`, as in the following code: - -```csharp -float a = 2.0; -float x = 3.0; -// Compute derivative with regard to `x`: -let result = fwd_diff(myFunc)(diffPair(a, 0.0), diffPair(x, 1.0)); -// Print the derivative. -printf("%f", result.d); - -// Output: 12.0 -``` - -In the example code above, `diffPair()` is a built-in function to construct a value of `DifferentialPair` with a primal value and a derivative value. The primal value and derivative value stored in a `DifferentialPair` can be accessed with the `.p` and a `.d` property. - -### Backward Propagation - -The forward derivative function allows the user to compute the derivative of a function with regard to a specific combination of input parameters at a time. In many cases, we need to know how each parameter affects the output. Instead of calling the forward derivative function once for each parameter, it is more efficient to call the *backward propagation* function that propagate the derivative of outputs to each input parameter. - -To allow the compiler to generate the backward propagation function, we simply mark our function with the `[Differentiable]` or `[BackwardDifferentiable]` attribute: -```csharp -[Differentiable] -float myFunc(float a, float x) -{ - return a * x * x; -} -``` - -> #### Note: -> When a function is marked as `[Differentiable]`, it is implied that the function is both `[ForwardDifferentiable]` and `[BackwardDifferentiable]` and can be used in the `fwd_diff` operator. - - -The `bwd_diff` operator applies to a backward differentiable function and returns the backward propagation function. In this case, `bwd_diff(myFunc)` will have the following signature: - -```csharp -void myFunc_backProp(inout DifferentialPair a, inout DifferentialPair x, float dResult); -``` - -Where `a` is an `inout DifferentialPair` where the initial value of `a` is passed into the function as primal value (in the `.p` property), and the propagated derivative of `a` is returned via the `.d` property of the `DifferentialPair`. The same rules apply to `x`. - -The additional `dResult` parameter is the derivative of the return value to be propagated to the input parameters. Note that in a backward propagation function, an input will become a `inout DifferentialPair` where the `.d` property of the pair is intended for receiving the propagation result, and the return value will become an input parameter that represents the source of backward propagation. - -The backward propagation function can be called as in the following code: -```csharp -var a = diffPair(2.0); // constructs DifferentialPair{2.0, 0.0} -var x = diffPair(3.0); // constructs DifferentialPair{3.0, 0.0} - -bwd_diff(myFunc)(a, x, 1.0); - -// a.d is now 9.0 -// x.d is now 12.0 -``` - -This completes the walkthrough of automatic differentiation features. The following sections will cover each perspective of the auto differentiation feature in more detail. - -## Mathematic Concepts and Terminologies - -This section briefly reviews the mathematic theories behind differentiable programming with the intention to clarify the concepts and terminologies that will be used in the rest of this documentation. We assume the reader is already familiar with the basic theories behind neural network training, in particular the back-propagation algorithm. - -A differentiable system can be represented a composition of differentiable functions (kernels) with learnable parameters, where each differentiable function has the form: - -$$\mathbf{w}_{i+1} = f_i(\mathbf{w}_i) $$ - -Where $$f_i$$ represents a differentiable function (kernel) in the system, $$\mathbf{w}$$ represents a collection of learnable parameters defined in function $$f_i$$, and $$\mathbf{w}_{i+1}$$ is the output of $$f_i$$. We will use $$\omega$$ to denote a specific parameter in $$\mathbf{w}$$. - -In a composed system, the value of $$\mathbf{w}$$ used to evaluate $$f_i$$ may come from an *upstream* function - -$$ \mathbf{w}_i = f_{i-1}(\mathbf{w}_{i-1}) $$ - -Similarly, the value computed by $$f_i$$ may be used as argument to a *downstream* function - -$$ h = f_{i+1}(\mathbf{w}_{i+1}) = f_{i+1}(f_{i}(\mathbf{w}_{i}))$$ - -The entire system composed from differentiable functions can be noted as - -$$Y = f_1 \circ f_2 \circ \cdots \circ f_n(\mathbf{w}_0)$$ - -Where $$\mathbf{w}_0$$ is the first layer of parameters. - -### Forward Propagation of Derivatives -When developing and training such a system, we often need to evaluate the partial derivative of a differentiable function with regard to some parameter $$\omega$$. The simplest way to obtain a partial derivative is to call a forward derivative propagation function, which is defined by: - -$$ \mathbb{F}[f_i] = f_i'(\mathbf{w}_i, \mathbf{w}_i') = \sum_{\omega_i\in\mathbf{w}_i} \frac{\partial f}{\partial \omega_i} \omega_i' $$ - -Where $$\omega' \in \mathbf{w}'$$ represents the partial derivative of $$\omega_i$$ with regard to some upstream parameter $$\omega_{i-1}$$ that is used to compute $$\omega_i$$, i.e. $$\omega'=\frac{\partial \omega_{i}}{\partial \omega_{i-1}}$$. - -Given this definition, $$\mathbb{F}[f]$$ can be used as a forward propagation function that is able to compute $$\frac{\partial f_i}{\partial \omega_0}$$ from $$\frac{\partial \omega_{i-1}}{\partial \omega_0}$$. - -### Backward Propagation of Derivatives -When using the backpropagation algorithm to train a neural network, we are more interested in figuring out the partial derivative of the final system output with regard to a parameter $$\omega_i$$ in $$f_i$$. To do so, we generally utilize the backward derivative propagation function - -$$\mathbb{B}[f_i] = f_i^{-1}(\frac{\partial Y}{\partial f_i}) = \frac{\partial Y}{\partial \mathbf{w}_i}$$ - -Where the backward propagation function $$\mathbb{B}[f_i]$$ takes as input the partial derivative of the final system output $$Y$$ with regard to the output of $$f_i$$ (i.e. $$\mathbf{w}_i$$), and computes the partial derivative of the final system output with regard to the input of $$f_i$$ (i.e. $$\mathbf{w}_{i-1}$$). - -The higher order operator $$\mathbb{F}$$ and $$\mathbb{B}$$ represent the operations that converts an original or primal function $$f$$ to its forward or backward derivative propagation function. Slang's automatic differentiation feature provide built-in support for these operators to automatically generate the derivative propagation functions from a user defined primal function. The remaining documentation will discuss this feature from a programming language perspective. - -## Differentiable Types -Slang will only generate differentiation code for values that has a *differentiable* type. A type is differentiable if it conforms to the built-in `IDifferentiable` interface. The definition of the `IDifferentiable` interface is: -```csharp -interface IDifferentiable -{ - associatedtype Differential : IDifferentiable - where Differential.Differential == Differential; - - static Differential dzero(); - - static Differential dadd(Differential, Differential); - - static Differential dmul(This, Differential); -} -``` -As defined by the `IDifferentiable` interface, a differentiable type must have a `Differential` associated type that stores the derivative of the value. A further requirement is that the type of the second-order derivative must be the same `Differential` type. In another word, given a type `T`, `T.Differential` can be different from `T`, but `T.Differential.Differential` must equal to `T.Differential`. - -In addition, a differentiable type must define the `zero` value of its derivative, and how to add and multiply derivative values. - -### Builtin Differentiable Types -The following built-in types are differentiable: -- Scalars: `float`, `double` and `half`. -- Vector/Matrix: `vector` and `matrix` of `float`, `double` and `half` types. -- Arrays: `T[n]` is differentiable if `T` is differentiable. - -### User Defined Differentiable Types - -The user can make any `struct` types differentiable by implementing the `IDifferentiable` interface on the type. The requirements from `IDifferentiable` interface can be fulfilled automatically or manually. - -#### Automatic Fulfillment of `IDifferentiable` Requirements -Assume the user has defined the following type: - -```csharp -struct MyRay -{ - float3 origin; - float3 dir; - int nonDifferentiablePayload; -} -``` - -The type can be made differentiable by adding `IDifferentiable` conformance: -```csharp -struct MyRay : IDifferentiable -{ - float3 origin; - float3 dir; - int nonDifferentiablePayload; -} -``` - -Note that this code does not provide any explicit implementation of the `IDifferentiable` requirements. In this case the compiler will automatically synthesize all the requirements. This should provide the desired behavior most of the time. The procedure for synthesizing the interface implementation is as follows: -1. A new type is generated that stores the `Differential` of all differentiable fields. This new type itself will conform to the `IDifferentiable` interface, and it will be used to satisfy the `Differential` associated type requirement. -2. Each differential field will be associated to its corresponding field in the newly synthesized `Differential` type. -3. The `zero` value of the differential type is made from the `zero` value of each field in the differential type. -4. The `dadd` and `dmul` methods simply perform `dadd` and `dmul` operations on each field. -5. If the synthesized `Differential` type contains exactly the same fields as the original type, and the type of each field is the same as the original field type, then the original type itself will be used as the `Differential` type instead of creating a new type to satisfy the `Differential` associated type requirement. This means that all the synthesized `Differential` type use itself to meet its own `IDifferentiable` requirements. - -#### Manual Fulfillment of `IDifferentiable` Requirements - -In rare cases where more control is desired, the user can manually provide the implementation. To do so, we will first define the `Differential` type for `MyRay`, and use it to fulfill the `Differential` requirement in `MyRay`: - -```csharp -struct MyRayDifferential -{ - float3 d_origin; - float3 d_dir; -} - -struct MyRay : IDifferentiable -{ - // Specify that `MyRay.Differential` is `MyRayDifferential`. - typealias Differential = MyRayDifferential; - - // Specify that the derivative for `origin` will be stored in `MayRayDifferential.d_origin`. - [DerivativeMember(MayRayDifferential.d_origin)] - float3 origin; - - // Specify that the derivative for `dir` will be stored in `MayRayDifferential.d_dir`. - [DerivativeMember(MayRayDifferential.d_dir)] - float3 dir; - - // This is a non-differentiable field so we don't put any attributes on it. - int nonDifferentiablePayload; - - // Define zero derivative. - static MyRayDifferential dzero() - { - return {float3(0.0), float3(0.0)}; - } - - // Define the add operation of two derivatives. - static MyRayDifferential dadd(MyRayDifferential v1, MyRayDifferential v2) - { - MyRayDifferential result; - result.d_origin = v1.d_origin + v2.d_origin; - result.d_dir = v1.d_dir + v2.d_dir; - return result; - } - - // Define the multiply operation of a primal value and a derivative value. - static MyRayDifferential dmul(MyRay p, MyRayDifferential d) - { - MyRayDifferential result; - result.d_origin = p.origin * d.d_origin; - result.d_dir = p.dir * d.d_dir; - return result; - } -} -``` - -Note that for each struct field that is differentiable, we need to use the `[DerivativeMember]` attribute to associate it with the corresponding field in the `Differential` type, so the compiler knows how to access the derivative for the field. - -However, there is still a missing piece in the above code: we also need to make `MyRayDifferential` conform to `IDifferentiable` because it is required that the `Differential` of a type must itself be `Differential`. Again we can use automatic fulfillment by simply adding `IDifferentiable` conformance to `MyRayDifferential`: -```csharp -struct MyRayDifferential : IDifferentiable -{ - float3 d_origin; - float3 d_dir; -} -``` -In this case, since all fields in `MyRayDifferential` are differentiable, and the `Differential` of each field is the same as the original type of each field (i.e. `float3.Differential == float3` as defined in built-in library), the compiler will automatically use the type itself as its own `Differential`, making `MyRayDifferential` suitable for use as `Differential` of `MyRay`. - -We can also choose to manually implement `IDifferentiable` interface for `MyRayDifferential` as in the following code: - -```csharp -struct MyRayDifferential : IDifferentiable -{ - typealias Differential = MyRayDifferential; - - [DerivativeMember(MyRayDifferential.d_origin)] - float3 d_origin; - - [DerivativeMember(MyRayDifferential.d_dir)] - float3 d_dir; - - static MyRayDifferential dzero() - { - return {float3(0.0), float3(0.0)}; - } - - static MyRayDifferential dadd(MyRayDifferential v1, MyRayDifferential v2) - { - MyRayDifferential result; - result.d_origin = v1.d_origin + v2.d_origin; - result.d_dir = v1.d_dir + v2.d_dir; - return result; - } - - static MyRayDifferential dmul(MyRayDifferential p, MyRayDifferential d) - { - MyRayDifferential result; - result.d_origin = p.d_origin * d.d_origin; - result.d_dir = p.d_dir * d.d_dir; - return result; - } -} -``` -In this specific case, the automatically generated `IDifferentiable` implementation will be exactly the same as the manually written code listed above. - - -## Forward Derivative Propagation Function - -Functions in Slang can be marked as forward-differentiable or backward-differentiable. The `fwd_diff` operator can be used on a forward-differentiable function to obtain the forward derivative propagation function. Likewise, the `bwd_diff` operator can be used on a backward-differentiable function to obtain the backward derivative propagation function. This and the next sections cover the semantics of forward and backward propagation functions, and different ways to make a function forward and backward differentiable. - -A forward derivative propagation function computes the derivative of the result value with regard to a specific set of input parameters. -Given an original function, the signature of its forward propagation function is determined using the following rules: -- If the return type `R` is differentiable, the forward propagation function will return `DifferentialPair` that consists of both the computed original result value and the (partial) derivative of the result value. Otherwise, the return type is kept unmodified as `R`. -- If a parameter has type `T` that is differentiable, it will be translated into a `DifferentialPair` parameter in the derivative function, where the differential component of the `DifferentialPair` holds the initial derivatives of each parameter with regard to their upstream parameters. -- All parameter directions are unchanged. For example, an `out` parameter in the original function will remain an `out` parameter in the derivative function. - -For example, given original function: -```csharp -R original(T0 p0, inout T1 p1, T2 p2); -``` -Where `R`, `T0`, and `T1` is differentiable and `T2` is non-differentiable, the forward derivative function will have the following signature: -```csharp -DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2); -``` - -This forward propagation function takes the initial primal value of `p0` in `p0.p`, and the partial derivative of `p0` with regard to some upstream parameter in `p0.d`. It takes the initial primal and derivative values of `p1` and updates `p1` to hold the newly computed value and propagated derivative. Since `p2` is not differentiable, it remains unchanged. - -`DifferentialPair` is a built-in type that carries both the original and derivative value of a term. It is defined as follows: -```csharp -struct DifferentialPair : IDifferentiable -{ - typealias Differential = DifferentialPair; - property T p {get;} - property T.Differential d {get;} - static Differential dzero(); - static Differential dadd(Differential a, Differential b); - static Differential dmul(This a, Differential b); -} -``` - -### Automatic Implementation of Forward Derivative Functions - -A function can be made forward-differentiable with a `[ForwardDifferentiable]` attribute. This attribute will cause the compiler to automatically implement the forward propagation function. The syntax for using `[ForwardDifferentiable]` is: - -```csharp -[ForwardDifferentiable] -R original(T0 p0, inout T1, p1, T2 p2); -``` - -Once the function is made forward-differentiable, the forward propagation function can then be called with the `fwd_diff` operator: -```csharp -DifferentialPair result = fwd_diff(original)(...); -``` - -### User Defined Forward Derivative Functions -As an alternative to compiler-implemented forward derivatives, the user can choose to manually provide a derivative implementation to make an existing function forward-differentiable. The `[ForwardDerivative(derivative_func)]` attribute is used to associate a function with its forward derivative propagation implementation. The syntax for using `[ForwardDerivative]` attribute is: -```csharp -DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2) -{ - .... -} - -[ForwardDerivative(derivative)] -R original(T0 p0, inout T1, p1, T2 p2); -``` -If `derivative` is defined in a different scope from `original`, such as in a different namespace or `struct` type, a fully qualified name is required. For example: -```csharp -struct MyType -{ - // Implementing derivative function in a different name scope. - static DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2) - { - .... - } -} - -// Use fully qualified name in the attribute. -[ForwardDerivative(MyType.derivative)] -R original(T0 p0, inout T1, p1, T2 p2); -``` - -Sometimes the derivative function needs to be defined in a different module from the original function, or the derivative function cannot be made visible from the original function. In this case, we can use the `[ForwardDerivativeOf(originalFunnc)]` attribute to inform the compiler that `originalFunc` should be treated as a forward-differentiable function, and the current function is the derivative implementation of `originalFunc`. The following code will have the same effect to associate `derivative` and the forward-derivative implementation of `original`: - -```csharp -R original(T0 p0, inout T1, p1, T2 p2); - -[ForwardDerivativeOf(original)] -DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2) -{ - .... -} -``` - -## Backward Derivative Propagation Function - -A backward derivative propagation function propagates the derivative of the function output to all the input parameters simultaneously. - -Given an original function `f`, the general rule for determining the signature of its backward propagation function is that a differentiable output `o` becomes an input parameter holding the partial derivative of a downstream output with regard to the differentiable output, i.e. $$\partial y/\partial o$$); an input differentiable parameter `i` in the original function will become an output in the backward propagation function, holding the propagated partial derivative $$\partial y/\partial i$$; and any non-differentiable outputs are dropped from the backward propagation function. This means that the backward propagation function never returns any values computed in the original function. - -More specifically, the signature of its backward propagation function is determined using the following rules: -- A backward propagation function always returns `void`. -- A differentiable `in` parameter of type `T` will become an `inout DifferentialPair` parameter, where the original value part of the differential pair contains the original value of the parameter to pass into the back-prop function. The original value will not be overwritten by the backward propagation function. The propagated derivative will be written to the derivative part of the differential pair after the backward propagation function returns. The initial derivative value of the pair is ignored as input. -- A differentiable `out` parameter of type `T` will become an `in T.Differential` parameter, carrying the partial derivative of some downstream term with regard to the return value. -- A differentiable `inout` parameter of type `T` will become an `inout DifferentialPair` parameter, where the original value of the argument, along with the downstream partial derivative with regard to the argument is passed as input to the backward propagation function as the original and derivative part of the pair. The propagated derivative with regard to this input parameter will be written back and replace the derivative part of the pair. The primal value part of the parameter will *not* be updated. -- A differentiable return value of type `R` will become an additional `in R.Differential` parameter at the end of the backward propagation function parameter list, carrying the result derivative of a downstream term with regard to the return value of the original function. -- A non-differentiable return value of type `NDR` will be dropped. -- A non-differentiable `in` parameter of type `ND` will remain unchanged in the backward propagation function. -- A non-differentiable `out` parameter of type `ND` will be removed from the parameter list of the backward propagation function. -- A non-differentiable `inout` parameter of type `ND` will become an `in ND` parameter. - -For example consider the following original function: -```csharp -struct T : IDifferentiable {...} -struct R : IDifferentiable {...} -struct ND {} // Non differentiable - -[Differentiable] -R original(T p0, out T p1, inout T p2, ND p3, out ND p4, inout ND p5); -``` -The signature of its backward propagation function is: -```csharp -void back_prop( - inout DifferentialPair p0, - T.Differential p1, - inout DifferentialPair p2, - ND p3, - ND p5, - R.Differential dResult); -``` -Note that although `p2` is still `inout` in the backward propagation function, the backward propagation function will only write propagated derivative to `p2.d` and will not modify `p2.p`. - -### Automatically Implemented Backward Propagation Functions - -A function can be made backward-differentiable with a `[Differentiable]` or `[BackwardDifferentiable]` attribute. This attribute will cause the compiler to automatically implement the backward propagation function. The syntax for using `[Differentiable]` is: - -```csharp -[Differentiable] -R original(T0 p0, inout T1, p1, T2 p2); -``` - -Once the function is made backward-differentiable, the backward propagation function can then be called with the `bwd_diff` operator: -```csharp -bwd_diff(original)(...); -``` - -### User Defined Backward Propagation Functions -Similar to user-defined forward derivative functions, the `[BackwardDerivative]` and `[BackwardDerivativeOf]` attributes can be used to supply a function with user defined backward propagation function. - -The syntax for using `[BackwardDerivative]` attribute is: -```csharp -void back_prop( - inout DifferentialPair p0, - T1.Differential p1, - inout DifferentialPair p2, - ND p3, - ND p5, - R.Differential dResult) -{ - ... -} - -[BackwardDerivative(back_prop)] -R original(T0 p0, inout T1, p1, T2 p2); -``` - -Similarly, the `[BackwardDerivativeOf]` attribute can be used on the back-prop function in case it is not convenient to modify the definition of the original function, or the back-prop function can't be made visible from the original function: - -```csharp -R original(T0 p0, inout T1, p1, T2 p2); - -[BackwardDerivativeOf(original)] -void back_prop( - inout DifferentialPair p0, - T1.Differential p1, - inout DifferentialPair p2, - ND p3, - ND p5, - R.Differential dResult) -{ - ... -} -``` - -## Builtin Differentiable Functions - -The following built-in functions are backward differentiable and both their forward-derivative and backward-propagation functions are already defined in the built-in library: - -- Arithmetic functions: `abs`, `max`, `min`, `sqrt`, `rcp`, `rsqrt`, `fma`, `mad`, `fmod`, `frac`, `radians`, `degrees` -- Interpolation and clamping functions: `lerp`, `smoothstep`, `clamp`, `saturate` -- Trigonometric functions: `sin`, `cos`, `sincos`, `tan`, `asin`, `acos`, `atan`, `atan2` -- Hyperbolic functions: `sinh`, `cosh`, `tanh` -- Exponential and logarithmic functions: `exp`, `exp2`, `pow`, `log`, `log2`, `log10` -- Vector functions: `dot`, `cross`, `length`, `distance`, `normalize`, `reflect`, `refract` -- Matrix transforms: `mul(matrix, vector)`, `mul(vector, matrix)`, `mul(matrix, matrix)` -- Matrix operations: `transpose`, `determinant` -- Legacy blending and lighting intrinsics: `dst`, `lit` - -## Primal Substitute Functions - -Sometimes it is desirable to replace a function with another when generating forward or backward derivative propagation code. For example, the following code shows a function that computes the integral of some term by sampling and we want to use a different sampling stragegy when computing the derivatives. -```csharp -float myTerm(float x) -{ - return someComplexComputation(x); -} - -float getSample(float a, float b) { ... } - -[Differentiable] -float computeIntegralOverMyTerm(float x, float a, float b) -{ - float sum = 0.0; - for (int i = 0; i < SAMPLE_COUNT; i++) - { - let s = no_diff getSample(a, b); - let y = myTerm(s); - sum += y * ((b-a)/SAMPLE_COUNT); - } - return sum; -} -``` - -In this code, the `getSample` function returns a random sample in the range of `[a,b]`. Assume we have another sampling function `getSampleForDerivativeComputation(a,b)` that we wish to use instead in derivative computation, we can do so by marking it as a primal-substitute of `getSample`, as in the following code: -```csharp -[PrimalSubstituteOf(getSample)] -float getSampleForDerivativeComputation(float a, float b) -{ - ... -} -``` - -Here, the `[PrimalSubstituteOf(getSample)]` attributes marks the `getSampleForDerivativeComputation` function as the substitute for `getSample` in derivative propagation functions. When a function has a primal substitute, the compiler will treat all calls to that function as if it is a call to the substitute function when generating derivative code. Note that this only applies to compiler generated derivative function and does not affect user provided derivative functions. If a user provided derivative function calls `getSample`, it will not be replaced by `getSampleForDerivativeComputation` by the compiler. - -Similar to `[ForwardDerivative]` and `[ForwardDerivativeOf]` attributes, The `[PrimalSubsitute(substFunc)]` attribute works the other way around: it specifies the primal substitute function of the function being marked. - -Primal substitute can be used as another way to make a function differentiable. A function is considered differentiable if it has a primal substitute that is differentiable. The following code illustrates this mechanism. -```csharp -float myFunc(float x) {...} - -[PrimalSubstituteOf(myFunc)] -[Differentiable] -float myFuncSubst(float x) {...} - -// myFunc is now considered backward differentiable. -``` - -The following example shows in more detail on how primal substitute affects derivative computation. -```csharp -float myFunc(float x) { return x*x; } - -[PrimalSubstituteOf(myFunc)] -[ForwardDifferentiable] -float myFuncSubst(float x) { return x*x*x; } - -[ForwardDifferentiable] -float caller(float x) { return myFunc(x); } - -let a = caller(4.0); // a == 16.0 (calling myFunc) -let b = fwd_diff(caller)(diffPair(4.0, 1.0)).p; // b == 64.0 (calling myFuncSubst) -let c = fwd_diff(caller)(diffPair(4.0, 1.0)).d; // c == 48.0 (calling derivative of myFuncSubst) -``` - -In case that a function has both custom defined derivatives and a differentiable primal substitute, the primal substitute overrides the custom defined derivative on the original function. All calls to the original function will be translated into calls to the primal substitute first, and differentiation step follows after. This means that the derivatives of the primal substitute function will be used instead of the derivatives defined on the original function. - -## Working with Mixed Differentiable and Non-Differentiable Code - -Introducing differentiability to an existing system often involves dealing with code that mixes differentiable and non-differentiable logic. -Slang provides type checking and code analysis features to allow users to clarify the intention and guard against unexpected behaviors involving when to propagate derivatives through operations. - -### Excluding Parameters from Differentiation - -Sometimes we do not wish a parameter to be considered differentiable despite it has a differentiable type. We can use the `no_diff` modifier on the parameter to inform the compiler to treat the parameter as non-differentiable and skip generating differentiation code for the parameter. The syntax is: - -```csharp -// Only differentiate this function with regard to `x`. -float myFunc(no_diff float a, float x); -``` - -The forward derivative and backward propagation functions of `myFunc` should have the following signature: -```csharp -DifferentialPair fwd_derivative(float a, DifferentialPair x); -void back_prop(float a, inout DifferentialPair x, float dResult); -``` - -In addition, the `no_diff` modifier can also be used on the return type to indicate the return value should be considered non-differentiable. For example, the function -```csharp -no_diff float myFunc(no_diff float a, float x, out float y); -``` -Will have the following forward derivative and backward propagation function signatures: - -```csharp -float fwd_derivative(float a, DifferentialPair x); -void back_prop(float a, inout DifferentialPair x, float d_y); -``` - -By default, the implicit `this` parameter will be treated as differentiable if the enclosing type of the member method is differentiable. If you wish to exclude `this` parameter from differentiation, use `[NoDiffThis]` attribute on the method: -```csharp -struct MyDifferentiableType : IDifferentiable -{ - [NoDiffThis] // Make `this` parameter `no_diff`. - float compute(float x) { ... } -} -``` - -### Excluding Struct Members from Differentiation - -When using automatic `IDifferentiable` conformance synthesis for a `struct` type, Slang will by-default treat all struct members that have a differentiable type as differentiable, and thus include a corresponding field in the generated `Differential` type for the struct. -For example, given the following definition -```csharp -struct MyType : IDifferentiable -{ - float member1; - float2 member2; -} -``` -Slang will generate: -```csharp -struct MyType.Differential : IDifferentiable -{ - float member1; // derivative for MyType.member1 - float2 member2; // derivative for MyType.member2 -} -``` -If the user does not want a certain member to be treated as differentiable despite it has a differentiable type, a `no_diff` modifier can be used on the struct member to exclude it from differentiation. -For example, the following code excludes `member1` from differentiation: -```csharp -struct MyType : IDifferentiable -{ - no_diff float member1; // excluded from differentiation - float2 member2; -} -``` -The generated `Differential` in this case will be: -```csharp -struct MyType.Differential : IDifferentiable -{ - float2 member2; -} -``` - -### Assigning Differentiable Values into a Non-Differentiable Location - -When a value with derivatives is being assigned to a location that is not differentiable, such as a struct member that is marked as `no_diff`, the derivative info is discarded and any derivative propagation is stopped at the assignment site. -This may lead to unexpected results. For example: -```csharp -struct MyType : IDifferentiable -{ - no_diff float member; - float someOtherMemther; -} -[ForwardDifferentiable] -float f(float x) -{ - MyType t; - t.member = x * x; // Error: assigning value with derivative into a non-differentiable location. - return t.member; -} -... -let result = fwd_diff(f)(diffPair(3.0, 1.0)).d; // result == 0.0 -``` -In this case, we are assigning the value `x*x`, which carries a derivative, into a non-differentiable location `MyType.member`, thus throwing away any derivative info. When `f` returns `t.member`, there will be no derivative associated with it, so the function will not propagate the derivative through. This code is most likely not intending to discard the derivative through the assignment. To help avoid this kind of unintentional behavior, Slang will treat any assignments of a value with derivative info into a non-differentiable location as a compile-time error. To eliminate this error, the user should either make `t.member` differentiable, or to force the assignment by clarifying the intention to discard any derivatives using the built-in `detach` method. -The following code will compile, and the derivatives will be discarded: -```csharp -[ForwardDifferentiable] -float f(float x) -{ - MyType t; - // OK: the code has expressed clearly the intention to discard the derivative and perform the assignment. - t.member = detach(x * x); - return t.member; -} -``` - -### Calling Non-Differentiable Functions from a Differentiable Function -Calling non-differentiable function from a differentiable function is allowed. However, derivatives will not be propagated through the call. The user is required to clarify the intention by prefixing the call with the `no_diff` keyword. An un-clarified call to non-differentiable function will result in a compile-time error. - -For example, consider the following code: -```csharp -float g(float x) -{ - return 2*x; -} - -[ForwardDifferentiable] -float f(float x) -{ - // Error: implicit call to non-differentiable function g. - return g(x) + x * x; -} -``` -The derivative will not propagate through the call to `g` in `f`. As a result, `fwd_diff(f)(diffPair(1.0, 1.0))` will return -`{3.0, 2.0}` instead of `{3.0, 4.0}` as the derivative from `2*x` is lost through the non-differentiable call. To prevent unintended error, it is treated as a compile-time error to call `g` from `f`. If such a non-differentiable call is intended, a `no_diff` prefix is required in the call: -```csharp -[ForwardDifferentiable] -float f(float x) -{ - // OK. The intention to call a non-differentiable function is clarified. - return no_diff g(x) + x * x; -} -``` - -However, the `no_diff` keyword is not required in a call if a non-differentiable function does not take any differentiable parameters, or if the result of the differentiable function is not dependent on the derivative being propagated through the call. - -### Treat Non-Differentiable Functions as Differentiable -Slang allows functions to be marked with a `[TreatAsDifferentiable]` attribute for them to be considered as differentiable functions by the type-system. When a function is marked as `[TreatAsDifferentiable]`, the compiler will not generate derivative propagation code from the original function body or perform any additional checking on the function definition. Instead, it will generate trivial forward and backward propagation functions that returns 0. - -This feature can be useful if the user marked an `interface` method as forward or backward differentiable, but only wish to provide non-trivial derivative propagation functions for a subset of types that implement the interface. For other types that does not actually need differentiation, the user can simply put `[TreatAsDifferentiable]` on the method implementations for them to satisfy the interface requirement. - -See the following code for an example of `[TreatAsDifferentiable]`: -```csharp -interface IFoo -{ - [Differentiable] - float f(float v); -} - -struct B : IFoo -{ - [TreatAsDifferentiable] - float f(float v) - { - return v * v; - } -} - -[Differentiable] -float use(IFoo o, float x) -{ - return o.f(x); -} - -// Test: -B obj; -float result = fwd_diff(use)(obj, diffPair(2.0, 1.0)).d; -// result == 0.0, since `[TreatAsDifferentiable]` causes a trivial derivative implementation -// being generated regardless of the original code. -``` - -## Higher Order Differentiation - -Slang supports generating higher order forward and backward derivative propagation functions. It is allowed to use `fwd_diff` and `bwd_diff` operators inside a forward or backward differentiable function, or to nest `fwd_diff` and `bwd_diff` operators. For example, `fwd_diff(fwd_diff(sin))` will have the following signature: - -```csharp -DifferentialPair> sin_diff2(DifferentialPair> x); -``` - -The input parameter `x` contains four fields: `x.p.p`, `x.p.d,`, `x.d.p`, `x.d.d`, where `x.p.p` specifies the original input value, both `x.p.d` and `x.d.p` store the first order derivative if `x`, and `x.d.d` stores the second order derivative of `x`. Calling `fwd_diff(fwd_diff(sin))` with `diffPair(diffPair(pi/2, 1.0), DiffPair(1.0, 0.0))` will result `{ { 1.0, 0.0 }, { 0.0, -1.0 } }`. - -User defined higher-order derivative functions can be specified by using `[ForwardDerivative]` or `[BackwardDerivative]` attribute on the derivative function, or by using `[ForwardDerivativeOf]` or `[BackwardDerivativeOf]` attribute on the higher-order derivative function. - -## Interactions with Generics and Interfaces - -Automatic differentiation for generic functions is supported. The forward-derivative and backward propagation functions of a generic function is also a generic function with the same set of generic parameters and constraints. Using `[ForwardDerivative]`, `[ForwardDerivativeOf]`, `[BackwardDerivative]` or `[BackwardDerivativeOf]` attributes to associate a derivative function with different set of generic parameters or constraints is a compile-time error. - -An interface method requirement can be marked as `[ForwardDifferentiable]` or `[Differentiable]`, so they may be called in a forward or backward differentiable function and have the derivatives propagate through the call. This works regardless of whether the call can be specialized or has to go through dynamic dispatch. However, calls to interface methods are only differentiable once. Higher order differentiation through interface method calls are not supported. - -## Restrictions of Automatic Differentiation - -The compiler can generate forward derivative and backward propagation implementations for most uses of array and struct types, including arbitrary read and write access at dynamic array indices, and supports uses of all types of control flows, mutable parameters, generics and interfaces. This covers the set of operations that is sufficient for a lot of functions. However, the user needs to be aware of the following restrictions when using automatic differentiation: - -- All operations to global resources, global variables and shader parameters, including texture reads or atomic writes, are treating as a non-differentiable operation. -- If a differentiable function contains calls that cause side-effects such as updates to global memory, there will not be a guarantee on how many times the side-effect will occur during the resulting derivative function or back-propagation function. -- Loops: Loops must use the attribute `[MaxIters()]` to specify a maximum number of iterations. This will be used by compiler to allocate space to store intermediate data. If the actual number of iterations exceeds the provided maximum, the behavior is undefined. You can always mark a loop with the `[ForceUnroll]` attribute to instruct the Slang compiler to unroll the loop before generating derivative propagation functions. Unrolled loops will be treated the same way as ordinary code and are not subject to any additional restrictions. - -The above restrictions do not apply if a user-defined derivative or backward propagation function is provided. diff --git a/docs/user-guide/07-targets.md b/docs/user-guide/07-targets.md new file mode 100644 index 000000000..d8d71c9e7 --- /dev/null +++ b/docs/user-guide/07-targets.md @@ -0,0 +1,366 @@ +--- +layout: user-guide +--- + +Supported Compilation Targets +============================ + +This chapter provides a brief overview of the compilation targets supported by Slang, and their different capabilities. + +Background and Terminology +-------------------------- + +### Code Formats + +When Slang compiles for a target platform one of the most important distinctions is the _format_ of code for that platform. +For a native CPU target, the format is typically the executable machine-code format for the processor family (for example, x86-64). +In contrast, GPUs are typically programmed through APIs that abstract over multiple GPU processor families and versions. +GPU APIs usually define an _intermediate language_ that sits between a high-level-language compiler like Slang and GPU-specific compilers that live in drivers for the API. + +### Pipelines and Stages + +GPU code execution occurs in the context of a _pipeline_. +A pipeline comprises one or more _stages_ and dataflow connections between them. +Some stages are _programmable_ and run a user-defined _kernel_ that has been compiled from a language like Slang, while others are _fixed-function_ and can only be configured, rather than programmed, by the user. +Slang supports three different pipelines. + +#### Rasterization + +The _rasterization_ pipeline is the original GPU rendering pipeline. +On current GPUs, the simplest rasterization pipelines have two programmable stages: a `vertex` stage and a `fragment` stage. +The rasterization pipeline is named after its most important fixed-function stage: the rasterizer, which determines the pixels covered by a geometric primitive, and emits _fragments_ covering those pixels, to be shaded. + +#### Compute + +The _compute_ pipeline is a simple pipeline with only one stage: a programmable `compute` stage. +As a result of being a single-stage pipeline the compute pipeline doesn't need to deal with many issues around inter-stage dataflow that other pipelines do. + +#### Ray Tracing + +A _ray tracing_ pipeline has multiple stages pertaining to the life cycle of a ray being traced through a scene of geometric primitives. +These can include an `intersection` stage to compute whether a ray intersects a geometry primitive, a `miss` stage that runs when a ray does not intersect any geometric object in a scene, etc. + +Note that some platforms support types and operations related to ray tracing that can run outside of the context of a dedicated ray tracing pipeline. +Just as applications can do computation outside of the dedicated compute pipeline, the use of ray tracing does not necessarily mean that a ray tracing pipeline is being used. + +### Shader Parameter Bindings + +The kernels that execute within a pipeline typically has access to four different kinds of data: + +* _Varying inputs_ coming from the system or from a preceding pipeline stage + +* _Varying outputs_ which will be passed along to the system or to a following pipeline stage + +* _Temporaries_ which are scratch memory or registers used by each invocation of the kernel and then dismissed on exit. + +* _Shader parameters_ (sometimes also called _uniform parameters_), which provide access to data from outside the pipeline dataflow + +The first three of these kinds of data are largely handled by the implementation of a pipeline. +In contrast, an application programmer typically needs to manually prepare shader parameters, using the appropriate mechanisms and rules for each target platform. + +On platforms that provide a CPU-like "flat" memory model with a single virtual address space, and where any kind of data can be stored at any address, passing shader parameters can be almost trivial. +Current graphics APIs provide far more complicated and less uniform mechanisms for passing shader parameters. + +A high-level language compiler like Slang handles the task of _binding_ each user-defined shader parameter to one or more of the parameter-passing resources defined by a target platform. +For example, the Slang compiler might bindg a global `Texture2D` parameter called `gDiffuse` to the `t1` register defined by the Direct3D 11 API. + +An application is responsible for passing the argument data for a parameter using the using the corresponding platform-specific resource it was bound to. +For example, an application should set the texture they want to use for `gDiffuse` to the `t1` register using Direct3D 11 API calls. + +#### Slots + +Historically, most graphics APIs have used a model where shader parameters are passed using a number of API-defined _slots_. +Each slot can store a single argument value of an allowed type. +Depending on the platform slots might be called "registers," "locations," "bindings," "texture units," or other similar names. + +Slots almost exclusively use opaque types: textures, buffers, etc. +On platforms that use slots for passing shader parameters, value of ordinary types like `float` or `int` need to be stored into a buffer, and then that buffer is passed via an appropriate slot. + +Although many graphics APIs use slots as an abstraction, the details vary greatly across APIs. +Different APIs define different kinds of slots, and the types of arguments that may be stored in those slots vary. +For example, one API might use two different kinds of slots for textures and buffers, while another uses a single kind of slot for both. +On some APIs each pipeline stage gets is own dedicated slots, while on others slots are shared across all stages in a pipeline. + +#### Blocks + +Newer graphics APIs typically provide a system for grouping related shader parameters into re-usable _blocks_. +Blocks might be referred to as "descriptor tables," "descriptor sets," or "argument buffers." +Each block comprises one or more slots (often called "descriptors") that can be used to bind textures, buffers, etc. + +Blocks are in turn set into appropriate slots provided by a pipeline. +Because a block can contain many different slots for textures or buffers, switching a pipeline argument from one block to another can effectively swap out a large number of shader parameters in one operation. +Thus, while blocks introduce a level of indirection to parameter setting, then can also enable greater efficiency when parameters are grouped into blocks according to frequency of change. + +#### Root Constants + +Most recent graphics APIs also allow for a small amount of ordinary data (meaning types like `float` and `int` but not opaque types like buffers or textures) to be passed to the pipeline as _root constants_ (also called "push constants"). + +Using root constants can eliminate some overheads from passing parameters of ordinary types via buffers. +Passing a single `float` using a root constant rather than a buffer obviously eliminates a level of indirection. +More importantly, though, using a root constant can avoid application code having to allocate and manage the lifetime of a buffer in a concurrent CPU/GPU program. + +Direct3D 11 +----------- + +Direct3D 11 (D3D11) is a older graphics API, but remains popular because it is much simpler to learn and use than some more recent APIs. +In this section we will give an overview of the relevant features of D3D11 when used as a target platform for Slang. +Subsequent sections about other APIs may describe them by comparison to D3D11. + +D3D11 kernels must be compiled to the DirectX Bytecode (DXBC) intermediate language. +A DXBC binary includes a hash/checksum computed using an undocumented algorithm, and the runtime API rejects kernels without a valid checksum. +The only supported way to generate DXBC is by compiling HLSL using the fxc compiler. + +### Pipelines + +D3D11 exposes two pipelines: rasterization and compute. + +The D3D11 rasterization pipeline can include up to five programmable stages, although some of them are optional: + +* The `vertex` stage (VS) transforms vertex data loaded from memory + +* The optional `hull` stage (HS) typically sets up and computes desired tessellation levels for a higher-order primitive + +* The optional `domain` stage (DS) evaluates a higher-order surface at domain locations chosen by a fixed-function tessellator + +* The optional `geometry` stage (GS) receives as input a primitive and can produce zero or more new primitives as output + +* The `fragment` stage transforms fragments produced by the fixed-function rasterizer, determining the values for those fragments that will be merged with values in zero or more render targets. The fragment stage is sometimes called a "pixel" stage (PS), even when it does not process pixels. + +### Parameter Passing + +Shader parameters are passed to each D3D11 stage via slots. +Each stage has its own slots of the following types: + +* **Constant buffers** are used for passing relatively small (4KB or less) amounts of data that will be read by GPU code. Constant bufers are passed via `b` registers. + +* **Shader resource views** (SRVs) include most textures, buffers, and other opaque resource types thare are read or sampled by GPU code. SRVs use `t` registers. + +* **Unordered access views** (UAVs) include textures, buffers, and other opaque resource types used for write or read-write operations in GPU code. UAVs use `u` registers. + +* **Samplers** are used to pass opaque texture-sampling stage, and use `s` registers. + +In addition, the D3D11 pipeline provides _vertex buffer_ slots and a single _index buffer_ slot to be used as the source vertex and index data that defines primitives. +User-defined varying vertex shader inputs are bound to _vertex attribute_ slots (referred to as "input elements" in D3D11) which define how data from vertex buffers should be fetched to provide values for vertex attributes. + +The D3D11 rasterization pipeline also provides a mechanism for specifying _render target views_ (RTVs) and _depth-stencil views_ (DSVs) that provide the backing storage for the pixels in a framebuffer. +User-defined fragment shader varying outputs (with `SV_Target` binding semantics) are bound to RTV slots. + +One notable detail of the D3D11 API is that the slots for fragment-stage UAVs and RTVs overlap. +For example, a fragment kernel cannot use both `u0` and `SV_Target0` at once. + +Direct3D 12 +----------- + +Direct3D 12 (D3D12) is the current major version of the Direct2D API. + +D3D12 kernels must be compiled to the DirectX Intermediate Language (DXIL). +DXIL is a layered encoding based off of LLVM bitcode; it introduces additional formatting rules and constraints which are loosely documented. +A DXIL binary may be signed, and the runtime API only accepts appropriately signed binaries (unless a developer mode is enabled on the host machine). +A DXIL validator `dxil.dll` is included in SDK releases, and this validator can sign binaries that pass validation. +While DXIL can in principle be generated from multiple compiler front-ends, support for other compilers is not prioritized. + +### Pipelines + +D3D12 includes rasterization and compute pipelines similar to those in D3D11. +Revisions to D3D12 have added additional stages to the rasterization pipeline, as well as a ray-tracing pipeline. + +#### Mesh Shaders + +> #### Note ### +> The Slang system does not currently support mesh shaders. + +The D3D12 rasterization pipeline provides alternative geometry processing stages that may be used as an alternative to the `vertex`, `hull`, `domain`, and `geometry` stages: + +* The `mesh` stage runs groups of threads which are responsible cooperating to produce both the vertex and index data for a _meshlet_ a bounded-size chunk of geometry. + +* The optional `amplification` stage precedes the mesh stage and is responsible for determining how many mesh shader invocations should be run. + +Compared to the D3D11 pipeline without tesselllation (hull and domain shaders), a mesh shader is kind of like a combined/generalized vertex and geometry shader. + +Compared to the D3D11 pipeline with tessellation, an amplification shader is kind of like a combined/generalized vertex and hull shader, while a mesh shader is kind of like a combined/generalized domain and geometry shader. + +#### Ray Tracing + +The DirectX Ray Tracing (DXR) feature added a ray tracing pipeline to D3D12. +The D3D12 ray tracing pipeline exposes the following programmable stages: + +* The ray generation (`raygeneration`) stage is similar to a compute stage, but can trace zero or more rays and make use of the results of those traces. + +* The `intersection` stage runs kernels to compute whether a ray intersects a user-defined primitive type. The system also includes a default intersector that handles triangle meshes. + +* The so-called any-hit (`anyhit`) stage runs on _candidate_ hits where a ray has intersected some geometry, but the hit must be either accepted or rejected by application logic. Note that the any-hit stage does not necessarily run on *all* hits, because configuration options on both scene geometry and rays can lead to these checks being bypassed. + +* The closest-hit (`closesthit`) stage runs a single _accepted_ hit for a ray; under typical circumstances this will be the closest hit to the origin of the ray. A typical closest-hit shader might compute the apparent color of a surface, similar to a typical fragment shader. + +* The `miss` stage runs for rays that do not find or accept any hits in a scene. A typical miss shader might return a background color or sample an environment map. + +* The `callable` stage allows user-defined kernels to be invoked like subroutines in the context of the ray tracing pipeline. + +Compared to existing rasterization and compute pipelines, an important difference in the design of the D3D12 ray tracing pipeline is that multiple kernels can be loaded into the pipeline for each of the programming stages. +The specific closest-hit, miss, or other kernel that runs for a given hit or ray is determined by indexing into an appropriate _shader table_, which is effectively an array of kernels. +The indexing into a shader table can depend on many factors including the type of ray, the type of geometry hit, etc. + +Note that DXR version 1.1 adds ray tracing types and operations that can be used outside of the dedicated ray tracing pipeline. +These new mechanisms have less visible impact for a programmer using or integrating Slang. + + +### Parameter Passing + +The mechanisms for parameter passing in D3D12 differ greatly from D3D11. +Most opaque types (texture, resources, samplers) must be set into blocks (D3D12 calls blocks "descriptor tables"). +Each pipeline supports a fixed amount of storage for "root parameters," and allows those root parameters to be configured as root constants, slots for blocks, or slots for a limited number of opaque types (primarily just flat buffers). + +Shader parameters are still grouped and bound to registers as in D3D11; for example, a `Texture2D` parameter is considered as an SRV and uses a `t` register. +D3D12 additionally associates binds shader parameters to "spaces" which are expressed similarly to registers (e.g., `space2`), but represent an orthogonal "axis" of binding. + +While shader parameters are bound registers and spaces, those registers and spaces do not directly correspond to slots provided by the D3D12 API the way registers do in D3D11. +Instead, the configuration of the root parameters and the correspondence of registers/spaces to root parameters, blocks, and/or slots are defined by a _pipeline layout_ that D3D12 calls a "root signature." + +Unlike D3D11, all of the stages in a D3D12 pipeline share the same root parameters. +A D3D12 pipeline layout can specify that certain root parameters or certain slots within blocks will only be accessed by a subset of stages, and can map the *same* register/space pair to different parameters/blocks/slots as long as this is done for disjoint subset of stages. + +#### Ray Tracing Specifics + +The D3D12 ray tracing pipeline adds a new mechanism for passing shader parameters. +In addition to allowing shader parameters to be passed to the entire pipeline via root parameters, each shader table entry provides storage space for passing argument data specific to that entry. + +Similar to the use of a pipline layout (root signature) to configure the use of root parameters, each kernel used within shader entries must be configured with a "local root signature" that defines how the storage space in the shader table entry is to be used. +Shader parameters are still bound to registers and spaces as for non-ray-tracing code, and the local root signature simply allows those same registers/spaces to be associated with locations in a shader table entry. + +One important detail is that some shader table entries are associated with a kernel for a single stage (e.g., a single miss shader), while other shader table entries are associated with a "hit group" consisting of up to one each of an intersection, any-hit, and closest-hit kernel. +Because multiple kernels in a hit group share the same shader table entry, they also share the configured slots in that entry for binding root constants, blocks, etc. + +Vulkan +------ + +Vulkan is a cross-platform GPU API for graphics and compute with a detailed specification produced by a multi-vendor standards body. +In contrast with OpenGL, Vulkan focuses on providing explicit control over as many aspects of GPU work as possible. +In contrast with OpenCL, Vulkan focuses first and foremost on the needs of real-time graphics developers. + +Vulkan requires kernels to be compiled to the SPIR-V intermediate language. +SPIR-V is a simple and extensible binary program format with a detailed specification; it is largely unrelated to earlier "SPIR" formats that were LLVM-based and loosely specified. +The SPIR-V format does not require signing or hashing, and is explicitly designed to allow many different tools to produce and manipulate the format. +Drivers that consume SPIR-V are expected to perform validation at load time. +Some choices in the SPIR-V encoding are heavily influenced by specific design choices in the GLSL language, and may require non-GLSL compilers to transform code to match GLSL idioms. + +### Pipelines + +Vulkan includes rasterization, compute, and ray tracing pipelines with the same set of stages as described for D3D12 above. + +### Parameter Passing + +Like D3D12, Vulkan uses blocks (called "descriptor sets") to organize groups of bindings for opaque types (textures, buffers, samplers). +Similar to D3D12, a Vulkan pipeline supports a limited number of slots for passing blocks to the pipeline, and these slots are shared across all stages. +Vulkan also supports a limited number of bytes reserved for passing root constants (called "push constants"). +Vulkan uses pipeline layouts to describe configurations of usage for blocks and root constants. + +High-level-language shader parameters are bound to a combination of a "binding" and a "set" for Vulkan, which are superficially similar to the registers and spaces of D3D12. +Unlike D3D12, however, bindings and sets in Vulkan directly correspond to the API-provided parameter-passing mechanism. +The set index of a parameter indicates the zero-based index of a slot where a block must be passed, and the binding index is the zero-based index of a particular opaque value set into the block. +A shader parameter that will be passed using root constants (rather than via blocks) must be bound to a root-constant offset as part of compilation. + +Unlike D3D12, where SRVs, UAVs, etc. use distinct classes of registers, all opaque-type shader parameters use the same index space of bindings. +That is, a buffer and a texture both using `binding=2` in `set=3` for Vulkan will alias the same slot in the same block. + +The Vulkan ray tracing pipeline also uses a shader table, and also forms hit groups similar to D3D12. +Unlike D3D12, each shader table entry in Vulkan can only be used to pass ordinary values (akin to root constants), and cannot be configured for binding of opaque types or blocks. + +OpenGL +------ + +> #### Note #### +> Slang has only limited support for compiling code for OpenGL. + +OpenGL has existed for many years, and predates programmable GPU pipelines of the kind this chapter discusses; we will focus solely on use of OpenGL as an API for programmable GPU pipelines. + +OpenGL is a cross-platform GPU API for graphics and compute with a detailed specification produced by a multi-vendor standard body. +In contrast with Vulkan, OpenGL provides many convenience and safety features that can simplify GPU programming. + +OpenGL allows kernels to be loaded as SPIR-V binaries, vendor-specific binaries, or using GLSL source code. +Loading shaders as GLSL source code is the most widely supported of these options, such that GLSL is the _de facto_ intermediate language of OpenGL. + +### Pipelines + +OpenGL supports rasterization and compute pipelines with the same stages as described for D3D11. +The OpenGL rasterization pipeline also supports the same mesh shader stages that are supported by D3D12. + +### Parameter Passing + +OpenGL uses slots for binding. +There are distinct kinds of slots for buffers and textures/images, and each set of slots is shared by all pipeline stages. + +High-level-language shader parameters are bounding to a "binding" index for OpenGL. +The binding index of a parameter is the zero-based index of the slot (of the appropriate kind) that must be used to pass an argument value. + +Note that while OpenGL and Vulkan both use binding indices for shader parameters like textures, the semantics of those are different because OpenGL uses distinct slots for passing buffers and textures. +For OpenGL is is legal to have a texture that uses `binding=2` and a buffer that uses `binding=2` in the same kernel, because those are indices of distinct kinds of slots, while this scenario would typically be invalid for Vulkan. + +CUDA and OptiX +-------------- + +> #### Note #### +> Slang support for OptiX is a work in progress. + +CUDA C/C++ is a language for expressing heterogeneous CPU and GPU code with a simple interface to invoking GPU compute work. +OptiX is a ray tracing API that uses CUDA C++ as the language for expressing shader code. +We focus here on OptiX version 7 and up. + +CUDA and OptiX allow kernels to be loaded as GPU-specific binaries, or using the PTX intermediate language. + + +### Pipelines + +CUDA supports a compute pipeline that is similar to D3D12 or Vulkan, with additional features. + +OptiX introduced the style of ray tracing pipeline adopted by D3D12 and Vulkan, and thus uses the same basic stages. + +The CUDA system does not currently expose a rasterization pipeline. + +### Parameter Passing + +Unlike most of the GPU APIs discussed so far, CUDA supports a "flat" memory model with a single virtual address space for all GPU data. +Textures, buffers, etc. are not opaque types, but can instead sit in the same memory as ordinary data like `float`s or `int`s. + +With a flat memory model, a distinct notion of a slot or block is not needed. +A slot is just an ordinary memory location that happens to be used to store a value of texture, buffer, or other resource type. +A block is just an ordinary memory buffer that happens to be filled with values of texture/buffer/etc. type. + +CUDA provides two parameter-passing mechanisms for the compute pipeline. +First, when invoking a compute kernel, the application passes a limited number of bytes of parameter data that act as root constants. +Second, each loaded module of GPU code may contain pre-allocated "constant memory" storage which can be initialized from the host and then read by GPU code. +Because types like blocks or textures are not special in CUDA, either of these mechanisms can be utilized to pass any kind of data including references to pointer-based data structures stored in the GPU virtual address space. +The use of "slots" or "blocks" or "root constants" is a matter of application policy instead of API mechanism. + +OptiX supports use of constant memory storage for ray tracing pipelines, where all the stages in a ray tracing pipeline share that storage. +OptiX uses a shader table for managing kernels and hit groups, and allows kernels to access the bytes of their shader table entry via a pointer. +Similar to the compute pipeline, application code can layer many different policies on top of these mechanisms. + +CPU Compute +----------- + +> #### Note #### +> Slang's support for CPU compute is functional, but not feature- or performance-complete. +> Backwards-incompatible changes to this target may come in future versions of Slang. + +For the purposes of Slang, different CPU-based host platforms are largely the same. +All support binary code in a native machine-code format. +All CPU platforms Slang supports use a flat memory model with a single virtual address space, where any data type can be stored at any virtual address. + +Note that this section consider CPU-based platforms only as targets for kernel compilation; using a CPU as a target for scalar "host" code is an advanced target beyond the scope of this document. + +### Pipelines + +Slang's CPU compute target supports only a compute pipeline. + +### Parameter Passing + +Because CPU target support flexible pointer-based addressing and large low-latency caches, a compute kernel can simply be passed a small fixed number of pointers and be relied upon to load parameter values of any types via indirection through those pointers. + +Summary +------- + +This chapter has reviewed the main target platforms supported by the Slang compiler and runtime system. +A key point to take away is that there is great variation in the capabilities of these systems. +Even superficially similar graphics APIs have complicated differences in their parameter-passing mechanisms that must be accounted for by application programmers and GPU compilers. + +In the next chapter, we will discuss how the Slang compiler adapts to the different capabilities and rules of these platforms when laying out shader parameters in memory and then binding those parameters to the mechanisms defined by each platform. diff --git a/docs/user-guide/08-autodiff.md b/docs/user-guide/08-autodiff.md new file mode 100644 index 000000000..a2a06b64b --- /dev/null +++ b/docs/user-guide/08-autodiff.md @@ -0,0 +1,757 @@ +--- +layout: user-guide +--- + +# Automatic Differentiation + +Neural networks and other machine learning techniques are becoming an increasingly popular way to solve many difficult problems in modern visual computing systems. However, to take advantage of these techniques, developers often need to reimplement many existing system components in a differentiable form to allow computing the derivatives of a function, or to propagate the derivative of a result backwards to each parameter. Slang provides built-in auto differentiation features to support developers adding differentiability to their existing code with as little effort as possible. In this chapter, we provide an overview of the auto differentiation features, followed by a detailed description on the new syntax and rules. + +## Using Automatic Differentiation in Slang + +In this section, we walk through the steps to compute forward-derivative from input, and backward propagate the derivative from output to input. + +### Forward Differentiation + +Suppose the user has already written a function that computes some mathematic term: + +```csharp +float myFunc(float a, float x) +{ + return a * x * x; +} +``` + +The user can make this function *forward-differentiable* by adding a `[ForwardDerivative]` attribute: +```csharp +[ForwardDifferentiable] +float myFunc(float a, float x) +{ + return a * x * x; +} +``` + +This allows the function to be used in the `fwd_diff` operator, which is a higher order operation that takes in a forward-differentiable function and returns the forward-derivative of the function. + +The expression `fwd_diff(myFunc)` will have the following signature: +```csharp +DifferentialPair myFunc_fwd_derivative(DifferentialPair a, DifferentialPair x); +``` + +Where `DifferentialPair` is a built-in type that encodes both the primal(original) value and the derivative value of a term. +To use this function to compute the derivative of `myFunc` with regard to `x`, the user can call the forward-derivative function by supplying the derivative value of `x` with `1.0` and the derivative value of `a` with `0.0`, as in the following code: + +```csharp +float a = 2.0; +float x = 3.0; +// Compute derivative with regard to `x`: +let result = fwd_diff(myFunc)(diffPair(a, 0.0), diffPair(x, 1.0)); +// Print the derivative. +printf("%f", result.d); + +// Output: 12.0 +``` + +In the example code above, `diffPair()` is a built-in function to construct a value of `DifferentialPair` with a primal value and a derivative value. The primal value and derivative value stored in a `DifferentialPair` can be accessed with the `.p` and a `.d` property. + +### Backward Propagation + +The forward derivative function allows the user to compute the derivative of a function with regard to a specific combination of input parameters at a time. In many cases, we need to know how each parameter affects the output. Instead of calling the forward derivative function once for each parameter, it is more efficient to call the *backward propagation* function that propagate the derivative of outputs to each input parameter. + +To allow the compiler to generate the backward propagation function, we simply mark our function with the `[Differentiable]` or `[BackwardDifferentiable]` attribute: +```csharp +[Differentiable] +float myFunc(float a, float x) +{ + return a * x * x; +} +``` + +> #### Note: +> When a function is marked as `[Differentiable]`, it is implied that the function is both `[ForwardDifferentiable]` and `[BackwardDifferentiable]` and can be used in the `fwd_diff` operator. + + +The `bwd_diff` operator applies to a backward differentiable function and returns the backward propagation function. In this case, `bwd_diff(myFunc)` will have the following signature: + +```csharp +void myFunc_backProp(inout DifferentialPair a, inout DifferentialPair x, float dResult); +``` + +Where `a` is an `inout DifferentialPair` where the initial value of `a` is passed into the function as primal value (in the `.p` property), and the propagated derivative of `a` is returned via the `.d` property of the `DifferentialPair`. The same rules apply to `x`. + +The additional `dResult` parameter is the derivative of the return value to be propagated to the input parameters. Note that in a backward propagation function, an input will become a `inout DifferentialPair` where the `.d` property of the pair is intended for receiving the propagation result, and the return value will become an input parameter that represents the source of backward propagation. + +The backward propagation function can be called as in the following code: +```csharp +var a = diffPair(2.0); // constructs DifferentialPair{2.0, 0.0} +var x = diffPair(3.0); // constructs DifferentialPair{3.0, 0.0} + +bwd_diff(myFunc)(a, x, 1.0); + +// a.d is now 9.0 +// x.d is now 12.0 +``` + +This completes the walkthrough of automatic differentiation features. The following sections will cover each perspective of the auto differentiation feature in more detail. + +## Mathematic Concepts and Terminologies + +This section briefly reviews the mathematic theories behind differentiable programming with the intention to clarify the concepts and terminologies that will be used in the rest of this documentation. We assume the reader is already familiar with the basic theories behind neural network training, in particular the back-propagation algorithm. + +A differentiable system can be represented a composition of differentiable functions (kernels) with learnable parameters, where each differentiable function has the form: + +$$\mathbf{w}_{i+1} = f_i(\mathbf{w}_i) $$ + +Where $$f_i$$ represents a differentiable function (kernel) in the system, $$\mathbf{w}$$ represents a collection of learnable parameters defined in function $$f_i$$, and $$\mathbf{w}_{i+1}$$ is the output of $$f_i$$. We will use $$\omega$$ to denote a specific parameter in $$\mathbf{w}$$. + +In a composed system, the value of $$\mathbf{w}$$ used to evaluate $$f_i$$ may come from an *upstream* function + +$$ \mathbf{w}_i = f_{i-1}(\mathbf{w}_{i-1}) $$ + +Similarly, the value computed by $$f_i$$ may be used as argument to a *downstream* function + +$$ h = f_{i+1}(\mathbf{w}_{i+1}) = f_{i+1}(f_{i}(\mathbf{w}_{i}))$$ + +The entire system composed from differentiable functions can be noted as + +$$Y = f_1 \circ f_2 \circ \cdots \circ f_n(\mathbf{w}_0)$$ + +Where $$\mathbf{w}_0$$ is the first layer of parameters. + +### Forward Propagation of Derivatives +When developing and training such a system, we often need to evaluate the partial derivative of a differentiable function with regard to some parameter $$\omega$$. The simplest way to obtain a partial derivative is to call a forward derivative propagation function, which is defined by: + +$$ \mathbb{F}[f_i] = f_i'(\mathbf{w}_i, \mathbf{w}_i') = \sum_{\omega_i\in\mathbf{w}_i} \frac{\partial f}{\partial \omega_i} \omega_i' $$ + +Where $$\omega' \in \mathbf{w}'$$ represents the partial derivative of $$\omega_i$$ with regard to some upstream parameter $$\omega_{i-1}$$ that is used to compute $$\omega_i$$, i.e. $$\omega'=\frac{\partial \omega_{i}}{\partial \omega_{i-1}}$$. + +Given this definition, $$\mathbb{F}[f]$$ can be used as a forward propagation function that is able to compute $$\frac{\partial f_i}{\partial \omega_0}$$ from $$\frac{\partial \omega_{i-1}}{\partial \omega_0}$$. + +### Backward Propagation of Derivatives +When using the backpropagation algorithm to train a neural network, we are more interested in figuring out the partial derivative of the final system output with regard to a parameter $$\omega_i$$ in $$f_i$$. To do so, we generally utilize the backward derivative propagation function + +$$\mathbb{B}[f_i] = f_i^{-1}(\frac{\partial Y}{\partial f_i}) = \frac{\partial Y}{\partial \mathbf{w}_i}$$ + +Where the backward propagation function $$\mathbb{B}[f_i]$$ takes as input the partial derivative of the final system output $$Y$$ with regard to the output of $$f_i$$ (i.e. $$\mathbf{w}_i$$), and computes the partial derivative of the final system output with regard to the input of $$f_i$$ (i.e. $$\mathbf{w}_{i-1}$$). + +The higher order operator $$\mathbb{F}$$ and $$\mathbb{B}$$ represent the operations that converts an original or primal function $$f$$ to its forward or backward derivative propagation function. Slang's automatic differentiation feature provide built-in support for these operators to automatically generate the derivative propagation functions from a user defined primal function. The remaining documentation will discuss this feature from a programming language perspective. + +## Differentiable Types +Slang will only generate differentiation code for values that has a *differentiable* type. A type is differentiable if it conforms to the built-in `IDifferentiable` interface. The definition of the `IDifferentiable` interface is: +```csharp +interface IDifferentiable +{ + associatedtype Differential : IDifferentiable + where Differential.Differential == Differential; + + static Differential dzero(); + + static Differential dadd(Differential, Differential); + + static Differential dmul(This, Differential); +} +``` +As defined by the `IDifferentiable` interface, a differentiable type must have a `Differential` associated type that stores the derivative of the value. A further requirement is that the type of the second-order derivative must be the same `Differential` type. In another word, given a type `T`, `T.Differential` can be different from `T`, but `T.Differential.Differential` must equal to `T.Differential`. + +In addition, a differentiable type must define the `zero` value of its derivative, and how to add and multiply derivative values. + +### Builtin Differentiable Types +The following built-in types are differentiable: +- Scalars: `float`, `double` and `half`. +- Vector/Matrix: `vector` and `matrix` of `float`, `double` and `half` types. +- Arrays: `T[n]` is differentiable if `T` is differentiable. + +### User Defined Differentiable Types + +The user can make any `struct` types differentiable by implementing the `IDifferentiable` interface on the type. The requirements from `IDifferentiable` interface can be fulfilled automatically or manually. + +#### Automatic Fulfillment of `IDifferentiable` Requirements +Assume the user has defined the following type: + +```csharp +struct MyRay +{ + float3 origin; + float3 dir; + int nonDifferentiablePayload; +} +``` + +The type can be made differentiable by adding `IDifferentiable` conformance: +```csharp +struct MyRay : IDifferentiable +{ + float3 origin; + float3 dir; + int nonDifferentiablePayload; +} +``` + +Note that this code does not provide any explicit implementation of the `IDifferentiable` requirements. In this case the compiler will automatically synthesize all the requirements. This should provide the desired behavior most of the time. The procedure for synthesizing the interface implementation is as follows: +1. A new type is generated that stores the `Differential` of all differentiable fields. This new type itself will conform to the `IDifferentiable` interface, and it will be used to satisfy the `Differential` associated type requirement. +2. Each differential field will be associated to its corresponding field in the newly synthesized `Differential` type. +3. The `zero` value of the differential type is made from the `zero` value of each field in the differential type. +4. The `dadd` and `dmul` methods simply perform `dadd` and `dmul` operations on each field. +5. If the synthesized `Differential` type contains exactly the same fields as the original type, and the type of each field is the same as the original field type, then the original type itself will be used as the `Differential` type instead of creating a new type to satisfy the `Differential` associated type requirement. This means that all the synthesized `Differential` type use itself to meet its own `IDifferentiable` requirements. + +#### Manual Fulfillment of `IDifferentiable` Requirements + +In rare cases where more control is desired, the user can manually provide the implementation. To do so, we will first define the `Differential` type for `MyRay`, and use it to fulfill the `Differential` requirement in `MyRay`: + +```csharp +struct MyRayDifferential +{ + float3 d_origin; + float3 d_dir; +} + +struct MyRay : IDifferentiable +{ + // Specify that `MyRay.Differential` is `MyRayDifferential`. + typealias Differential = MyRayDifferential; + + // Specify that the derivative for `origin` will be stored in `MayRayDifferential.d_origin`. + [DerivativeMember(MayRayDifferential.d_origin)] + float3 origin; + + // Specify that the derivative for `dir` will be stored in `MayRayDifferential.d_dir`. + [DerivativeMember(MayRayDifferential.d_dir)] + float3 dir; + + // This is a non-differentiable field so we don't put any attributes on it. + int nonDifferentiablePayload; + + // Define zero derivative. + static MyRayDifferential dzero() + { + return {float3(0.0), float3(0.0)}; + } + + // Define the add operation of two derivatives. + static MyRayDifferential dadd(MyRayDifferential v1, MyRayDifferential v2) + { + MyRayDifferential result; + result.d_origin = v1.d_origin + v2.d_origin; + result.d_dir = v1.d_dir + v2.d_dir; + return result; + } + + // Define the multiply operation of a primal value and a derivative value. + static MyRayDifferential dmul(MyRay p, MyRayDifferential d) + { + MyRayDifferential result; + result.d_origin = p.origin * d.d_origin; + result.d_dir = p.dir * d.d_dir; + return result; + } +} +``` + +Note that for each struct field that is differentiable, we need to use the `[DerivativeMember]` attribute to associate it with the corresponding field in the `Differential` type, so the compiler knows how to access the derivative for the field. + +However, there is still a missing piece in the above code: we also need to make `MyRayDifferential` conform to `IDifferentiable` because it is required that the `Differential` of a type must itself be `Differential`. Again we can use automatic fulfillment by simply adding `IDifferentiable` conformance to `MyRayDifferential`: +```csharp +struct MyRayDifferential : IDifferentiable +{ + float3 d_origin; + float3 d_dir; +} +``` +In this case, since all fields in `MyRayDifferential` are differentiable, and the `Differential` of each field is the same as the original type of each field (i.e. `float3.Differential == float3` as defined in built-in library), the compiler will automatically use the type itself as its own `Differential`, making `MyRayDifferential` suitable for use as `Differential` of `MyRay`. + +We can also choose to manually implement `IDifferentiable` interface for `MyRayDifferential` as in the following code: + +```csharp +struct MyRayDifferential : IDifferentiable +{ + typealias Differential = MyRayDifferential; + + [DerivativeMember(MyRayDifferential.d_origin)] + float3 d_origin; + + [DerivativeMember(MyRayDifferential.d_dir)] + float3 d_dir; + + static MyRayDifferential dzero() + { + return {float3(0.0), float3(0.0)}; + } + + static MyRayDifferential dadd(MyRayDifferential v1, MyRayDifferential v2) + { + MyRayDifferential result; + result.d_origin = v1.d_origin + v2.d_origin; + result.d_dir = v1.d_dir + v2.d_dir; + return result; + } + + static MyRayDifferential dmul(MyRayDifferential p, MyRayDifferential d) + { + MyRayDifferential result; + result.d_origin = p.d_origin * d.d_origin; + result.d_dir = p.d_dir * d.d_dir; + return result; + } +} +``` +In this specific case, the automatically generated `IDifferentiable` implementation will be exactly the same as the manually written code listed above. + + +## Forward Derivative Propagation Function + +Functions in Slang can be marked as forward-differentiable or backward-differentiable. The `fwd_diff` operator can be used on a forward-differentiable function to obtain the forward derivative propagation function. Likewise, the `bwd_diff` operator can be used on a backward-differentiable function to obtain the backward derivative propagation function. This and the next sections cover the semantics of forward and backward propagation functions, and different ways to make a function forward and backward differentiable. + +A forward derivative propagation function computes the derivative of the result value with regard to a specific set of input parameters. +Given an original function, the signature of its forward propagation function is determined using the following rules: +- If the return type `R` is differentiable, the forward propagation function will return `DifferentialPair` that consists of both the computed original result value and the (partial) derivative of the result value. Otherwise, the return type is kept unmodified as `R`. +- If a parameter has type `T` that is differentiable, it will be translated into a `DifferentialPair` parameter in the derivative function, where the differential component of the `DifferentialPair` holds the initial derivatives of each parameter with regard to their upstream parameters. +- All parameter directions are unchanged. For example, an `out` parameter in the original function will remain an `out` parameter in the derivative function. + +For example, given original function: +```csharp +R original(T0 p0, inout T1 p1, T2 p2); +``` +Where `R`, `T0`, and `T1` is differentiable and `T2` is non-differentiable, the forward derivative function will have the following signature: +```csharp +DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2); +``` + +This forward propagation function takes the initial primal value of `p0` in `p0.p`, and the partial derivative of `p0` with regard to some upstream parameter in `p0.d`. It takes the initial primal and derivative values of `p1` and updates `p1` to hold the newly computed value and propagated derivative. Since `p2` is not differentiable, it remains unchanged. + +`DifferentialPair` is a built-in type that carries both the original and derivative value of a term. It is defined as follows: +```csharp +struct DifferentialPair : IDifferentiable +{ + typealias Differential = DifferentialPair; + property T p {get;} + property T.Differential d {get;} + static Differential dzero(); + static Differential dadd(Differential a, Differential b); + static Differential dmul(This a, Differential b); +} +``` + +### Automatic Implementation of Forward Derivative Functions + +A function can be made forward-differentiable with a `[ForwardDifferentiable]` attribute. This attribute will cause the compiler to automatically implement the forward propagation function. The syntax for using `[ForwardDifferentiable]` is: + +```csharp +[ForwardDifferentiable] +R original(T0 p0, inout T1, p1, T2 p2); +``` + +Once the function is made forward-differentiable, the forward propagation function can then be called with the `fwd_diff` operator: +```csharp +DifferentialPair result = fwd_diff(original)(...); +``` + +### User Defined Forward Derivative Functions +As an alternative to compiler-implemented forward derivatives, the user can choose to manually provide a derivative implementation to make an existing function forward-differentiable. The `[ForwardDerivative(derivative_func)]` attribute is used to associate a function with its forward derivative propagation implementation. The syntax for using `[ForwardDerivative]` attribute is: +```csharp +DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2) +{ + .... +} + +[ForwardDerivative(derivative)] +R original(T0 p0, inout T1, p1, T2 p2); +``` +If `derivative` is defined in a different scope from `original`, such as in a different namespace or `struct` type, a fully qualified name is required. For example: +```csharp +struct MyType +{ + // Implementing derivative function in a different name scope. + static DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2) + { + .... + } +} + +// Use fully qualified name in the attribute. +[ForwardDerivative(MyType.derivative)] +R original(T0 p0, inout T1, p1, T2 p2); +``` + +Sometimes the derivative function needs to be defined in a different module from the original function, or the derivative function cannot be made visible from the original function. In this case, we can use the `[ForwardDerivativeOf(originalFunnc)]` attribute to inform the compiler that `originalFunc` should be treated as a forward-differentiable function, and the current function is the derivative implementation of `originalFunc`. The following code will have the same effect to associate `derivative` and the forward-derivative implementation of `original`: + +```csharp +R original(T0 p0, inout T1, p1, T2 p2); + +[ForwardDerivativeOf(original)] +DifferentialPair derivative(DifferentialPair p0, inout DifferentialPair p1, T2 p2) +{ + .... +} +``` + +## Backward Derivative Propagation Function + +A backward derivative propagation function propagates the derivative of the function output to all the input parameters simultaneously. + +Given an original function `f`, the general rule for determining the signature of its backward propagation function is that a differentiable output `o` becomes an input parameter holding the partial derivative of a downstream output with regard to the differentiable output, i.e. $$\partial y/\partial o$$); an input differentiable parameter `i` in the original function will become an output in the backward propagation function, holding the propagated partial derivative $$\partial y/\partial i$$; and any non-differentiable outputs are dropped from the backward propagation function. This means that the backward propagation function never returns any values computed in the original function. + +More specifically, the signature of its backward propagation function is determined using the following rules: +- A backward propagation function always returns `void`. +- A differentiable `in` parameter of type `T` will become an `inout DifferentialPair` parameter, where the original value part of the differential pair contains the original value of the parameter to pass into the back-prop function. The original value will not be overwritten by the backward propagation function. The propagated derivative will be written to the derivative part of the differential pair after the backward propagation function returns. The initial derivative value of the pair is ignored as input. +- A differentiable `out` parameter of type `T` will become an `in T.Differential` parameter, carrying the partial derivative of some downstream term with regard to the return value. +- A differentiable `inout` parameter of type `T` will become an `inout DifferentialPair` parameter, where the original value of the argument, along with the downstream partial derivative with regard to the argument is passed as input to the backward propagation function as the original and derivative part of the pair. The propagated derivative with regard to this input parameter will be written back and replace the derivative part of the pair. The primal value part of the parameter will *not* be updated. +- A differentiable return value of type `R` will become an additional `in R.Differential` parameter at the end of the backward propagation function parameter list, carrying the result derivative of a downstream term with regard to the return value of the original function. +- A non-differentiable return value of type `NDR` will be dropped. +- A non-differentiable `in` parameter of type `ND` will remain unchanged in the backward propagation function. +- A non-differentiable `out` parameter of type `ND` will be removed from the parameter list of the backward propagation function. +- A non-differentiable `inout` parameter of type `ND` will become an `in ND` parameter. + +For example consider the following original function: +```csharp +struct T : IDifferentiable {...} +struct R : IDifferentiable {...} +struct ND {} // Non differentiable + +[Differentiable] +R original(T p0, out T p1, inout T p2, ND p3, out ND p4, inout ND p5); +``` +The signature of its backward propagation function is: +```csharp +void back_prop( + inout DifferentialPair p0, + T.Differential p1, + inout DifferentialPair p2, + ND p3, + ND p5, + R.Differential dResult); +``` +Note that although `p2` is still `inout` in the backward propagation function, the backward propagation function will only write propagated derivative to `p2.d` and will not modify `p2.p`. + +### Automatically Implemented Backward Propagation Functions + +A function can be made backward-differentiable with a `[Differentiable]` or `[BackwardDifferentiable]` attribute. This attribute will cause the compiler to automatically implement the backward propagation function. The syntax for using `[Differentiable]` is: + +```csharp +[Differentiable] +R original(T0 p0, inout T1, p1, T2 p2); +``` + +Once the function is made backward-differentiable, the backward propagation function can then be called with the `bwd_diff` operator: +```csharp +bwd_diff(original)(...); +``` + +### User Defined Backward Propagation Functions +Similar to user-defined forward derivative functions, the `[BackwardDerivative]` and `[BackwardDerivativeOf]` attributes can be used to supply a function with user defined backward propagation function. + +The syntax for using `[BackwardDerivative]` attribute is: +```csharp +void back_prop( + inout DifferentialPair p0, + T1.Differential p1, + inout DifferentialPair p2, + ND p3, + ND p5, + R.Differential dResult) +{ + ... +} + +[BackwardDerivative(back_prop)] +R original(T0 p0, inout T1, p1, T2 p2); +``` + +Similarly, the `[BackwardDerivativeOf]` attribute can be used on the back-prop function in case it is not convenient to modify the definition of the original function, or the back-prop function can't be made visible from the original function: + +```csharp +R original(T0 p0, inout T1, p1, T2 p2); + +[BackwardDerivativeOf(original)] +void back_prop( + inout DifferentialPair p0, + T1.Differential p1, + inout DifferentialPair p2, + ND p3, + ND p5, + R.Differential dResult) +{ + ... +} +``` + +## Builtin Differentiable Functions + +The following built-in functions are backward differentiable and both their forward-derivative and backward-propagation functions are already defined in the built-in library: + +- Arithmetic functions: `abs`, `max`, `min`, `sqrt`, `rcp`, `rsqrt`, `fma`, `mad`, `fmod`, `frac`, `radians`, `degrees` +- Interpolation and clamping functions: `lerp`, `smoothstep`, `clamp`, `saturate` +- Trigonometric functions: `sin`, `cos`, `sincos`, `tan`, `asin`, `acos`, `atan`, `atan2` +- Hyperbolic functions: `sinh`, `cosh`, `tanh` +- Exponential and logarithmic functions: `exp`, `exp2`, `pow`, `log`, `log2`, `log10` +- Vector functions: `dot`, `cross`, `length`, `distance`, `normalize`, `reflect`, `refract` +- Matrix transforms: `mul(matrix, vector)`, `mul(vector, matrix)`, `mul(matrix, matrix)` +- Matrix operations: `transpose`, `determinant` +- Legacy blending and lighting intrinsics: `dst`, `lit` + +## Primal Substitute Functions + +Sometimes it is desirable to replace a function with another when generating forward or backward derivative propagation code. For example, the following code shows a function that computes the integral of some term by sampling and we want to use a different sampling stragegy when computing the derivatives. +```csharp +float myTerm(float x) +{ + return someComplexComputation(x); +} + +float getSample(float a, float b) { ... } + +[Differentiable] +float computeIntegralOverMyTerm(float x, float a, float b) +{ + float sum = 0.0; + for (int i = 0; i < SAMPLE_COUNT; i++) + { + let s = no_diff getSample(a, b); + let y = myTerm(s); + sum += y * ((b-a)/SAMPLE_COUNT); + } + return sum; +} +``` + +In this code, the `getSample` function returns a random sample in the range of `[a,b]`. Assume we have another sampling function `getSampleForDerivativeComputation(a,b)` that we wish to use instead in derivative computation, we can do so by marking it as a primal-substitute of `getSample`, as in the following code: +```csharp +[PrimalSubstituteOf(getSample)] +float getSampleForDerivativeComputation(float a, float b) +{ + ... +} +``` + +Here, the `[PrimalSubstituteOf(getSample)]` attributes marks the `getSampleForDerivativeComputation` function as the substitute for `getSample` in derivative propagation functions. When a function has a primal substitute, the compiler will treat all calls to that function as if it is a call to the substitute function when generating derivative code. Note that this only applies to compiler generated derivative function and does not affect user provided derivative functions. If a user provided derivative function calls `getSample`, it will not be replaced by `getSampleForDerivativeComputation` by the compiler. + +Similar to `[ForwardDerivative]` and `[ForwardDerivativeOf]` attributes, The `[PrimalSubsitute(substFunc)]` attribute works the other way around: it specifies the primal substitute function of the function being marked. + +Primal substitute can be used as another way to make a function differentiable. A function is considered differentiable if it has a primal substitute that is differentiable. The following code illustrates this mechanism. +```csharp +float myFunc(float x) {...} + +[PrimalSubstituteOf(myFunc)] +[Differentiable] +float myFuncSubst(float x) {...} + +// myFunc is now considered backward differentiable. +``` + +The following example shows in more detail on how primal substitute affects derivative computation. +```csharp +float myFunc(float x) { return x*x; } + +[PrimalSubstituteOf(myFunc)] +[ForwardDifferentiable] +float myFuncSubst(float x) { return x*x*x; } + +[ForwardDifferentiable] +float caller(float x) { return myFunc(x); } + +let a = caller(4.0); // a == 16.0 (calling myFunc) +let b = fwd_diff(caller)(diffPair(4.0, 1.0)).p; // b == 64.0 (calling myFuncSubst) +let c = fwd_diff(caller)(diffPair(4.0, 1.0)).d; // c == 48.0 (calling derivative of myFuncSubst) +``` + +In case that a function has both custom defined derivatives and a differentiable primal substitute, the primal substitute overrides the custom defined derivative on the original function. All calls to the original function will be translated into calls to the primal substitute first, and differentiation step follows after. This means that the derivatives of the primal substitute function will be used instead of the derivatives defined on the original function. + +## Working with Mixed Differentiable and Non-Differentiable Code + +Introducing differentiability to an existing system often involves dealing with code that mixes differentiable and non-differentiable logic. +Slang provides type checking and code analysis features to allow users to clarify the intention and guard against unexpected behaviors involving when to propagate derivatives through operations. + +### Excluding Parameters from Differentiation + +Sometimes we do not wish a parameter to be considered differentiable despite it has a differentiable type. We can use the `no_diff` modifier on the parameter to inform the compiler to treat the parameter as non-differentiable and skip generating differentiation code for the parameter. The syntax is: + +```csharp +// Only differentiate this function with regard to `x`. +float myFunc(no_diff float a, float x); +``` + +The forward derivative and backward propagation functions of `myFunc` should have the following signature: +```csharp +DifferentialPair fwd_derivative(float a, DifferentialPair x); +void back_prop(float a, inout DifferentialPair x, float dResult); +``` + +In addition, the `no_diff` modifier can also be used on the return type to indicate the return value should be considered non-differentiable. For example, the function +```csharp +no_diff float myFunc(no_diff float a, float x, out float y); +``` +Will have the following forward derivative and backward propagation function signatures: + +```csharp +float fwd_derivative(float a, DifferentialPair x); +void back_prop(float a, inout DifferentialPair x, float d_y); +``` + +By default, the implicit `this` parameter will be treated as differentiable if the enclosing type of the member method is differentiable. If you wish to exclude `this` parameter from differentiation, use `[NoDiffThis]` attribute on the method: +```csharp +struct MyDifferentiableType : IDifferentiable +{ + [NoDiffThis] // Make `this` parameter `no_diff`. + float compute(float x) { ... } +} +``` + +### Excluding Struct Members from Differentiation + +When using automatic `IDifferentiable` conformance synthesis for a `struct` type, Slang will by-default treat all struct members that have a differentiable type as differentiable, and thus include a corresponding field in the generated `Differential` type for the struct. +For example, given the following definition +```csharp +struct MyType : IDifferentiable +{ + float member1; + float2 member2; +} +``` +Slang will generate: +```csharp +struct MyType.Differential : IDifferentiable +{ + float member1; // derivative for MyType.member1 + float2 member2; // derivative for MyType.member2 +} +``` +If the user does not want a certain member to be treated as differentiable despite it has a differentiable type, a `no_diff` modifier can be used on the struct member to exclude it from differentiation. +For example, the following code excludes `member1` from differentiation: +```csharp +struct MyType : IDifferentiable +{ + no_diff float member1; // excluded from differentiation + float2 member2; +} +``` +The generated `Differential` in this case will be: +```csharp +struct MyType.Differential : IDifferentiable +{ + float2 member2; +} +``` + +### Assigning Differentiable Values into a Non-Differentiable Location + +When a value with derivatives is being assigned to a location that is not differentiable, such as a struct member that is marked as `no_diff`, the derivative info is discarded and any derivative propagation is stopped at the assignment site. +This may lead to unexpected results. For example: +```csharp +struct MyType : IDifferentiable +{ + no_diff float member; + float someOtherMemther; +} +[ForwardDifferentiable] +float f(float x) +{ + MyType t; + t.member = x * x; // Error: assigning value with derivative into a non-differentiable location. + return t.member; +} +... +let result = fwd_diff(f)(diffPair(3.0, 1.0)).d; // result == 0.0 +``` +In this case, we are assigning the value `x*x`, which carries a derivative, into a non-differentiable location `MyType.member`, thus throwing away any derivative info. When `f` returns `t.member`, there will be no derivative associated with it, so the function will not propagate the derivative through. This code is most likely not intending to discard the derivative through the assignment. To help avoid this kind of unintentional behavior, Slang will treat any assignments of a value with derivative info into a non-differentiable location as a compile-time error. To eliminate this error, the user should either make `t.member` differentiable, or to force the assignment by clarifying the intention to discard any derivatives using the built-in `detach` method. +The following code will compile, and the derivatives will be discarded: +```csharp +[ForwardDifferentiable] +float f(float x) +{ + MyType t; + // OK: the code has expressed clearly the intention to discard the derivative and perform the assignment. + t.member = detach(x * x); + return t.member; +} +``` + +### Calling Non-Differentiable Functions from a Differentiable Function +Calling non-differentiable function from a differentiable function is allowed. However, derivatives will not be propagated through the call. The user is required to clarify the intention by prefixing the call with the `no_diff` keyword. An un-clarified call to non-differentiable function will result in a compile-time error. + +For example, consider the following code: +```csharp +float g(float x) +{ + return 2*x; +} + +[ForwardDifferentiable] +float f(float x) +{ + // Error: implicit call to non-differentiable function g. + return g(x) + x * x; +} +``` +The derivative will not propagate through the call to `g` in `f`. As a result, `fwd_diff(f)(diffPair(1.0, 1.0))` will return +`{3.0, 2.0}` instead of `{3.0, 4.0}` as the derivative from `2*x` is lost through the non-differentiable call. To prevent unintended error, it is treated as a compile-time error to call `g` from `f`. If such a non-differentiable call is intended, a `no_diff` prefix is required in the call: +```csharp +[ForwardDifferentiable] +float f(float x) +{ + // OK. The intention to call a non-differentiable function is clarified. + return no_diff g(x) + x * x; +} +``` + +However, the `no_diff` keyword is not required in a call if a non-differentiable function does not take any differentiable parameters, or if the result of the differentiable function is not dependent on the derivative being propagated through the call. + +### Treat Non-Differentiable Functions as Differentiable +Slang allows functions to be marked with a `[TreatAsDifferentiable]` attribute for them to be considered as differentiable functions by the type-system. When a function is marked as `[TreatAsDifferentiable]`, the compiler will not generate derivative propagation code from the original function body or perform any additional checking on the function definition. Instead, it will generate trivial forward and backward propagation functions that returns 0. + +This feature can be useful if the user marked an `interface` method as forward or backward differentiable, but only wish to provide non-trivial derivative propagation functions for a subset of types that implement the interface. For other types that does not actually need differentiation, the user can simply put `[TreatAsDifferentiable]` on the method implementations for them to satisfy the interface requirement. + +See the following code for an example of `[TreatAsDifferentiable]`: +```csharp +interface IFoo +{ + [Differentiable] + float f(float v); +} + +struct B : IFoo +{ + [TreatAsDifferentiable] + float f(float v) + { + return v * v; + } +} + +[Differentiable] +float use(IFoo o, float x) +{ + return o.f(x); +} + +// Test: +B obj; +float result = fwd_diff(use)(obj, diffPair(2.0, 1.0)).d; +// result == 0.0, since `[TreatAsDifferentiable]` causes a trivial derivative implementation +// being generated regardless of the original code. +``` + +## Higher Order Differentiation + +Slang supports generating higher order forward and backward derivative propagation functions. It is allowed to use `fwd_diff` and `bwd_diff` operators inside a forward or backward differentiable function, or to nest `fwd_diff` and `bwd_diff` operators. For example, `fwd_diff(fwd_diff(sin))` will have the following signature: + +```csharp +DifferentialPair> sin_diff2(DifferentialPair> x); +``` + +The input parameter `x` contains four fields: `x.p.p`, `x.p.d,`, `x.d.p`, `x.d.d`, where `x.p.p` specifies the original input value, both `x.p.d` and `x.d.p` store the first order derivative if `x`, and `x.d.d` stores the second order derivative of `x`. Calling `fwd_diff(fwd_diff(sin))` with `diffPair(diffPair(pi/2, 1.0), DiffPair(1.0, 0.0))` will result `{ { 1.0, 0.0 }, { 0.0, -1.0 } }`. + +User defined higher-order derivative functions can be specified by using `[ForwardDerivative]` or `[BackwardDerivative]` attribute on the derivative function, or by using `[ForwardDerivativeOf]` or `[BackwardDerivativeOf]` attribute on the higher-order derivative function. + +## Interactions with Generics and Interfaces + +Automatic differentiation for generic functions is supported. The forward-derivative and backward propagation functions of a generic function is also a generic function with the same set of generic parameters and constraints. Using `[ForwardDerivative]`, `[ForwardDerivativeOf]`, `[BackwardDerivative]` or `[BackwardDerivativeOf]` attributes to associate a derivative function with different set of generic parameters or constraints is a compile-time error. + +An interface method requirement can be marked as `[ForwardDifferentiable]` or `[Differentiable]`, so they may be called in a forward or backward differentiable function and have the derivatives propagate through the call. This works regardless of whether the call can be specialized or has to go through dynamic dispatch. However, calls to interface methods are only differentiable once. Higher order differentiation through interface method calls are not supported. + +## Restrictions of Automatic Differentiation + +The compiler can generate forward derivative and backward propagation implementations for most uses of array and struct types, including arbitrary read and write access at dynamic array indices, and supports uses of all types of control flows, mutable parameters, generics and interfaces. This covers the set of operations that is sufficient for a lot of functions. However, the user needs to be aware of the following restrictions when using automatic differentiation: + +- All operations to global resources, global variables and shader parameters, including texture reads or atomic writes, are treating as a non-differentiable operation. +- If a differentiable function contains calls that cause side-effects such as updates to global memory, there will not be a guarantee on how many times the side-effect will occur during the resulting derivative function or back-propagation function. +- Loops: Loops must use the attribute `[MaxIters()]` to specify a maximum number of iterations. This will be used by compiler to allocate space to store intermediate data. If the actual number of iterations exceeds the provided maximum, the behavior is undefined. You can always mark a loop with the `[ForceUnroll]` attribute to instruct the Slang compiler to unroll the loop before generating derivative propagation functions. Unrolled loops will be treated the same way as ordinary code and are not subject to any additional restrictions. + +The above restrictions do not apply if a user-defined derivative or backward propagation function is provided. diff --git a/docs/user-guide/toc.html b/docs/user-guide/toc.html index 48a7780d6..615b96b15 100644 --- a/docs/user-guide/toc.html +++ b/docs/user-guide/toc.html @@ -45,51 +45,53 @@
  • Modules
  • -
  • Interfaces and Generics +
  • Modules and Access Control +
  • +
  • Interfaces and Generics
      -
    • Interfaces
    • -
    • Generics
    • -
    • Supported Constructs in Interface Definitions
    • -
    • Associated Types
    • -
    • Generic Value Parameters
    • -
    • Interface-typed Values
    • -
    • Extending a Type with Additional Interface Conformances
    • -
    • `is` and `as` Operator
    • -
    • Extensions to Interfaces
    • +
    • Interfaces
    • +
    • Generics
    • +
    • Supported Constructs in Interface Definitions
    • +
    • Associated Types
    • +
    • Generic Value Parameters
    • +
    • Interface-typed Values
    • +
    • Extending a Type with Additional Interface Conformances
    • +
    • `is` and `as` Operator
    • +
    • Extensions to Interfaces
  • -
  • Compiling Code with Slang +
  • Compiling Code with Slang
      -
    • Concepts
    • -
    • Command-Line Compilation with `slangc`
    • -
    • Using the Compilation API
    • +
    • Concepts
    • +
    • Command-Line Compilation with `slangc`
    • +
    • Using the Compilation API
  • -
  • Supported Compilation Targets +
  • Supported Compilation Targets
      -
    • Background and Terminology
    • -
    • Direct3D 11
    • -
    • Direct3D 12
    • -
    • Vulkan
    • -
    • OpenGL
    • -
    • CUDA and OptiX
    • -
    • CPU Compute
    • -
    • Summary
    • +
    • Background and Terminology
    • +
    • Direct3D 11
    • +
    • Direct3D 12
    • +
    • Vulkan
    • +
    • OpenGL
    • +
    • CUDA and OptiX
    • +
    • CPU Compute
    • +
    • Summary
  • -
  • Automatic Differentiation +
  • Automatic Differentiation
      -
    • Using Automatic Differentiation in Slang
    • -
    • Mathematic Concepts and Terminologies
    • -
    • Differentiable Types
    • -
    • Forward Derivative Propagation Function
    • -
    • Backward Derivative Propagation Function
    • -
    • Builtin Differentiable Functions
    • -
    • Primal Substitute Functions
    • -
    • Working with Mixed Differentiable and Non-Differentiable Code
    • -
    • Higher Order Differentiation
    • -
    • Interactions with Generics and Interfaces
    • -
    • Restrictions of Automatic Differentiation
    • +
    • Using Automatic Differentiation in Slang
    • +
    • Mathematic Concepts and Terminologies
    • +
    • Differentiable Types
    • +
    • Forward Derivative Propagation Function
    • +
    • Backward Derivative Propagation Function
    • +
    • Builtin Differentiable Functions
    • +
    • Primal Substitute Functions
    • +
    • Working with Mixed Differentiable and Non-Differentiable Code
    • +
    • Higher Order Differentiation
    • +
    • Interactions with Generics and Interfaces
    • +
    • Restrictions of Automatic Differentiation
  • Special Topics diff --git a/examples/cpu-com-example/shader.slang b/examples/cpu-com-example/shader.slang index 951fdc218..b312da364 100644 --- a/examples/cpu-com-example/shader.slang +++ b/examples/cpu-com-example/shader.slang @@ -2,9 +2,9 @@ // Example using 'NativeString' and COM interface -public __global __extern_cpp IDoThings globalDoThings; +export __global __extern_cpp IDoThings globalDoThings; -public __extern_cpp NativeString getString(NativeString in) +export __extern_cpp NativeString getString(NativeString in) { return in; } @@ -17,12 +17,12 @@ interface IDoThings void printMessage(NativeString nativeString); } -public __extern_cpp int calcHash(NativeString text) +export __extern_cpp int calcHash(NativeString text) { return globalDoThings.calcHash(text); } -public __extern_cpp void printMessage(NativeString text) +export __extern_cpp void printMessage(NativeString text) { return globalDoThings.printMessage(text); } diff --git a/source/slang/diff.meta.slang b/source/slang/diff.meta.slang index 31ed5845e..6f4888a5d 100644 --- a/source/slang/diff.meta.slang +++ b/source/slang/diff.meta.slang @@ -26,7 +26,7 @@ __attributeTarget(FunctionDeclBase) attribute_syntax [NoDiffThis] : NoDiffThisAttribute; // A 'none-type' that acts as a run-time sentinel for zero differentials. -public struct NullDifferential : IDifferentiable +export struct NullDifferential : IDifferentiable { // for now, we'll use at least one field to make sure the type is non-empty uint dummy; diff --git a/source/slang/glsl.meta.slang b/source/slang/glsl.meta.slang index 98f30cd3c..a43a7ba3c 100644 --- a/source/slang/glsl.meta.slang +++ b/source/slang/glsl.meta.slang @@ -2,385 +2,385 @@ // From the GLSL spec, section 4.1. 'asic Types' // -typealias vec2 = vector; -typealias vec3 = vector; -typealias vec4 = vector; - -typealias dvec2 = vector; -typealias dvec3 = vector; -typealias dvec4 = vector; - -typealias bvec2 = vector; -typealias bvec3 = vector; -typealias bvec4 = vector; - -typealias ivec2 = vector; -typealias ivec3 = vector; -typealias ivec4 = vector; - -typealias uvec2 = vector; -typealias uvec3 = vector; -typealias uvec4 = vector; - -typealias i8vec2 = vector; -typealias i8vec3 = vector; -typealias i8vec4 = vector; - -typealias u8vec2 = vector; -typealias u8vec3 = vector; -typealias u8vec4 = vector; - -typealias i16vec2 = vector; -typealias i16vec3 = vector; -typealias i16vec4 = vector; - -typealias u16vec2 = vector; -typealias u16vec3 = vector; -typealias u16vec4 = vector; - -typealias i64vec2 = vector; -typealias i64vec3 = vector; -typealias i64vec4 = vector; - -typealias u64vec2 = vector; -typealias u64vec3 = vector; -typealias u64vec4 = vector; - -typealias mat2 = matrix; -typealias mat3 = matrix; -typealias mat4 = matrix; - -typealias mat2x2 = matrix; -typealias mat2x3 = matrix; -typealias mat2x4 = matrix; - -typealias mat3x2 = matrix; -typealias mat3x3 = matrix; -typealias mat3x4 = matrix; - -typealias mat4x2 = matrix; -typealias mat4x3 = matrix; -typealias mat4x4 = matrix; - -typealias dmat2 = matrix; -typealias dmat3 = matrix; -typealias dmat4 = matrix; - -typealias dmat2x2 = matrix; -typealias dmat2x3 = matrix; -typealias dmat2x4 = matrix; - -typealias dmat3x2 = matrix; -typealias dmat3x3 = matrix; -typealias dmat3x4 = matrix; - -typealias dmat4x2 = matrix; -typealias dmat4x3 = matrix; -typealias dmat4x4 = matrix; - -typealias usampler1D = Sampler1D; -typealias isampler1D = Sampler1D; -typealias sampler1D = Sampler1D; - -typealias usampler2D = Sampler2D; -typealias isampler2D = Sampler2D; -typealias sampler2D = Sampler2D; - -typealias usampler3D = Sampler3D; -typealias isampler3D = Sampler3D; -typealias sampler3D = Sampler3D; - -typealias usamplerCube = SamplerCube; -typealias isamplerCube = SamplerCube; -typealias samplerCube = SamplerCube; - -typealias Sampler1DShadow = __TextureImpl; -typealias usampler1DShadow = Sampler1DShadow; -typealias isampler1DShadow = Sampler1DShadow; -typealias sampler1DShadow = Sampler1DShadow; - -typealias Sampler2DShadow = __TextureImpl; -typealias usampler2DShadow = Sampler2DShadow; -typealias isampler2DShadow = Sampler2DShadow; -typealias sampler2DShadow = Sampler2DShadow; - -typealias SamplerCubeShadow = __TextureImpl; -typealias usamplerCubeShadow = SamplerCubeShadow; -typealias isamplerCubeShadow = SamplerCubeShadow; -typealias samplerCubeShadow = SamplerCubeShadow; - -typealias usampler1DArray = Sampler1DArray; -typealias isampler1DArray = Sampler1DArray; -typealias sampler1DArray = Sampler1DArray; - -typealias usampler2DArray = Sampler2DArray; -typealias isampler2DArray = Sampler2DArray; -typealias sampler2DArray = Sampler2DArray; - -typealias usamplerCubeArray = SamplerCubeArray; -typealias isamplerCubeArray = SamplerCubeArray; -typealias samplerCubeArray = SamplerCubeArray; - -typealias Sampler1DArrayShadow = __TextureImpl; -typealias usampler1DArrayShadow = Sampler1DArrayShadow; -typealias isampler1DArrayShadow = Sampler1DArrayShadow; -typealias sampler1DArrayShadow = Sampler1DArrayShadow; +public typealias vec2 = vector; +public typealias vec3 = vector; +public typealias vec4 = vector; + +public typealias dvec2 = vector; +public typealias dvec3 = vector; +public typealias dvec4 = vector; + +public typealias bvec2 = vector; +public typealias bvec3 = vector; +public typealias bvec4 = vector; + +public typealias ivec2 = vector; +public typealias ivec3 = vector; +public typealias ivec4 = vector; + +public typealias uvec2 = vector; +public typealias uvec3 = vector; +public typealias uvec4 = vector; + +public typealias i8vec2 = vector; +public typealias i8vec3 = vector; +public typealias i8vec4 = vector; + +public typealias u8vec2 = vector; +public typealias u8vec3 = vector; +public typealias u8vec4 = vector; + +public typealias i16vec2 = vector; +public typealias i16vec3 = vector; +public typealias i16vec4 = vector; + +public typealias u16vec2 = vector; +public typealias u16vec3 = vector; +public typealias u16vec4 = vector; + +public typealias i64vec2 = vector; +public typealias i64vec3 = vector; +public typealias i64vec4 = vector; + +public typealias u64vec2 = vector; +public typealias u64vec3 = vector; +public typealias u64vec4 = vector; + +public typealias mat2 = matrix; +public typealias mat3 = matrix; +public typealias mat4 = matrix; + +public typealias mat2x2 = matrix; +public typealias mat2x3 = matrix; +public typealias mat2x4 = matrix; + +public typealias mat3x2 = matrix; +public typealias mat3x3 = matrix; +public typealias mat3x4 = matrix; + +public typealias mat4x2 = matrix; +public typealias mat4x3 = matrix; +public typealias mat4x4 = matrix; + +public typealias dmat2 = matrix; +public typealias dmat3 = matrix; +public typealias dmat4 = matrix; + +public typealias dmat2x2 = matrix; +public typealias dmat2x3 = matrix; +public typealias dmat2x4 = matrix; + +public typealias dmat3x2 = matrix; +public typealias dmat3x3 = matrix; +public typealias dmat3x4 = matrix; + +public typealias dmat4x2 = matrix; +public typealias dmat4x3 = matrix; +public typealias dmat4x4 = matrix; + +public typealias usampler1D = Sampler1D; +public typealias isampler1D = Sampler1D; +public typealias sampler1D = Sampler1D; + +public typealias usampler2D = Sampler2D; +public typealias isampler2D = Sampler2D; +public typealias sampler2D = Sampler2D; + +public typealias usampler3D = Sampler3D; +public typealias isampler3D = Sampler3D; +public typealias sampler3D = Sampler3D; + +public typealias usamplerCube = SamplerCube; +public typealias isamplerCube = SamplerCube; +public typealias samplerCube = SamplerCube; + +public typealias Sampler1DShadow = __TextureImpl; +public typealias usampler1DShadow = Sampler1DShadow; +public typealias isampler1DShadow = Sampler1DShadow; +public typealias sampler1DShadow = Sampler1DShadow; + +public typealias Sampler2DShadow = __TextureImpl; +public typealias usampler2DShadow = Sampler2DShadow; +public typealias isampler2DShadow = Sampler2DShadow; +public typealias sampler2DShadow = Sampler2DShadow; + +public typealias SamplerCubeShadow = __TextureImpl; +public typealias usamplerCubeShadow = SamplerCubeShadow; +public typealias isamplerCubeShadow = SamplerCubeShadow; +public typealias samplerCubeShadow = SamplerCubeShadow; + +public typealias usampler1DArray = Sampler1DArray; +public typealias isampler1DArray = Sampler1DArray; +public typealias sampler1DArray = Sampler1DArray; + +public typealias usampler2DArray = Sampler2DArray; +public typealias isampler2DArray = Sampler2DArray; +public typealias sampler2DArray = Sampler2DArray; + +public typealias usamplerCubeArray = SamplerCubeArray; +public typealias isamplerCubeArray = SamplerCubeArray; +public typealias samplerCubeArray = SamplerCubeArray; + +public typealias Sampler1DArrayShadow = __TextureImpl; +public typealias usampler1DArrayShadow = Sampler1DArrayShadow; +public typealias isampler1DArrayShadow = Sampler1DArrayShadow; +public typealias sampler1DArrayShadow = Sampler1DArrayShadow; -typealias Sampler2DArrayShadow = __TextureImpl; -typealias usampler2DArrayShadow = Sampler2DArrayShadow; -typealias isampler2DArrayShadow = Sampler2DArrayShadow; -typealias sampler2DArrayShadow = Sampler2DArrayShadow; - -typealias SamplerCubeArrayShadow = __TextureImpl; -typealias usamplerCubeArrayShadow = SamplerCubeArrayShadow; -typealias isamplerCubeArrayShadow = SamplerCubeArrayShadow; -typealias samplerCubeArrayShadow = SamplerCubeArrayShadow; +public typealias Sampler2DArrayShadow = __TextureImpl; +public typealias usampler2DArrayShadow = Sampler2DArrayShadow; +public typealias isampler2DArrayShadow = Sampler2DArrayShadow; +public typealias sampler2DArrayShadow = Sampler2DArrayShadow; + +public typealias SamplerCubeArrayShadow = __TextureImpl; +public typealias usamplerCubeArrayShadow = SamplerCubeArrayShadow; +public typealias isamplerCubeArrayShadow = SamplerCubeArrayShadow; +public typealias samplerCubeArrayShadow = SamplerCubeArrayShadow; [ForceInline] -vector texelFetch (Sampler1D> sampler, int p, int lod) +public vector texelFetch (Sampler1D> sampler, int p, int lod) { return __vectorReshape<4>(sampler.Load(int2(p, lod))); } [ForceInline] -vector texelFetch (Sampler2D> sampler, ivec2 p, int lod) +public vector texelFetch (Sampler2D> sampler, ivec2 p, int lod) { return __vectorReshape<4>(sampler.Load(int3(p, lod))); } [ForceInline] -vector texelFetch (Sampler3D> sampler, ivec3 p, int lod) +public vector texelFetch (Sampler3D> sampler, ivec3 p, int lod) { return __vectorReshape<4>(sampler.Load(int4(p, lod))); } [ForceInline] -vector texelFetch (Sampler1DArray> sampler, ivec2 p, int lod) +public vector texelFetch (Sampler1DArray> sampler, ivec2 p, int lod) { return __vectorReshape<4>(sampler.Load(int3(p, lod))); } [ForceInline] -vector texelFetch (Sampler2DArray> sampler, ivec3 p, int lod) +public vector texelFetch (Sampler2DArray> sampler, ivec3 p, int lod) { return __vectorReshape<4>(sampler.Load(int4(p, lod))); } [ForceInline] -vector texture (Sampler1D> sampler, float p, float bias = 0.0) +public vector texture (Sampler1D> sampler, float p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -vector texture (Sampler2D> sampler, float2 p, float bias = 0.0) +public vector texture (Sampler2D> sampler, float2 p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -vector texture (Sampler3D> sampler, float3 p, float bias = 0.0) +public vector texture (Sampler3D> sampler, float3 p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -vector texture (SamplerCube> sampler, float3 p, float bias = 0.0) +public vector texture (SamplerCube> sampler, float3 p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -float texture (Sampler1DShadow> sampler, float2 p) +public float texture (Sampler1DShadow> sampler, float2 p) { return sampler.SampleCmp(p.x, p.y); } [ForceInline] -float texture (Sampler2DShadow> sampler, float3 p) +public float texture (Sampler2DShadow> sampler, float3 p) { return sampler.SampleCmp(p.xy, p.z); } [ForceInline] -float texture (SamplerCubeShadow> sampler, float4 p) +public float texture (SamplerCubeShadow> sampler, float4 p) { return sampler.SampleCmp(p.xyz, p.w); } [ForceInline] -vector texture (Sampler1DArray> sampler, float2 p, float bias = 0.0) +public vector texture (Sampler1DArray> sampler, float2 p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -vector texture (Sampler2DArray> sampler, float3 p, float bias = 0.0) +public vector texture (Sampler2DArray> sampler, float3 p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -vector texture (SamplerCubeArray> sampler, float4 p, float bias = 0.0) +public vector texture (SamplerCubeArray> sampler, float4 p, float bias = 0.0) { return __vectorReshape<4>(sampler.SampleBias(p, bias)); } [ForceInline] -float texture (Sampler1DArrayShadow> sampler, float3 p) +public float texture (Sampler1DArrayShadow> sampler, float3 p) { return sampler.SampleCmp(p.xy, p.z); } [ForceInline] -float texture (Sampler2DArrayShadow> sampler, float4 p) +public float texture (Sampler2DArrayShadow> sampler, float4 p) { return sampler.SampleCmp(p.xyz, p.w); } __generic -vector textureGrad(__TextureImpl, shape, isArray, 0, sampleCount, 0, 1, 1, format> sampler, vector P, vector dPdx, vector dPdy) +public vector textureGrad(__TextureImpl, shape, isArray, 0, sampleCount, 0, 1, 1, format> sampler, vector P, vector dPdx, vector dPdy) { return __vectorReshape<4>(sampler.SampleGrad(P, dPdx, dPdy)); } -out float4 gl_Position : SV_Position; -out float gl_PointSize : SV_PointSize; -in vec4 gl_FragCoord : SV_Position; -out float gl_FragDepth : SV_Depth; -out int gl_FragStencilRef : SV_StencilRef; +public out float4 gl_Position : SV_Position; +public out float gl_PointSize : SV_PointSize; +public in vec4 gl_FragCoord : SV_Position; +public out float gl_FragDepth : SV_Depth; +public out int gl_FragStencilRef : SV_StencilRef; -in uvec3 gl_GlobalInvocationID : SV_DispatchThreadID; -in uvec3 gl_WorkGroupID : SV_GroupID; -in uvec3 gl_LocalInvocationIndex : SV_GroupIndex; -in uvec3 gl_LocalInvocationID : SV_GroupThreadID; +public in uvec3 gl_GlobalInvocationID : SV_DispatchThreadID; +public in uvec3 gl_WorkGroupID : SV_GroupID; +public in uvec3 gl_LocalInvocationIndex : SV_GroupIndex; +public in uvec3 gl_LocalInvocationID : SV_GroupThreadID; // TODO: define overload for tessellation control stage. -in int gl_InvocationID : SV_GSInstanceID; +public in int gl_InvocationID : SV_GSInstanceID; -in int gl_InstanceIndex : SV_InstanceID; -in bool gl_FrontFacing : SV_IsFrontFace; +public in int gl_InstanceIndex : SV_InstanceID; +public in bool gl_FrontFacing : SV_IsFrontFace; // TODO: define overload for geometry stage. -in int gl_Layer : SV_RenderTargetArrayIndex; +public in int gl_Layer : SV_RenderTargetArrayIndex; -in int gl_SampleID : SV_SampleIndex; -in int gl_VertexIndex : SV_VertexID; -in int gl_ViewIndex : SV_ViewID; -in int gl_ViewportIndex : SV_ViewportArrayIndex; +public in int gl_SampleID : SV_SampleIndex; +public in int gl_VertexIndex : SV_VertexID; +public in int gl_ViewIndex : SV_ViewID; +public in int gl_ViewportIndex : SV_ViewportArrayIndex; // Override operator* behavior to compute algebric product of matrices and vectors. [OverloadRank(15)] [ForceInline] -matrix operator*(matrix m1, matrix m2) +public matrix operator*(matrix m1, matrix m2) { return mul(m2, m1); } [OverloadRank(15)] [ForceInline] -matrix operator*(matrix m1, matrix m2) +public matrix operator*(matrix m1, matrix m2) { return mul(m2, m1); } [OverloadRank(15)] [ForceInline] -matrix operator*(matrix m1, matrix m2) +public matrix operator*(matrix m1, matrix m2) { return mul(m2, m1); } [ForceInline] [OverloadRank(15)] -matrix operator*(matrix m1, matrix m2) +public matrix operator*(matrix m1, matrix m2) { return mul(m2, m1); } [ForceInline] [OverloadRank(15)] -vector operator*(vector v, matrix m) +public vector operator*(vector v, matrix m) { return mul(m, v); } [ForceInline] [OverloadRank(15)] -vector operator*(matrix m, vector v) +public vector operator*(matrix m, vector v) { return mul(v, m); } __intrinsic_op(mul) -matrix matrixCompMult(matrix left, matrix right); +public matrix matrixCompMult(matrix left, matrix right); __intrinsic_op(cmpLE) -vector lessThanEqual(vector x, vector y); +public vector lessThanEqual(vector x, vector y); __intrinsic_op(cmpLT) -vector lessThan(vector x, vector y); +public vector lessThan(vector x, vector y); __intrinsic_op(cmpGT) -vector greaterThan(vector x, vector y); +public vector greaterThan(vector x, vector y); __intrinsic_op(cmpGE) -vector greaterThanEqual(vector x, vector y); +public vector greaterThanEqual(vector x, vector y); __intrinsic_op(cmpEQ) -vector equal(vector x, vector y); +public vector equal(vector x, vector y); __intrinsic_op(cmpNE) -vector notEqual(vector x, vector y); +public vector notEqual(vector x, vector y); __generic -extension vector +public extension vector { - [ForceInline] __init(vector bigger) { this = bigger.xy; } - [ForceInline] __init(vector bigger) { this = bigger.xy; } + [ForceInline] public __init(vector bigger) { this = bigger.xy; } + [ForceInline] public __init(vector bigger) { this = bigger.xy; } } __generic -extension vector +public extension vector { - [ForceInline] __init(vector bigger) { this = bigger.xyz; } + [ForceInline] public __init(vector bigger) { this = bigger.xyz; } } [ForceInline] [OverloadRank(15)] -bool operator==(vector left, vector right) +public bool operator==(vector left, vector right) { return all(equal(left, right)); } [ForceInline] [OverloadRank(15)] -bool operator!=(vector left, vector right) +public bool operator!=(vector left, vector right) { return any(notEqual(left, right)); } [ForceInline] [OverloadRank(14)] -bool operator==(vector left, vector right) +public bool operator==(vector left, vector right) { return all(equal(left, right)); } [ForceInline] [OverloadRank(14)] -bool operator!=(vector left, vector right) +public bool operator!=(vector left, vector right) { return any(notEqual(left, right)); } [ForceInline] [OverloadRank(14)] -bool operator==(vector left, vector right) +public bool operator==(vector left, vector right) { return all(equal(left, right)); } [ForceInline] [OverloadRank(14)] -bool operator!=(vector left, vector right) +public bool operator!=(vector left, vector right) { return any(notEqual(left, right)); } @@ -392,14 +392,14 @@ for (auto type : kBaseTypes) { }}}} [ForceInline] [OverloadRank(15)] -bool operator==(vector<$(typeName), N> left, vector<$(typeName), N> right) +public bool operator==(vector<$(typeName), N> left, vector<$(typeName), N> right) { return all(equal(left, right)); } [ForceInline] [OverloadRank(15)] -bool operator!=(vector<$(typeName), N> left, vector<$(typeName), N> right) +public bool operator!=(vector<$(typeName), N> left, vector<$(typeName), N> right) { return any(notEqual(left, right)); } @@ -407,13 +407,13 @@ ${{{{ } }}}} -[ForceInline] int findLSB(int v) { return firstbitlow(v); } -[ForceInline] uint findLSB(uint v) { return firstbitlow(v); } -[ForceInline] vector findLSB(vector value) +[ForceInline] public int findLSB(int v) { return firstbitlow(v); } +[ForceInline] public uint findLSB(uint v) { return firstbitlow(v); } +[ForceInline] public vector findLSB(vector value) { return firstbitlow(value); } -[ForceInline] vector findLSB(vector value) +[ForceInline] public vector findLSB(vector value) { return firstbitlow(value); } diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h index ddff39502..ab4fbaf17 100644 --- a/source/slang/slang-ast-decl.h +++ b/source/slang/slang-ast-decl.h @@ -426,6 +426,15 @@ class ModuleDecl : public NamespaceDeclBase /// OrderedDictionary> mapDeclToAssociatedDecls; + /// Whether the module is defined in legacy language. + /// The legacy Slang language does not have visibility modifiers and everything is treated as + /// `public`. Newer version of the language introduces visibility and makes `internal` as the + /// default. To prevent this from breaking existing code, we need to know whether a module is + /// written in the legacy language. We detect this by checking whether the module has any + /// visibility modifiers, or if the module uses new language constructs, e.g. `module`, `__include`, + /// `__implementing` etc. + bool isInLegacyLanguage = true; + SLANG_UNREFLECTED /// Map a type to the list of extensions of that type (if any) declared in this module diff --git a/source/slang/slang-ast-modifier.h b/source/slang/slang-ast-modifier.h index 9ee3fea86..ae87c4e10 100644 --- a/source/slang/slang-ast-modifier.h +++ b/source/slang/slang-ast-modifier.h @@ -16,7 +16,10 @@ class ConstModifier : public Modifier { SLANG_AST_CLASS(ConstModifier)}; class InstanceModifier : public Modifier { SLANG_AST_CLASS(InstanceModifier)}; class BuiltinModifier : public Modifier { SLANG_AST_CLASS(BuiltinModifier)}; class InlineModifier : public Modifier { SLANG_AST_CLASS(InlineModifier)}; -class PublicModifier : public Modifier { SLANG_AST_CLASS(PublicModifier)}; +class VisibilityModifier : public Modifier {SLANG_AST_CLASS(VisibilityModifier)}; +class PublicModifier : public VisibilityModifier { SLANG_AST_CLASS(PublicModifier)}; +class PrivateModifier : public VisibilityModifier { SLANG_AST_CLASS(PrivateModifier) }; +class InternalModifier : public VisibilityModifier { SLANG_AST_CLASS(InternalModifier) }; class RequireModifier : public Modifier { SLANG_AST_CLASS(RequireModifier)}; class ParamModifier : public Modifier { SLANG_AST_CLASS(ParamModifier)}; class ExternModifier : public Modifier { SLANG_AST_CLASS(ExternModifier)}; diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index 5da19f377..1e71d0c4a 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -1598,7 +1598,13 @@ namespace Slang /// Get the operator name from the higher order invoke expr. UnownedStringSlice getHigherOrderOperatorName(HigherOrderInvokeExpr* expr); - + enum class DeclVisibility + { + Private, + Internal, + Public, + Default = Internal, + }; } // namespace Slang #endif diff --git a/source/slang/slang-check-conversion.cpp b/source/slang/slang-check-conversion.cpp index c2cfabcfe..d76b2a80e 100644 --- a/source/slang/slang-check-conversion.cpp +++ b/source/slang/slang-check-conversion.cpp @@ -1063,7 +1063,7 @@ namespace Slang overloadContext.argCount = 1; overloadContext.argTypes = &fromType.type; overloadContext.args = &fromExpr; - + overloadContext.sourceScope = m_outerScope; overloadContext.originalExpr = nullptr; if(fromExpr) { diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index b4cc6c00a..671bb26dd 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -298,7 +298,9 @@ namespace Slang { // Things at the global scope are always "members" of their module. // - if(as(parentDecl)) + if(as(parentDecl)) + return false; + if (as(parentDecl)) return false; // Anything explicitly marked `static` and not at module scope @@ -373,7 +375,7 @@ namespace Slang auto parentDecl = decl->parentDecl; if (auto genericDecl = as(parentDecl)) parentDecl = genericDecl->parentDecl; - return as(parentDecl) != nullptr; + return as(parentDecl) != nullptr || as(parentDecl) != nullptr; } /// Is `decl` a global shader parameter declaration? @@ -385,7 +387,7 @@ namespace Slang // A global shader parameter must be declared at global or namespace // scope, so that it has a single definition across the module. // - if(!as(decl->parentDecl)) return false; + if(!isGlobalDecl(decl)) return false; // A global variable marked `static` indicates a traditional // global variable (albeit one that is implicitly local to @@ -733,6 +735,7 @@ namespace Slang // The coding of this loop is somewhat defensive to deal // with special cases that will be described along the way. // + auto outerScope = getScope(decl); for(;;) { // The first thing is to check what state the decl is @@ -757,6 +760,8 @@ namespace Slang // cannot affect the state in which the declaration is *checked*. // SemanticsContext subContext = baseContext ? SemanticsContext(*baseContext) : SemanticsContext(getShared()); + if (outerScope) + subContext = subContext.withOuterScope(outerScope); _dispatchDeclCheckingVisitor(decl, nextState, subContext); // In the common case, the visitor will have done the necessary @@ -1245,6 +1250,8 @@ namespace Slang } } } + + checkVisibility(varDecl); } void SemanticsDeclHeaderVisitor::visitStructDecl(StructDecl* structDecl) @@ -1261,11 +1268,12 @@ namespace Slang { addModifier(structDecl, m_astBuilder->create()); } + checkVisibility(structDecl); } void SemanticsDeclHeaderVisitor::visitClassDecl(ClassDecl* classDecl) { - SLANG_UNUSED(classDecl); + checkVisibility(classDecl); } void SemanticsDeclBodyVisitor::checkVarDeclCommon(VarDeclBase* varDecl) @@ -1313,6 +1321,7 @@ namespace Slang OverloadResolveContext overloadContext; overloadContext.loc = varDecl->nameAndLoc.loc; overloadContext.mode = OverloadResolveContext::Mode::JustTrying; + overloadContext.sourceScope = m_outerScope; AddTypeOverloadCandidates(type, overloadContext); if(overloadContext.bestCandidates.getCount() != 0) @@ -1368,6 +1377,24 @@ namespace Slang } } + void addVisibilityModifier(ASTBuilder* builder, Decl* decl, DeclVisibility vis) + { + switch (vis) + { + case DeclVisibility::Public: + addModifier(decl, builder->create()); + break; + case DeclVisibility::Internal: + addModifier(decl, builder->create()); + break; + case DeclVisibility::Private: + addModifier(decl, builder->create()); + break; + default: + break; + } + } + bool SemanticsVisitor::trySynthesizeDifferentialAssociatedTypeRequirementWitness( ConformanceCheckingContext* context, DeclRef requirementDeclRef, @@ -1434,6 +1461,10 @@ namespace Slang diffField->checkState = DeclCheckState::SignatureChecked; diffField->parentDecl = aggTypeDecl; aggTypeDecl->members.add(diffField); + + auto visibility = getDeclVisibility(member); + addVisibilityModifier(m_astBuilder, diffField, visibility); + aggTypeDecl->invalidateMemberDictionary(); // Inject a `DerivativeMember` modifier on the differential field to point to itself. @@ -1539,6 +1570,15 @@ namespace Slang addModifier(aggTypeDecl, m_astBuilder->create()); + // The visibility of synthesized decl should be the min of the parent decl and the requirement. + if (auto visModifier = requirementDeclRef.getDecl()->findModifier()) + { + auto requirementVisibility = getDeclVisibility(requirementDeclRef.getDecl()); + auto thisVisibility = getDeclVisibility(context->parentDecl); + auto visibility = Math::Min(thisVisibility, requirementVisibility); + addVisibilityModifier(m_astBuilder, aggTypeDecl, visibility); + } + // Synthesize the rest of IDifferential method conformances by recursively checking // conformance on the synthesized decl. checkAggTypeConformance(aggTypeDecl); @@ -1890,6 +1930,7 @@ namespace Slang DeclCheckState::ModifiersChecked, DeclCheckState::ReadyForReference, DeclCheckState::ReadyForLookup, + DeclCheckState::ReadyForConformances, DeclCheckState::Checked }; for(auto s : states) @@ -2810,6 +2851,7 @@ namespace Slang { synFuncDecl->nameAndLoc.name = getSession()->getNameObj("$__syn_" + synFuncDecl->nameAndLoc.name->text); } + // The result type of our synthesized method will be the expected // result type from the interface requirement. // @@ -2933,6 +2975,14 @@ namespace Slang auto attr = m_astBuilder->create(); addModifier(synFuncDecl, attr); } + // The visibility of synthesized decl should be the min of the parent decl and the requirement. + if (auto visModifier = requiredMemberDeclRef.getDecl()->findModifier()) + { + auto requirementVisibility = getDeclVisibility(requiredMemberDeclRef.getDecl()); + auto thisVisibility = getDeclVisibility(context->parentDecl); + auto visibility = Math::Min(thisVisibility, requirementVisibility); + addVisibilityModifier(m_astBuilder, synFuncDecl, visibility); + } } return synFuncDecl; @@ -3471,6 +3521,15 @@ namespace Slang synPropertyDecl->parentDecl = context->parentDecl; + // The visibility of synthesized decl should be the min of the parent decl and the requirement. + if (auto visModifier = requiredMemberDeclRef.getDecl()->findModifier()) + { + auto requirementVisibility = getDeclVisibility(requiredMemberDeclRef.getDecl()); + auto thisVisibility = getDeclVisibility(context->parentDecl); + auto visibility = Math::Min(thisVisibility, requirementVisibility); + addVisibilityModifier(m_astBuilder, synPropertyDecl, visibility); + } + // Once our synthesized declaration is complete, we need // to install it as the witness that satifies the given // requirement. @@ -3969,7 +4028,7 @@ namespace Slang // requests will be handled further down. For now we include // lookup results that might be usable, but not as-is. // - auto lookupResult = lookUpMember(m_astBuilder, this, name, subType, LookupMask::Default, LookupOptions::IgnoreBaseInterfaces); + auto lookupResult = lookUpMember(m_astBuilder, this, name, subType, nullptr, LookupMask::Default, LookupOptions::IgnoreBaseInterfaces); if(!lookupResult.isValid()) { @@ -4502,6 +4561,8 @@ namespace Slang void SemanticsDeclBasesVisitor::visitInterfaceDecl(InterfaceDecl* decl) { + SLANG_OUTER_SCOPE_CONTEXT_DECL_RAII(this, decl); + checkVisibility(decl); for( auto inheritanceDecl : decl->getMembersOfType() ) { ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); @@ -4567,6 +4628,8 @@ namespace Slang // Furthermore, only the first inheritance clause (in source // order) is allowed to declare a base `struct` type. // + SLANG_OUTER_SCOPE_CONTEXT_DECL_RAII(this, decl); + Index inheritanceClauseCounter = 0; for( auto inheritanceDecl : decl->getMembersOfType() ) { @@ -4636,6 +4699,7 @@ namespace Slang // Furthermore, only the first inheritance clause (in source // order) is allowed to declare a base `class` type. // + SLANG_OUTER_SCOPE_CONTEXT_DECL_RAII(this, decl); Index inheritanceClauseCounter = 0; for (auto inheritanceDecl : decl->getMembersOfType()) { @@ -4768,6 +4832,9 @@ namespace Slang void SemanticsDeclBasesVisitor::visitEnumDecl(EnumDecl* decl) { + SLANG_OUTER_SCOPE_CONTEXT_DECL_RAII(this, decl); + checkVisibility(decl); + // An `enum` type can inherit from interfaces, and also // from a single "tag" type that must: // @@ -4775,7 +4842,6 @@ namespace Slang // * come first in the list of base types // Index inheritanceClauseCounter = 0; - Type* tagType = nullptr; InheritanceDecl* tagTypeInheritanceDecl = nullptr; for(auto inheritanceDecl : decl->getMembersOfType()) @@ -4938,6 +5004,8 @@ namespace Slang void SemanticsDeclBodyVisitor::visitEnumDecl(EnumDecl* decl) { + SLANG_OUTER_SCOPE_CONTEXT_DECL_RAII(this, decl); + auto enumType = DeclRefType::create(m_astBuilder, makeDeclRef(decl)); auto tagType = decl->tagType; @@ -5063,6 +5131,7 @@ namespace Slang void SemanticsDeclHeaderVisitor::visitTypeDefDecl(TypeDefDecl* decl) { decl->type = CheckProperType(decl->type); + checkVisibility(decl); } void SemanticsDeclHeaderVisitor::visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl) @@ -5079,6 +5148,7 @@ namespace Slang auto interfaceDecl = as(decl->parentDecl); if (!interfaceDecl) getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly); + checkVisibility(decl); } void SemanticsDeclBodyVisitor::visitFunctionDeclBase(FunctionDeclBase* decl) @@ -6154,6 +6224,7 @@ namespace Slang } } } + checkVisibility(decl); } void SemanticsDeclHeaderVisitor::visitFuncDecl(FuncDecl* funcDecl) @@ -6464,6 +6535,7 @@ namespace Slang { decl->type = CheckUsableType(decl->type); visitAbstractStorageDeclCommon(decl); + checkVisibility(decl); } Type* SemanticsDeclHeaderVisitor::_getAccessorStorageType(AccessorDecl* decl) @@ -6768,13 +6840,19 @@ namespace Slang importedModulesList.add(moduleDecl); importedModulesSet.add(moduleDecl); - // Create a new sub-scope to wire the module + // Create a new sub-scope to wire the module's scope and its nested FileDecl's scopes // into our lookup chain. - auto subScope = getASTBuilder()->create(); - subScope->containerDecl = moduleDecl; + for (auto moduleScope = moduleDecl->ownedScope; moduleScope; moduleScope = moduleScope->nextSibling) + { + if (moduleScope->containerDecl != moduleDecl && moduleScope->containerDecl->parentDecl != moduleDecl) + continue; - subScope->nextSibling = scope->nextSibling; - scope->nextSibling = subScope; + auto subScope = getASTBuilder()->create(); + subScope->containerDecl = moduleScope->containerDecl; + + subScope->nextSibling = scope->nextSibling; + scope->nextSibling = subScope; + } // Also import any modules from nested `import` declarations // with the `__exported` modifier diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 7edf27b30..dd868f70c 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -225,6 +225,20 @@ namespace Slang return expr; } + Scope* SemanticsVisitor::getScope(SyntaxNode* node) + { + while (auto declBase = as(node)) + { + if (auto container = as(node)) + { + if (container->ownedScope) + return container->ownedScope; + } + node = declBase->parentDecl; + } + return nullptr; + } + static SourceLoc _getMemberOpLoc(Expr* expr) { if (auto m = as(expr)) @@ -794,6 +808,121 @@ namespace Slang } } + DeclVisibility SemanticsVisitor::getDeclVisibility(Decl* decl) + { + if (as(decl) || as(decl) || as(decl)) + { + auto genericDecl = as(decl->parentDecl); + if (!genericDecl) + return DeclVisibility::Default; + if (genericDecl->inner) + return getDeclVisibility(genericDecl->inner); + return DeclVisibility::Default; + } + if (auto genericDecl = as(decl)) + decl = genericDecl->inner; + for (; decl; decl = getParentDecl(decl)) + { + if (as(decl)) + continue; + if (as(decl)) + continue; + break; + } + if (!decl) + return DeclVisibility::Public; + + for (auto modifier : decl->modifiers) + { + if (as(modifier)) + return DeclVisibility::Public; + else if (as(modifier)) + return DeclVisibility::Internal; + else if (as(modifier)) + return DeclVisibility::Private; + } + + if (auto parentModule = getModuleDecl(decl)) + return parentModule->isInLegacyLanguage ? DeclVisibility::Public : DeclVisibility::Internal; + + return DeclVisibility::Default; + } + + DeclVisibility SemanticsVisitor::getTypeVisibility(Type* type) + { + if (auto declRefType = as(type)) + { + auto v = getDeclVisibility(declRefType->getDeclRef().getDecl()); + auto args = findInnerMostGenericArgs(SubstitutionSet(declRefType->getDeclRef())); + for (auto arg : args) + { + if (auto typeArg = as(arg)) + v = Math::Min(v, getTypeVisibility(typeArg)); + } + return v; + } + return DeclVisibility::Public; + } + + bool SemanticsVisitor::isDeclVisibleFromScope(DeclRef declRef, Scope* scope) + { + auto visibility = getDeclVisibility(declRef.getDecl()); + if (visibility == DeclVisibility::Public) + return true; + if (visibility == DeclVisibility::Internal) + { + // Check that the decl is in the same module as the scope. + auto declModule = getModuleDecl(declRef.getDecl()); + if (declModule == getModuleDecl(scope)) + return true; + } + if (visibility == DeclVisibility::Private) + { + // Check that the decl is in the same or parent container decl as scope. + Decl* parentContainer = declRef.getDecl(); + for (;parentContainer; parentContainer = parentContainer->parentDecl) + { + if (as(parentContainer)) + break; + if (as(parentContainer)) + break; + } + + for (auto s = scope; s; s = s->parent) + { + if (s->containerDecl == parentContainer) + return true; + } + return false; + } + return false; + } + + LookupResult SemanticsVisitor::filterLookupResultByVisibility(const LookupResult& lookupResult) + { + if (!m_outerScope) + return lookupResult; + LookupResult filteredResult; + for (auto item : lookupResult) + { + if (isDeclVisibleFromScope(item.declRef, m_outerScope)) + AddToLookupResult(filteredResult, item); + } + return filteredResult; + } + + LookupResult SemanticsVisitor::filterLookupResultByVisibilityAndDiagnose(const LookupResult& lookupResult, SourceLoc loc, bool& outDiagnosed) + { + outDiagnosed = false; + auto result = filterLookupResultByVisibility(lookupResult); + if (lookupResult.isValid() && !result.isValid()) + { + getSink()->diagnose(loc, Diagnostics::declIsNotVisible, lookupResult.item.declRef); + outDiagnosed = true; + } + return result; + } + LookupResult SemanticsVisitor::resolveOverloadedLookup(LookupResult const& inResult) { // If the result isn't actually overloaded, it is fine as-is @@ -987,6 +1116,7 @@ namespace Slang this, getName("Differential"), type, + nullptr, Slang::LookupMask::type, Slang::LookupOptions::None); @@ -1141,7 +1271,7 @@ namespace Slang // a scope in place. If we do, we will re-use it for any sub-expressions. // If not, we need to create one. // - if(getExprLocalScope()) + if (getExprLocalScope()) { return dispatchExpr(term, *this); } @@ -1860,11 +1990,15 @@ namespace Slang this, operatorName, baseType, + m_outerScope, LookupMask::Default, LookupOptions::NoDeref); + bool diagnosed = false; + lookupResult = filterLookupResultByVisibilityAndDiagnose(lookupResult, subscriptExpr->loc, diagnosed); if (!lookupResult.isValid()) { - getSink()->diagnose(subscriptExpr, Diagnostics::subscriptNonArray, baseType); + if (!diagnosed) + getSink()->diagnose(subscriptExpr, Diagnostics::subscriptNonArray, baseType); return CreateErrorExpr(subscriptExpr); } auto subscriptFuncExpr = createLookupResultExpr( @@ -2333,6 +2467,9 @@ namespace Slang auto lookupResult = lookUp( m_astBuilder, this, expr->name, expr->scope); + + bool diagnosed = false; + lookupResult = filterLookupResultByVisibilityAndDiagnose(lookupResult, expr->loc, diagnosed); if (expr->name == getSession()->getCompletionRequestTokenName()) { @@ -2353,7 +2490,8 @@ namespace Slang expr); } - getSink()->diagnose(expr, Diagnostics::undefinedIdentifier2, expr->name); + if (!diagnosed) + getSink()->diagnose(expr, Diagnostics::undefinedIdentifier2, expr->name); return expr; } @@ -3387,161 +3525,183 @@ namespace Slang Expr* SemanticsVisitor::_lookupStaticMember(DeclRefExpr* expr, Expr* baseExpression) { - auto& baseType = baseExpression->type; + LookupResult globalLookupResult; + bool hasErrors = false; + Expr* base = nullptr; + auto handleLeafCase = [&](DeclRef baseDeclRef, Type* type) + { + auto aggTypeDeclRef = as(baseDeclRef); - // TODO: Need to handle overloaded case (in case we - // have multiple visible types and/or namespaces - // with the same name). + if (auto namespaceDeclRef = as(baseDeclRef)) + { + // We are looking up a namespace member. + // + // This ought to be the easy case, because + // there are no restrictions on whether + // we can reference the declaration here. + // + LookupResult nsLookupResult = lookUpDirectAndTransparentMembers( + m_astBuilder, + this, + expr->name, + namespaceDeclRef.getDecl(), + namespaceDeclRef); + AddToLookupResult(globalLookupResult, nsLookupResult); - if (auto namespaceType = as(baseType)) - { - // We are looking up a namespace member. - // - auto namespaceDeclRef = namespaceType->getDeclRef(); + } + else if (aggTypeDeclRef || type) + { + // We are looking up a member inside a type. + // We want to be careful here because we should only find members + // that are implicitly or explicitly `static`. + // + if (type == nullptr) + type = DeclRefType::create(m_astBuilder, aggTypeDeclRef); - // This ought to be the easy case, because - // there are no restrictions on whether - // we can reference the declaration here. - // - LookupResult lookupResult = lookUpDirectAndTransparentMembers( - m_astBuilder, - this, - expr->name, - namespaceDeclRef.getDecl(), - namespaceDeclRef); - if (!lookupResult.isValid()) - { - return lookupMemberResultFailure(expr, baseType); - } + if (as(type)) + { + return; + } - if (expr->name == getSession()->getCompletionRequestTokenName()) - { - suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult); - } - return createLookupResultExpr( - expr->name, - lookupResult, - nullptr, - expr->loc, - expr); - } - else if (auto typeType = as(baseType)) - { - // We are looking up a member inside a type. - // We want to be careful here because we should only find members - // that are implicitly or explicitly `static`. - // - // TODO: this duplicates a *lot* of logic with the case below. - // We need to fix that. - auto type = typeType->getType(); + LookupResult lookupResult = lookUpMember( + m_astBuilder, + this, + expr->name, + type, + m_outerScope); - if (as(type)) - { - return CreateErrorExpr(expr); - } + // We need to confirm that whatever member we + // are trying to refer to is usable via static reference. + // + // TODO: eventually we might allow a non-static + // member to be adapted by turning it into something + // like a closure that takes the missing `this` parameter. + // + // E.g., a static reference to a method could be treated + // as a value with a function type, where the first parameter + // is `type`. + // + // The biggest challenge there is that we'd need to arrange + // to generate "dispatcher" functions that could be used + // to implement that function, in the case where we are + // making a static reference to some kind of polymorphic declaration. + // + // (Also, static references to fields/properties would get even + // harder, because you'd have to know whether a getter/setter/ref-er + // is needed). + // + // For now let's just be expedient and disallow all of that, because + // we can always add it back in later. - LookupResult lookupResult = lookUpMember( - m_astBuilder, - this, - expr->name, - type); - if (!lookupResult.isValid()) - { - return lookupMemberResultFailure(expr, baseType); - } + // If the lookup result is valid, then we want to filter + // it to just those candidates that can be referenced statically, + // and ignore any that would only be allowed as instance members. + // + if (lookupResult.isValid()) + { + // We track both the usable items, and whether or + // not there were any non-static items that need + // to be ignored. + // + bool anyNonStatic = false; + List staticItems; + for (auto item : lookupResult) + { + // Is this item usable as a static member? + if (isUsableAsStaticMember(item)) + { + // If yes, then it will be part of the output. + staticItems.add(item); + } + else + { + // If no, then we might need to output an error. + anyNonStatic = true; + } + } - // We need to confirm that whatever member we - // are trying to refer to is usable via static reference. - // - // TODO: eventually we might allow a non-static - // member to be adapted by turning it into something - // like a closure that takes the missing `this` parameter. - // - // E.g., a static reference to a method could be treated - // as a value with a function type, where the first parameter - // is `type`. - // - // The biggest challenge there is that we'd need to arrange - // to generate "dispatcher" functions that could be used - // to implement that function, in the case where we are - // making a static reference to some kind of polymorphic declaration. - // - // (Also, static references to fields/properties would get even - // harder, because you'd have to know whether a getter/setter/ref-er - // is needed). - // - // For now let's just be expedient and disallow all of that, because - // we can always add it back in later. + // Was there anything non-static in the list? + if (anyNonStatic) + { + // If we had some static items, then that's okay, + // we just want to use our newly-filtered list. + if (staticItems.getCount()) + { + lookupResult.items = staticItems; + lookupResult.item = staticItems[0]; + } + else + { + // Otherwise, it is time to report an error. + getSink()->diagnose( + expr->loc, + Diagnostics::staticRefToNonStaticMember, + type, + expr->name); + hasErrors = true; + return; + } + } + // If there were no non-static items, then the `items` + // array already represents what we'd get by filtering... - // If the lookup result is valid, then we want to filter - // it to just those candidates that can be referenced statically, - // and ignore any that would only be allowed as instance members. - // - if(lookupResult.isValid()) - { - // We track both the usable items, and whether or - // not there were any non-static items that need - // to be ignored. - // - bool anyNonStatic = false; - List staticItems; - for (auto item : lookupResult) - { - // Is this item usable as a static member? - if (isUsableAsStaticMember(item)) - { - // If yes, then it will be part of the output. - staticItems.add(item); - } - else - { - // If no, then we might need to output an error. - anyNonStatic = true; + AddToLookupResult(globalLookupResult, lookupResult); + base = baseExpression; } } + }; - // Was there anything non-static in the list? - if (anyNonStatic) - { - // If we had some static items, then that's okay, - // we just want to use our newly-filtered list. - if (staticItems.getCount()) - { - lookupResult.items = staticItems; - lookupResult.item = staticItems[0]; - } - else - { - // Otherwise, it is time to report an error. - getSink()->diagnose( - expr->loc, - Diagnostics::staticRefToNonStaticMember, - type, - expr->name); - return CreateErrorExpr(expr); - } - } - // If there were no non-static items, then the `items` - // array already represents what we'd get by filtering... - } - if (expr->name == getSession()->getCompletionRequestTokenName()) + auto handleLeafExpr = [&](Expr* e) { - suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult); + if (auto nsType = as(e->type)) + handleLeafCase(nsType->getDeclRef(), nsType); + else if (auto aggType = as(e->type)) + handleLeafCase(aggType->getDeclRef(), aggType); + else if (auto typetype = as(e->type)) + handleLeafCase(DeclRef(), typetype->getType()); + }; + + auto& baseType = baseExpression->type; + if (as(baseType)) + { + return CreateErrorExpr(expr); + } + + if (auto overloaded = as(baseExpression)) + { + for (auto candidate : overloaded->lookupResult2.items) + handleLeafCase(candidate.declRef, nullptr); + } + else if (auto overloaded2 = as(baseExpression)) + { + for (auto candidate : overloaded2->candidiateExprs) + { + handleLeafExpr(candidate); } - return createLookupResultExpr( - expr->name, - lookupResult, - baseExpression, - expr->loc, - expr); } - else if (as(baseType)) + else { - return CreateErrorExpr(expr); + handleLeafExpr(baseExpression); + } + + bool diagnosed = false; + globalLookupResult = filterLookupResultByVisibilityAndDiagnose(globalLookupResult, expr->loc, diagnosed); + diagnosed |= hasErrors; + if (!globalLookupResult.isValid()) + { + return lookupMemberResultFailure(expr, baseType, diagnosed); } - // Failure - return lookupMemberResultFailure(expr, baseType); + if (expr->name == getSession()->getCompletionRequestTokenName()) + { + suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, globalLookupResult); + } + return createLookupResultExpr( + expr->name, + globalLookupResult, + base, + expr->loc, + expr); } Expr* SemanticsExprVisitor::visitStaticMemberExpr(StaticMemberExpr* expr) @@ -3565,12 +3725,14 @@ namespace Slang Expr* SemanticsVisitor::lookupMemberResultFailure( DeclRefExpr* expr, - QualType const& baseType) + QualType const& baseType, + bool supressDiagnostic) { // Check it's a member expression SLANG_ASSERT(as(expr) || as(expr)); - getSink()->diagnose(expr, Diagnostics::noMemberOfNameInType, expr->name, baseType); + if (!supressDiagnostic) + getSink()->diagnose(expr, Diagnostics::noMemberOfNameInType, expr->name, baseType); expr->type = QualType(m_astBuilder->getErrorType()); return expr; } @@ -3678,6 +3840,14 @@ namespace Slang { return _lookupStaticMember(expr, expr->baseExpression); } + else if (as(expr->baseExpression)) + { + return _lookupStaticMember(expr, expr->baseExpression); + } + else if (as(expr->baseExpression)) + { + return _lookupStaticMember(expr, expr->baseExpression); + } else if (as(baseType)) { return CreateErrorExpr(expr); @@ -3688,10 +3858,13 @@ namespace Slang m_astBuilder, this, expr->name, - baseType.Ptr()); + baseType.Ptr(), + m_outerScope); + bool diagnosed = false; + lookupResult = filterLookupResultByVisibilityAndDiagnose(lookupResult, expr->loc, diagnosed); if (!lookupResult.isValid()) { - return lookupMemberResultFailure(expr, baseType); + return lookupMemberResultFailure(expr, baseType, diagnosed); } if (expr->name == getSession()->getCompletionRequestTokenName()) { diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index e9654830a..43844cf70 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -217,6 +217,7 @@ namespace Slang FixityChecked, TypeChecked, DirectionChecked, + VisibilityChecked, Applicable, }; Status status = Status::Unchecked; @@ -773,6 +774,8 @@ namespace Slang struct SemanticsContext { public: + friend struct OuterScopeContextRAII; + explicit SemanticsContext( SharedSemanticsContext* shared) : m_shared(shared) @@ -804,6 +807,8 @@ namespace Slang result.m_parentFunc = parentFunc; result.m_outerStmts = nullptr; result.m_parentDifferentiableAttr = parentFunc->findModifier(); + if (parentFunc->ownedScope) + result.m_outerScope = parentFunc->ownedScope; return result; } @@ -868,6 +873,7 @@ namespace Slang }; ExprLocalScope* getExprLocalScope() { return m_exprLocalScope; } + Scope* getOuterScope() { return m_outerScope; } SemanticsContext withExprLocalScope(ExprLocalScope* exprLocalScope) { @@ -876,6 +882,13 @@ namespace Slang return result; } + SemanticsContext withOuterScope(Scope* scope) + { + SemanticsContext result(*this); + result.m_outerScope = scope; + return result; + } + SemanticsContext withTreatAsDifferentiable(TreatAsDifferentiableExpr* expr) { SemanticsContext result(*this); @@ -921,8 +934,31 @@ namespace Slang TreatAsDifferentiableExpr* m_treatAsDifferentiableExpr = nullptr; ASTBuilder* m_astBuilder = nullptr; + + Scope* m_outerScope = nullptr; + }; + + struct OuterScopeContextRAII + { + SemanticsContext* m_context; + Scope* m_oldOuterScope; + + OuterScopeContextRAII(SemanticsContext* context, Scope* outerScope) + : m_context(context) + , m_oldOuterScope(context->getOuterScope()) + { + context->m_outerScope = outerScope; + } + + ~OuterScopeContextRAII() + { + m_context->m_outerScope = m_oldOuterScope; + } }; +#define SLANG_OUTER_SCOPE_CONTEXT_RAII(context, scope) OuterScopeContextRAII _outerScopeContextRAII(context, scope) +#define SLANG_OUTER_SCOPE_CONTEXT_DECL_RAII(context, decl) OuterScopeContextRAII _outerScopeContextRAII(context, decl->ownedScope?decl->ownedScope:context->getOuterScope()) + struct SemanticsVisitor : public SemanticsContext { typedef SemanticsContext Super; @@ -1010,6 +1046,8 @@ namespace Slang /// If `expr` has Ref Type, convert it into an l-value expr that has T type. Expr* maybeOpenRef(Expr* expr); + Scope* getScope(SyntaxNode* node); + void diagnoseDeprecatedDeclRefUsage(DeclRef declRef, SourceLoc loc, Expr* originalExpr); DeclRef getDefaultDeclRef(Decl* decl) @@ -1056,6 +1094,11 @@ namespace Slang SourceLoc loc, Expr* originalExpr); + DeclVisibility getDeclVisibility(Decl* decl); + DeclVisibility getTypeVisibility(Type* type); + bool isDeclVisibleFromScope(DeclRef declRef, Scope* scope); + LookupResult filterLookupResultByVisibility(const LookupResult& lookupResult); + LookupResult filterLookupResultByVisibilityAndDiagnose(const LookupResult& lookupResult, SourceLoc loc, bool& outDiagnosed); Val* resolveVal(Val* val) { @@ -1444,6 +1487,7 @@ namespace Slang ModifiableSyntaxNode* syntaxNode); void checkModifiers(ModifiableSyntaxNode* syntaxNode); + void checkVisibility(Decl* decl); bool doesSignatureMatchRequirement( DeclRef satisfyingMemberDeclRef, @@ -1985,6 +2029,9 @@ namespace Slang // Source location of the "function" part of the expression, if any SourceLoc funcLoc; + // The source scope of the lookup for performing visibiliity tests. + Scope* sourceScope = nullptr; + // The original arguments to the call Index argCount = 0; Expr** args = nullptr; @@ -2048,6 +2095,10 @@ namespace Slang OverloadResolveContext& context, OverloadCandidate const& candidate); + bool TryCheckOverloadCandidateVisibility( + OverloadResolveContext& context, + OverloadCandidate const& candidate); + bool TryCheckGenericOverloadCandidateTypes( OverloadResolveContext& context, OverloadCandidate& candidate); @@ -2371,7 +2422,8 @@ namespace Slang Expr* lookupMemberResultFailure( DeclRefExpr* expr, - QualType const& baseType); + QualType const& baseType, + bool supressDiagnostic = false); SharedSemanticsContext& operator=(const SharedSemanticsContext &) = delete; diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp index 569804ff4..339ecba4c 100644 --- a/source/slang/slang-check-modifier.cpp +++ b/source/slang/slang-check-modifier.cpp @@ -950,7 +950,7 @@ namespace Slang { if (auto parentExtension = as(varDecl->parentDecl)) { - auto originalMemberLookup = lookUpMember(m_astBuilder, this, varDecl->getName(), parentExtension->targetType); + auto originalMemberLookup = lookUpMember(m_astBuilder, this, varDecl->getName(), parentExtension->targetType, parentExtension->ownedScope); LookupResult filteredResult; for (auto item : originalMemberLookup.items) { @@ -1042,6 +1042,23 @@ namespace Slang } } + if (as(m)) + { + if (as(syntaxNode) || as(syntaxNode)) + { + getSink()->diagnose(m, Diagnostics::invalidUseOfPrivateVisibility, as(syntaxNode)); + } + else if (auto decl = as(syntaxNode)) + { + // Interface requirements can't be private. + if (isInterfaceRequirement(decl)) + { + getSink()->diagnose(m, Diagnostics::invalidUseOfPrivateVisibility, as(syntaxNode)); + } + } + } + + // Default behavior is to leave things as they are, // and assume that modifiers are mostly already checked. // @@ -1054,6 +1071,71 @@ namespace Slang return m; } + void SemanticsVisitor::checkVisibility(Decl* decl) + { + if (as(decl)) + { + return; + } + ShortList typesToVerify; + if (auto varDecl = as(decl)) + { + typesToVerify.add(varDecl->type); + } + else if (auto callable = as(decl)) + { + typesToVerify.add(callable->returnType); + typesToVerify.add(callable->errorType); + for (auto param : callable->getParameters()) + { + typesToVerify.add(param->type); + } + } + else if (auto propertyDecl = as(decl)) + { + typesToVerify.add(propertyDecl->type); + } + else if (as(decl)) + { + } + else if (auto typeDecl = as(decl)) + { + typesToVerify.add(typeDecl->type); + } + else + { + return; + } + auto thisVisibility = getDeclVisibility(decl); + + // First, we check that the decl's type does not have lower visibility. + for (auto type : typesToVerify) + { + if (!type) + continue; + DeclVisibility typeVisibility = getTypeVisibility(type); + if (typeVisibility < thisVisibility) + { + getSink()->diagnose(decl, Diagnostics::useOfLessVisibleType, decl, type); + break; + } + } + + // Next, we check that the decl does not have higher visiblity than its parent. + Decl* parentDecl = decl; + for (; parentDecl; parentDecl = parentDecl->parentDecl) + { + if (as(parentDecl)) + break; + } + if (!parentDecl) + return; + auto parentVisibility = getDeclVisibility(parentDecl); + if (thisVisibility > parentVisibility) + { + getSink()->diagnose(decl, Diagnostics::declCannotHaveHigherVisibility, decl, parentDecl); + } + } void SemanticsVisitor::checkModifiers(ModifiableSyntaxNode* syntaxNode) { diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index d7d29a4e1..2912a79b0 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -190,6 +190,33 @@ namespace Slang } } + bool SemanticsVisitor::TryCheckOverloadCandidateVisibility(OverloadResolveContext& context, OverloadCandidate const& candidate) + { + // Always succeeds when we are trying out constructors. + if (context.mode == OverloadResolveContext::Mode::JustTrying) + { + if (as(candidate.item.declRef)) + return true; + } + + if (!context.sourceScope) + return true; + + if (!candidate.item.declRef) + return true; + + if (!isDeclVisibleFromScope(candidate.item.declRef, context.sourceScope)) + { + if (context.mode == OverloadResolveContext::Mode::ForReal) + { + getSink()->diagnose(context.loc, Diagnostics::declIsNotVisible, candidate.item.declRef); + } + return false; + } + + return true; + } + bool SemanticsVisitor::TryCheckGenericOverloadCandidateTypes( OverloadResolveContext& context, OverloadCandidate& candidate) @@ -704,6 +731,10 @@ namespace Slang if (!TryCheckOverloadCandidateConstraints(context, candidate)) return; + candidate.status = OverloadCandidate::Status::VisibilityChecked; + if (!TryCheckOverloadCandidateVisibility(context, candidate)) + return; + candidate.status = OverloadCandidate::Status::Applicable; } @@ -777,6 +808,9 @@ namespace Slang if (!TryCheckOverloadCandidateConstraints(context, candidate)) goto error; + if (!TryCheckOverloadCandidateVisibility(context, candidate)) + goto error; + { Expr* baseExpr; switch(candidate.flavor) @@ -887,7 +921,6 @@ namespace Slang } else { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "no original expression for overload result"); return nullptr; } } @@ -1511,6 +1544,7 @@ namespace Slang this, getName("$init"), type, + context.sourceScope, LookupMask::Default, LookupOptions::NoDeref); @@ -1885,7 +1919,7 @@ namespace Slang context.argCount = expr->arguments.getCount(); context.args = expr->arguments.getBuffer(); context.loc = expr->loc; - + context.sourceScope = m_outerScope; context.baseExpr = GetBaseExpr(funcExpr); // TODO: We should have a special case here where an `InvokeExpr` @@ -1975,26 +2009,16 @@ namespace Slang Index candidateCount = context.bestCandidates.getCount(); Index maxCandidatesToPrint = 10; // don't show too many candidates at once... Index candidateIndex = 0; + context.bestCandidates.sort([](const OverloadCandidate& c1, const OverloadCandidate& c2) { return c1.status < c2.status; }); + for (auto candidate : context.bestCandidates) { String declString = ASTPrinter::getDeclSignatureString(candidate.item, m_astBuilder); -// declString = declString + "[" + String(candidate.conversionCostSum) + "]"; - -#if 0 - // Debugging: ensure that we don't consider multiple declarations of the same operation - if (auto decl = as(candidate.item.declRef.decl)) - { - char buffer[1024]; - sprintf_s(buffer, sizeof(buffer), "[this:%p, primary:%p, next:%p]", - decl, - decl->primaryDecl, - decl->nextDecl); - declString.append(buffer); - } -#endif - - getSink()->diagnose(candidate.item.declRef, Diagnostics::overloadCandidate, declString); + if (candidate.status == OverloadCandidate::Status::VisibilityChecked) + getSink()->diagnose(candidate.item.declRef, Diagnostics::invisibleOverloadCandidate, declString); + else + getSink()->diagnose(candidate.item.declRef, Diagnostics::overloadCandidate, declString); candidateIndex++; if (candidateIndex == maxCandidatesToPrint) @@ -2126,7 +2150,7 @@ namespace Slang context.argCount = args.getCount(); context.args = args.getBuffer(); context.loc = genericAppExpr->loc; - + context.sourceScope = m_outerScope; context.baseExpr = GetBaseExpr(baseExpr); AddGenericOverloadCandidates(baseExpr, context); diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 8eb0584d0..c95d5ea98 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -357,6 +357,13 @@ DIAGNOSTIC(30503, Error, primaryModuleFileCannotStartWithImplementingDecl, "a pr DIAGNOSTIC(30504, Warning, primaryModuleFileMustStartWithModuleDecl, "a primary source file for a module should start with 'module'.") DIAGNOSTIC(30505, Error, implementingMustReferencePrimaryModuleFile, "the source file referenced by 'implementing' must be a primary module file starting with a 'module' declaration.") +// Visibilty +DIAGNOSTIC(30600, Error, declIsNotVisible, "'$0' is not accessible from the current context.") +DIAGNOSTIC(30601, Error, declCannotHaveHigherVisibility, "'$0' cannot have a higher visibility than '$1'.") +DIAGNOSTIC(30602, Error, declCannotHaveLowerVisibility, "'$0' cannot have a lower visibility than '$1'.") +DIAGNOSTIC(30603, Error, invalidUseOfPrivateVisibility, "'$0' cannot have private visibility.") +DIAGNOSTIC(30604, Error, useOfLessVisibleType, "'$0' references less visible type '$1'.") + // Attributes DIAGNOSTIC(31000, Error, unknownAttributeName, "unknown attribute '$0'") DIAGNOSTIC(31001, Error, attributeArgumentCountMismatch, "attribute '$0' expects $1 arguments ($2 provided)") @@ -502,6 +509,8 @@ DIAGNOSTIC(39999, Error, ambiguousOverloadForNameWithArgs, "ambiguous call to '$ DIAGNOSTIC(39999, Error, ambiguousOverloadWithArgs, "ambiguous call to overloaded operation with arguments of type $0") DIAGNOSTIC(39999, Note, overloadCandidate, "candidate: $0") +DIAGNOSTIC(39999, Note, invisibleOverloadCandidate, "candidate (invisible): $0") + DIAGNOSTIC(39999, Note, moreOverloadCandidates, "$0 more overload candidates") DIAGNOSTIC(39999, Error, caseOutsideSwitch, "'case' not allowed outside of a 'switch' statement") diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index d0efce0f5..7f7e23a1c 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -4122,8 +4122,8 @@ void CLikeSourceEmitter::computeEmitActions(IRModule* module, List& { if( as(inst) ) { - // Don't emit a type unless it is actually used or is marked public. - if (!inst->findDecoration()) + // Don't emit a type unless it is actually used or is marked exported. + if (!inst->findDecoration()) continue; } diff --git a/source/slang/slang-ir-autodiff.cpp b/source/slang/slang-ir-autodiff.cpp index e4f3f3f94..9166c560a 100644 --- a/source/slang/slang-ir-autodiff.cpp +++ b/source/slang/slang-ir-autodiff.cpp @@ -2066,20 +2066,15 @@ void releaseNullDifferentialType(AutoDiffSharedContext* context) { if (auto nullStruct = context->nullDifferentialStructType) { - if (auto publicDecoration = nullStruct->findDecoration()) - publicDecoration->removeAndDeallocate(); if (auto keepAliveDecoration = nullStruct->findDecoration()) keepAliveDecoration->removeAndDeallocate(); } if (auto nullWitness = context->nullDifferentialWitness) { - if (auto publicDecoration = nullWitness->findDecoration()) - publicDecoration->removeAndDeallocate(); if (auto keepAliveDecoration = nullWitness->findDecoration()) keepAliveDecoration->removeAndDeallocate(); } - } bool finalizeAutoDiffPass(IRModule* module) diff --git a/source/slang/slang-ir-dll-export.cpp b/source/slang/slang-ir-dll-export.cpp index d7a18e665..af5f70eb3 100644 --- a/source/slang/slang-ir-dll-export.cpp +++ b/source/slang/slang-ir-dll-export.cpp @@ -4,6 +4,7 @@ #include "slang-ir.h" #include "slang-ir-insts.h" #include "slang-ir-marshal-native-call.h" +#include "slang-ir-util.h" namespace Slang { @@ -26,10 +27,7 @@ struct DllExportContext builder.addPublicDecoration(wrapper); builder.addKeepAliveDecoration(wrapper); builder.addHLSLExportDecoration(wrapper); - if (auto oldPublicDecoration = func->findDecoration()) - { - oldPublicDecoration->removeFromParent(); - } + removeLinkageDecorations(func); } void processModule() diff --git a/source/slang/slang-ir-generics-lowering-context.cpp b/source/slang/slang-ir-generics-lowering-context.cpp index 325568040..212e16483 100644 --- a/source/slang/slang-ir-generics-lowering-context.cpp +++ b/source/slang/slang-ir-generics-lowering-context.cpp @@ -60,7 +60,7 @@ namespace Slang return result; IRBuilder builderStorage(module); auto builder = &builderStorage; - builder->setInsertBefore(typeInst->next); + builder->setInsertAfter(typeInst); result = builder->emitMakeRTTIObject(typeInst); @@ -75,10 +75,11 @@ namespace Slang String rttiObjName = exportDecoration->getMangledName(); builder->addExportDecoration(result, rttiObjName.getUnownedSlice()); } - // Make sure the RTTI object for a public struct type has public visiblity. - if (typeInst->findDecoration()) + + // Make sure the RTTI object for an exported struct type is marked as export if the type is. + if (typeInst->findDecoration()) { - builder->addPublicDecoration(result); + builder->addHLSLExportDecoration(result); builder->addKeepAliveDecoration(result); } mapTypeToRTTIObject[typeInst] = result; diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp index 87b4f3fde..36769cc34 100644 --- a/source/slang/slang-ir-link.cpp +++ b/source/slang/slang-ir-link.cpp @@ -1412,13 +1412,12 @@ struct IRSpecializationState } }; -static bool _isPublicOrHLSLExported(IRInst* inst) +static bool _isHLSLExported(IRInst* inst) { for (auto decoration : inst->getDecorations()) { const auto op = decoration->getOp(); - if (op == kIROp_PublicDecoration || - op == kIROp_HLSLExportDecoration) + if (op == kIROp_HLSLExportDecoration) { return true; } @@ -1582,8 +1581,8 @@ LinkedIR linkIR( { for (auto inst : irModule->getGlobalInsts()) { - // Is it `public` or (HLSL) `export` clone - if (_isPublicOrHLSLExported(inst)) + // Is it (HLSL) `export` clone + if (_isHLSLExported(inst)) { auto cloned = cloneValue(context, inst); if (!cloned->findDecorationImpl(kIROp_KeepAliveDecoration)) diff --git a/source/slang/slang-ir-lower-generic-type.cpp b/source/slang/slang-ir-lower-generic-type.cpp index ae085145c..28eed3582 100644 --- a/source/slang/slang-ir-lower-generic-type.cpp +++ b/source/slang/slang-ir-lower-generic-type.cpp @@ -16,10 +16,10 @@ namespace Slang IRInst* processInst(IRInst* inst) { - // Ensure public struct types has RTTI object defined. + // Ensure exported struct types has RTTI object defined. if (as(inst)) { - if (inst->findDecoration()) + if (inst->findDecoration()) { sharedContext->maybeEmitRTTIObject(inst); } diff --git a/source/slang/slang-ir-pytorch-cpp-binding.cpp b/source/slang/slang-ir-pytorch-cpp-binding.cpp index 432cd93f3..6a85f0324 100644 --- a/source/slang/slang-ir-pytorch-cpp-binding.cpp +++ b/source/slang/slang-ir-pytorch-cpp-binding.cpp @@ -575,7 +575,7 @@ void generateReflectionFunc(IRBuilder* builder, IRFunc* kernelFunc, IRFunc* host builder->addExternCppDecoration(reflectionFunc, reflFuncExportName.getUnownedSlice()); builder->addTorchEntryPointDecoration(reflectionFunc, reflFuncExportName.getUnownedSlice()); - builder->addPublicDecoration(reflectionFunc); + builder->addHLSLExportDecoration(reflectionFunc); builder->addKeepAliveDecoration(reflectionFunc); } @@ -760,7 +760,7 @@ void generateReflectionForType(IRType* type, DiagnosticSink* sink) builder.addTorchEntryPointDecoration(reflFunc, reflFuncExportName.getUnownedSlice()); builder.addExternCppDecoration(reflFunc, reflFuncExportName.getUnownedSlice()); - builder.addPublicDecoration(reflFunc); + builder.addHLSLExportDecoration(reflFunc); builder.addKeepAliveDecoration(reflFunc); } @@ -842,7 +842,7 @@ IRFunc* generateCUDAWrapperForFunc(IRFunc* func, DiagnosticSink* sink) // Mark for host-side emit logic. builder.addCudaHostDecoration(hostFunc); // Keep alive. This method will be accessed externally. - builder.addPublicDecoration(hostFunc); + builder.addHLSLExportDecoration(hostFunc); builder.addKeepAliveDecoration(hostFunc); } @@ -1047,7 +1047,7 @@ void generateDerivativeWrappers(IRModule* module, DiagnosticSink* sink) builder.addExternCppDecoration(wrapperFunc, nameBuilder.getUnownedSlice()); } - builder.addPublicDecoration(wrapperFunc); + builder.addHLSLExportDecoration(wrapperFunc); builder.addKeepAliveDecoration(wrapperFunc); builder.addCudaKernelForwardDerivativeDecoration(func, wrapperFunc); @@ -1106,7 +1106,7 @@ void generateDerivativeWrappers(IRModule* module, DiagnosticSink* sink) builder.addExternCppDecoration(wrapperFunc, nameBuilder.getUnownedSlice()); } - builder.addPublicDecoration(wrapperFunc); + builder.addHLSLExportDecoration(wrapperFunc); builder.addKeepAliveDecoration(wrapperFunc); builder.addCudaKernelBackwardDerivativeDecoration(func, wrapperFunc); diff --git a/source/slang/slang-language-server-completion.cpp b/source/slang/slang-language-server-completion.cpp index 7b01dac34..6038a432a 100644 --- a/source/slang/slang-language-server-completion.cpp +++ b/source/slang/slang-language-server-completion.cpp @@ -298,7 +298,7 @@ SlangResult CompletionContext::tryCompleteImport() Index pos = -1; for (auto prefix : prefixes) { - static auto importStr = UnownedStringSlice(prefix); + auto importStr = UnownedStringSlice(prefix); lineContent = doc->getLine(line); pos = lineContent.indexOf(importStr); if (pos == -1) diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index ee4518c20..04a855c3c 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -98,6 +98,19 @@ void AddToLookupResult( } } +void AddToLookupResult(LookupResult& result, const LookupResult& items) +{ + if (items.isOverloaded()) + { + for (auto item : items.items) + AddToLookupResult(result, item); + } + else if (items.isValid()) + { + AddToLookupResult(result, items.item); + } +} + LookupResult refineLookup(LookupResult const& inResult, LookupMask mask) { if (!inResult.isValid()) return inResult; @@ -894,11 +907,12 @@ LookupResult lookUpMember( SemanticsVisitor* semantics, Name* name, Type* type, + Scope* sourceScope, LookupMask mask, LookupOptions options) { LookupResult result; - LookupRequest request = initLookupRequest(semantics, name, mask, options, nullptr); + LookupRequest request = initLookupRequest(semantics, name, mask, options, sourceScope); _lookUpMembersInType(astBuilder, name, type, request, result, nullptr); return result; } diff --git a/source/slang/slang-lookup.h b/source/slang/slang-lookup.h index 8af760f70..84b453bf2 100644 --- a/source/slang/slang-lookup.h +++ b/source/slang/slang-lookup.h @@ -27,6 +27,7 @@ LookupResult lookUpMember( SemanticsVisitor* semantics, Name* name, Type* type, + Scope* sourceScope, LookupMask mask = LookupMask::Default, LookupOptions options = LookupOptions::None); @@ -58,7 +59,9 @@ QualType getTypeForDeclRef( void AddToLookupResult( LookupResult& result, LookupResultItem item); - +void AddToLookupResult( + LookupResult& result, + const LookupResult& items); } #endif diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 609a5d1e5..8f94b7e90 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -1327,7 +1327,6 @@ static void addLinkageDecoration( if (as(modifier)) { builder->addPublicDecoration(inst); - builder->addKeepAliveDecoration(inst); } else if (as(modifier)) { @@ -1349,43 +1348,50 @@ static void addLinkageDecoration( else if (as(modifier)) { builder->addDllExportDecoration(inst, decl->getName()->text.getUnownedSlice()); - builder->addPublicDecoration(inst); + builder->addHLSLExportDecoration(inst); + builder->addKeepAliveDecoration(inst); } else if (as(modifier)) { builder->addCudaDeviceExportDecoration(inst, decl->getName()->text.getUnownedSlice()); - builder->addPublicDecoration(inst); + builder->addHLSLExportDecoration(inst); builder->addExternCppDecoration(inst, decl->getName()->text.getUnownedSlice()); + builder->addKeepAliveDecoration(inst); } else if (as(modifier)) { builder->addCudaHostDecoration(inst); builder->addExternCppDecoration(inst, decl->getName()->text.getUnownedSlice()); + builder->addKeepAliveDecoration(inst); } else if (as(modifier)) { builder->addCudaKernelDecoration(inst); builder->addExternCppDecoration(inst, decl->getName()->text.getUnownedSlice()); - builder->addPublicDecoration(inst); + builder->addHLSLExportDecoration(inst); builder->addKeepAliveDecoration(inst); } else if (as(modifier)) { builder->addTorchEntryPointDecoration(inst, decl->getName()->text.getUnownedSlice()); builder->addCudaHostDecoration(inst); - builder->addPublicDecoration(inst); + builder->addHLSLExportDecoration(inst); + builder->addKeepAliveDecoration(inst); builder->addExternCppDecoration(inst, decl->getName()->text.getUnownedSlice()); } else if (as(modifier)) { builder->addAutoPyBindCudaDecoration(inst, decl->getName()->text.getUnownedSlice()); builder->addAutoPyBindExportInfoDecoration(inst); + builder->addKeepAliveDecoration(inst); + builder->addHLSLExportDecoration(inst); } else if (auto pyExportModifier = as(modifier)) { builder->addPyExportDecoration(inst, pyExportModifier->name.getLength() ? pyExportModifier->name.getUnownedSlice() : decl->getName()->text.getUnownedSlice()); + builder->addHLSLExportDecoration(inst); } else if (auto knownBuiltinModifier = as(modifier)) { @@ -7012,15 +7018,11 @@ struct DeclLoweringVisitor : DeclVisitor return LoweredValInfo::simple(inst); } - bool isPublicType(Type* type) + bool isExportedType(Type* type) { - // TODO(JS): - // Not clear how should handle HLSLExportModifier here. - // In the HLSL spec 'export' is only applicable to functions. So for now we ignore. - if (auto declRefType = as(type)) { - if (declRefType->getDeclRef().getDecl()->findModifier()) + if (declRefType->getDeclRef().getDecl()->findModifier()) return true; } return false; @@ -7077,9 +7079,9 @@ struct DeclLoweringVisitor : DeclVisitor astReqWitnessTable->witnessedType, astReqWitnessTable->baseType); subBuilder->addExportDecoration(irSatisfyingWitnessTable, mangledName.getUnownedSlice()); - if (isPublicType(astReqWitnessTable->witnessedType)) + if (isExportedType(astReqWitnessTable->witnessedType)) { - subBuilder->addPublicDecoration(irSatisfyingWitnessTable); + subBuilder->addHLSLExportDecoration(irSatisfyingWitnessTable); subBuilder->addKeepAliveDecoration(irSatisfyingWitnessTable); } @@ -7219,17 +7221,12 @@ struct DeclLoweringVisitor : DeclVisitor subBuilder->addPublicDecoration(irWitnessTable); } - // TODO(JS): - // Not clear what to do here around HLSLExportModifier. - // In HLSL it only (currently) applies to functions, so perhaps do nothing is reasonable. - - if (parentDecl->findModifier()) + if (parentDecl->findModifier()) { - subBuilder->addPublicDecoration(irWitnessTable); + subBuilder->addHLSLExportDecoration(irWitnessTable); subBuilder->addKeepAliveDecoration(irWitnessTable); } - // Make sure that all the entries in the witness table have been filled in, // including any cases where there are sub-witness-tables for conformances Dictionary mapASTToIRWitnessTable; @@ -10607,7 +10604,9 @@ struct TypeConformanceIRGenContext context->irBuilder = builder; auto witness = lowerSimpleVal(context, typeConformance->getSubtypeWitness()); - builder->addPublicDecoration(witness); + builder->addKeepAliveDecoration(witness); + builder->addHLSLExportDecoration(witness); + if (conformanceIdOverride != -1) { builder->addSequentialIDDecoration(witness, conformanceIdOverride); diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index 350bc9443..7367007e6 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -105,6 +105,7 @@ namespace Slang Scope* outerScope = nullptr; Scope* currentScope = nullptr; + ModuleDecl* currentModule = nullptr; bool hasSeenCompletionToken = false; @@ -153,12 +154,7 @@ namespace Slang ModuleDecl* getCurrentModuleDecl() { - for (auto scope = currentScope; scope; scope = scope->parent) - { - if (auto moduleDecl = as(scope->containerDecl)) - return moduleDecl; - } - return nullptr; + return currentModule; } Parser( @@ -1157,7 +1153,11 @@ namespace Slang { parsedModifier->loc = nameToken.loc; } - + if (as(parsedModifier)) + { + if (auto currentModule = parser->getCurrentModuleDecl()) + currentModule->isInLegacyLanguage = false; + } AddModifier(&modifierLink, parsedModifier); continue; } @@ -1262,6 +1262,8 @@ namespace Slang { auto decl = parser->astBuilder->create(); parseFileReferenceDeclBase(parser, decl); + if (auto currentModule = parser->getCurrentModuleDecl()) + currentModule->isInLegacyLanguage = false; return decl; } @@ -1296,6 +1298,8 @@ namespace Slang decl->nameAndLoc.loc = parser->tokenReader.peekLoc(); } parser->ReadToken(TokenType::Semicolon); + if (auto currentModule = parser->getCurrentModuleDecl()) + currentModule->isInLegacyLanguage = false; return decl; } @@ -3063,6 +3067,9 @@ namespace Slang // The first is a type declaration that holds all the members, while // the second is a variable declaration that uses the buffer type. StructDecl* bufferDataTypeDecl = parser->astBuilder->create(); + + addModifier(bufferDataTypeDecl, parser->astBuilder->create()); + VarDecl* bufferVarDecl = parser->astBuilder->create(); // Both declarations will have a location that points to the name @@ -3646,6 +3653,7 @@ namespace Slang decl->nameAndLoc = declaratorInfo.nameAndLoc; decl->type = TypeExp(declaratorInfo.typeSpec); + decl->loc = decl->nameAndLoc.loc; } parseStorageDeclBody(parser, decl); @@ -4238,6 +4246,8 @@ namespace Slang currentScope = outerScope; } + currentModule = getModuleDecl(program); + PushScope(program); // A single `ModuleDecl` might span multiple source files, so it @@ -6997,7 +7007,7 @@ namespace Slang syntaxDecl->syntaxClass = syntaxClass; syntaxDecl->parseCallback = callback; syntaxDecl->parseUserData = userData; - + addModifier(syntaxDecl, globalASTBuilder->create()); AddMember(scope, syntaxDecl); } @@ -7511,6 +7521,9 @@ namespace Slang _makeParseModifier("inline", InlineModifier::kReflectClassInfo), _makeParseModifier("public", PublicModifier::kReflectClassInfo), + _makeParseModifier("private", PrivateModifier::kReflectClassInfo), + _makeParseModifier("internal", InternalModifier::kReflectClassInfo), + _makeParseModifier("require", RequireModifier::kReflectClassInfo), _makeParseModifier("param", ParamModifier::kReflectClassInfo), _makeParseModifier("extern", ExternModifier::kReflectClassInfo), diff --git a/source/slang/slang-syntax.cpp b/source/slang/slang-syntax.cpp index 53a02ed87..678d9ec26 100644 --- a/source/slang/slang-syntax.cpp +++ b/source/slang/slang-syntax.cpp @@ -765,6 +765,17 @@ Module* getModule(Decl* decl) return moduleDecl->module; } +ModuleDecl* getModuleDecl(Scope* scope) +{ + for (; scope; scope = scope->parent) + { + if (scope->containerDecl) + return getModuleDecl(scope->containerDecl); + } + return nullptr; + +} + Decl* getParentDecl(Decl* decl) { decl = decl->parentDecl; diff --git a/source/slang/slang-syntax.h b/source/slang/slang-syntax.h index b2b5deb22..fff567049 100644 --- a/source/slang/slang-syntax.h +++ b/source/slang/slang-syntax.h @@ -330,6 +330,7 @@ namespace Slang /// Get the module dclaration that a declaration is associated with, if any. ModuleDecl* getModuleDecl(Decl* decl); + ModuleDecl* getModuleDecl(Scope* scope); /// Get the module that a declaration is associated with, if any. Module* getModule(Decl* decl); diff --git a/tests/autodiff/bsdf/bsdf-auto-rev.slang b/tests/autodiff/bsdf/bsdf-auto-rev.slang index cd31f2097..1bb1989bf 100644 --- a/tests/autodiff/bsdf/bsdf-auto-rev.slang +++ b/tests/autodiff/bsdf/bsdf-auto-rev.slang @@ -1,4 +1,5 @@ //TEST_IGNORE_FILE: +implementing "bsdf-sample"; struct ShadingData { @@ -103,4 +104,4 @@ float bsdfGGXPDF(in float3 hLocal, in Auto_Bwd_BSDFParameters params) float d = ((cosTheta * a2 - cosTheta) * cosTheta + 1); return (a2 / (d * d * 3.1415926)) * cosTheta; -} \ No newline at end of file +} diff --git a/tests/autodiff/bsdf/bsdf-sample.slang b/tests/autodiff/bsdf/bsdf-sample.slang index 8a9508791..9dfecf97e 100644 --- a/tests/autodiff/bsdf/bsdf-sample.slang +++ b/tests/autodiff/bsdf/bsdf-sample.slang @@ -2,9 +2,10 @@ //TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -output-using-type //TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer -RWStructuredBuffer outputBuffer; +module "bsdf-sample"; +__include bsdf_auto_rev; -__exported import bsdf_auto_rev; +RWStructuredBuffer outputBuffer; [numthreads(1, 1, 1)] void computeMain(uint3 dispatchThreadID: SV_DispatchThreadID) diff --git a/tests/autodiff/cuda-kernel-export.slang b/tests/autodiff/cuda-kernel-export.slang index 928133c94..39815ede8 100644 --- a/tests/autodiff/cuda-kernel-export.slang +++ b/tests/autodiff/cuda-kernel-export.slang @@ -35,7 +35,7 @@ void myKernel(TensorView inValues, TensorView outValues) // TORCH: {{^SLANG_PRELUDE_EXPORT$}} // TORCH-NEXT: std::tuple, std::tuple>, std::tuple>>> runCompute(std::tuple input_[[#]]) [TorchEntryPoint] -public __extern_cpp MyType runCompute(MyInput input) +export __extern_cpp MyType runCompute(MyInput input) { MyType rs; var outValues = TorchTensor.alloc(1); diff --git a/tests/autodiff/material/DiffuseMaterial.slang b/tests/autodiff/material/DiffuseMaterial.slang index e762e17c8..3dd2927f5 100644 --- a/tests/autodiff/material/DiffuseMaterial.slang +++ b/tests/autodiff/material/DiffuseMaterial.slang @@ -3,13 +3,13 @@ __exported import IMaterial; __exported import DiffuseMaterialInstance; -struct DiffuseMaterial : IMaterial +public struct DiffuseMaterial : IMaterial { - float3 baseColor; + public float3 baseColor; - typedef DiffuseMaterialInstance MaterialInstance; + public typedef DiffuseMaterialInstance MaterialInstance; - DiffuseMaterialInstance setupMaterialInstance() + public DiffuseMaterialInstance setupMaterialInstance() { DiffuseMaterialInstance mi = { {baseColor} }; return mi; diff --git a/tests/autodiff/material/DiffuseMaterialInstance.slang b/tests/autodiff/material/DiffuseMaterialInstance.slang index ca6bfc69a..c6f0e315b 100644 --- a/tests/autodiff/material/DiffuseMaterialInstance.slang +++ b/tests/autodiff/material/DiffuseMaterialInstance.slang @@ -3,23 +3,23 @@ __exported import IMaterialInstance; __exported import IBSDF; -struct DiffuseBSDF : IBSDF +public struct DiffuseBSDF : IBSDF { - float3 albedo; + public float3 albedo; [TreatAsDifferentiable] - float3 eval(const float3 wiLocal, const float3 woLocal) + public float3 eval(const float3 wiLocal, const float3 woLocal) { return albedo; } }; -struct DiffuseMaterialInstance : IMaterialInstance +public struct DiffuseMaterialInstance : IMaterialInstance { - DiffuseBSDF bsdf; + public DiffuseBSDF bsdf; [TreatAsDifferentiable] - float3 eval(const float3 wi, const float3 wo) + public float3 eval(const float3 wi, const float3 wo) { return bsdf.eval(wi, wo); } diff --git a/tests/autodiff/material/GlossyMaterial.slang b/tests/autodiff/material/GlossyMaterial.slang index 59cee5afe..9848c82ed 100644 --- a/tests/autodiff/material/GlossyMaterial.slang +++ b/tests/autodiff/material/GlossyMaterial.slang @@ -3,13 +3,13 @@ __exported import IMaterial; __exported import GlossyMaterialInstance; -struct GlossyMaterial : IMaterial +public struct GlossyMaterial : IMaterial { - float3 baseColor; + public float3 baseColor; - typedef GlossyMaterialInstance MaterialInstance; + public typedef GlossyMaterialInstance MaterialInstance; - GlossyMaterialInstance setupMaterialInstance() + public GlossyMaterialInstance setupMaterialInstance() { GlossyMaterialInstance mi = { { baseColor } }; return mi; diff --git a/tests/autodiff/material/GlossyMaterialInstance.slang b/tests/autodiff/material/GlossyMaterialInstance.slang index fbbcc1a26..fb1ce55c0 100644 --- a/tests/autodiff/material/GlossyMaterialInstance.slang +++ b/tests/autodiff/material/GlossyMaterialInstance.slang @@ -3,44 +3,44 @@ __exported import IMaterialInstance; __exported import IBSDF; -struct GlossyBSDF : IBSDF +public struct GlossyBSDF : IBSDF { - float3 albedo; + public float3 albedo; - float3 getAlbedo() + public float3 getAlbedo() { return albedo; } [ForwardDerivativeOf(getAlbedo)] [TreatAsDifferentiable] - DifferentialPair __fwd_d_getAlbedo() + public DifferentialPair __fwd_d_getAlbedo() { return diffPair(albedo, float3(1.f)); } [BackwardDerivativeOf(getAlbedo)] [TreatAsDifferentiable] - void __bwd_d_getAlbedo(float3 dOut) + public void __bwd_d_getAlbedo(float3 dOut) { [unroll] for (int j = 0; j < 3; j++) outputBuffer[j + 3] += dOut[j]; } [BackwardDifferentiable] - float3 eval(const float3 wiLocal, const float3 woLocal) + public float3 eval(const float3 wiLocal, const float3 woLocal) { float3 a = getAlbedo(); return a * a; } }; -struct GlossyMaterialInstance : IMaterialInstance +public struct GlossyMaterialInstance : IMaterialInstance { - GlossyBSDF bsdf; + public GlossyBSDF bsdf; [BackwardDifferentiable] - float3 eval(const float3 wi, const float3 wo) + public float3 eval(const float3 wi, const float3 wo) { return bsdf.eval(wi, wo); } diff --git a/tests/autodiff/material/IBSDF.slang b/tests/autodiff/material/IBSDF.slang index 9bdeb9197..933392b80 100644 --- a/tests/autodiff/material/IBSDF.slang +++ b/tests/autodiff/material/IBSDF.slang @@ -1,9 +1,9 @@ //TEST_IGNORE_FILE: //TEST_INPUT:ubuffer(data=[0 0 0 0 0 0], stride=4):out,name=outputBuffer -RWStructuredBuffer outputBuffer; +public RWStructuredBuffer outputBuffer; -interface IBSDF +public interface IBSDF { [BackwardDifferentiable] float3 eval(const float3 wi, const float3 wo); diff --git a/tests/autodiff/material/IMaterial.slang b/tests/autodiff/material/IMaterial.slang index 4bdaac2bc..73390ba0e 100644 --- a/tests/autodiff/material/IMaterial.slang +++ b/tests/autodiff/material/IMaterial.slang @@ -2,9 +2,9 @@ __exported import IMaterialInstance; -interface IMaterial +public interface IMaterial { - associatedtype MaterialInstance : IMaterialInstance; + public associatedtype MaterialInstance : IMaterialInstance; - MaterialInstance setupMaterialInstance(); + public MaterialInstance setupMaterialInstance(); } diff --git a/tests/autodiff/material/IMaterialInstance.slang b/tests/autodiff/material/IMaterialInstance.slang index fb13f8982..4425c2c65 100644 --- a/tests/autodiff/material/IMaterialInstance.slang +++ b/tests/autodiff/material/IMaterialInstance.slang @@ -1,7 +1,7 @@ //TEST_IGNORE_FILE: -interface IMaterialInstance +public interface IMaterialInstance { [BackwardDifferentiable] - float3 eval(const float3 wi, const float3 wo); + public float3 eval(const float3 wi, const float3 wo); } diff --git a/tests/autodiff/material/MaterialSystem.slang b/tests/autodiff/material/MaterialSystem.slang index 5e733fad9..9fcee1386 100644 --- a/tests/autodiff/material/MaterialSystem.slang +++ b/tests/autodiff/material/MaterialSystem.slang @@ -2,7 +2,7 @@ __exported import IMaterial; -IMaterial createMaterialClassConformance(int type, float3 value) +public IMaterial createMaterialClassConformance(int type, float3 value) { return createDynamicObject(type, value); } diff --git a/tests/autodiff/material2/DiffuseMaterial.slang b/tests/autodiff/material2/DiffuseMaterial.slang index 721445249..d4ae579ff 100644 --- a/tests/autodiff/material2/DiffuseMaterial.slang +++ b/tests/autodiff/material2/DiffuseMaterial.slang @@ -3,20 +3,20 @@ __exported import IMaterial; __exported import DiffuseMaterialInstance; -struct DiffuseMaterial : IMaterial +public struct DiffuseMaterial : IMaterial { - float3 baseColor; + public float3 baseColor; - typedef DiffuseMaterialInstance MaterialInstance; + public typedef DiffuseMaterialInstance MaterialInstance; [TreatAsDifferentiable] - float3 getAlbedo(float3 albedo) + public float3 getAlbedo(float3 albedo) { return albedo; } [BackwardDifferentiable] - DiffuseMaterialInstance setupMaterialInstance(out MaterialInstanceData miData) + public DiffuseMaterialInstance setupMaterialInstance(out MaterialInstanceData miData) { float3 albedo = getAlbedo(baseColor); DiffuseMaterialInstance mi = { baseColor }; diff --git a/tests/autodiff/material2/DiffuseMaterialInstance.slang b/tests/autodiff/material2/DiffuseMaterialInstance.slang index e33e3f568..7f8421d9f 100644 --- a/tests/autodiff/material2/DiffuseMaterialInstance.slang +++ b/tests/autodiff/material2/DiffuseMaterialInstance.slang @@ -3,12 +3,12 @@ __exported import IMaterialInstance; __exported import IBSDF; -struct DiffuseMaterialInstance : IMaterialInstance +public struct DiffuseMaterialInstance : IMaterialInstance { - float3 albedo; + public float3 albedo; [BackwardDifferentiable] - float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo) + public float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo) { float3 albedo; for (uint i = 0; i < 3; i++) albedo[i] = miData.data[i]; diff --git a/tests/autodiff/material2/GlossyMaterial.slang b/tests/autodiff/material2/GlossyMaterial.slang index 34ade54cb..e937443d4 100644 --- a/tests/autodiff/material2/GlossyMaterial.slang +++ b/tests/autodiff/material2/GlossyMaterial.slang @@ -3,34 +3,34 @@ __exported import IMaterial; __exported import GlossyMaterialInstance; -struct GlossyMaterial : IMaterial +public struct GlossyMaterial : IMaterial { - float3 baseColor; + public float3 baseColor; - typedef GlossyMaterialInstance MaterialInstance; + public typedef GlossyMaterialInstance MaterialInstance; - float3 getAlbedo(float3 albedo) + public float3 getAlbedo(float3 albedo) { return albedo; } [ForwardDerivativeOf(getAlbedo)] [TreatAsDifferentiable] - DifferentialPair __fwd_d_getAlbedo(DifferentialPair dpAlbedo) + public DifferentialPair __fwd_d_getAlbedo(DifferentialPair dpAlbedo) { return diffPair(dpAlbedo.p, float3(1.f)); } [BackwardDerivativeOf(getAlbedo)] [TreatAsDifferentiable] - void __bwd_d_getAlbedo(inout DifferentialPair dpAlbedo, float3 dOut) + public void __bwd_d_getAlbedo(inout DifferentialPair dpAlbedo, float3 dOut) { [unroll] for (int j = 0; j < 3; j++) outputBuffer[j + 3] += dOut[j]; } [BackwardDifferentiable] - GlossyMaterialInstance setupMaterialInstance(out MaterialInstanceData miData) + public GlossyMaterialInstance setupMaterialInstance(out MaterialInstanceData miData) { float3 albedo = getAlbedo(baseColor); GlossyMaterialInstance mi = { baseColor }; diff --git a/tests/autodiff/material2/GlossyMaterialInstance.slang b/tests/autodiff/material2/GlossyMaterialInstance.slang index 3cef458ff..64973a5ea 100644 --- a/tests/autodiff/material2/GlossyMaterialInstance.slang +++ b/tests/autodiff/material2/GlossyMaterialInstance.slang @@ -3,12 +3,12 @@ __exported import IMaterialInstance; __exported import IBSDF; -struct GlossyMaterialInstance : IMaterialInstance +public struct GlossyMaterialInstance : IMaterialInstance { - float3 albedo; + public float3 albedo; [BackwardDifferentiable] - float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo) + public float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo) { float3 albedo = float3(0.0f, 0.0f, 0.0f); for (uint i = 0; i < 3; i++) albedo[i] = miData.data[i]; diff --git a/tests/autodiff/material2/IBSDF.slang b/tests/autodiff/material2/IBSDF.slang index 57cff2883..49bfde6e7 100644 --- a/tests/autodiff/material2/IBSDF.slang +++ b/tests/autodiff/material2/IBSDF.slang @@ -1,10 +1,10 @@ //TEST_IGNORE_FILE: -//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer -RWStructuredBuffer outputBuffer; +// TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer +public RWStructuredBuffer outputBuffer; -interface IBSDF +public interface IBSDF { [BackwardDifferentiable] - float3 eval(const float3 wi, const float3 wo); + public float3 eval(const float3 wi, const float3 wo); } diff --git a/tests/autodiff/material2/IMaterial.slang b/tests/autodiff/material2/IMaterial.slang index 66780c9ff..cefb69393 100644 --- a/tests/autodiff/material2/IMaterial.slang +++ b/tests/autodiff/material2/IMaterial.slang @@ -2,10 +2,10 @@ __exported import IMaterialInstance; -interface IMaterial +public interface IMaterial { - associatedtype MaterialInstance : IMaterialInstance; + public associatedtype MaterialInstance : IMaterialInstance; [BackwardDifferentiable] - MaterialInstance setupMaterialInstance(out MaterialInstanceData miData); + public MaterialInstance setupMaterialInstance(out MaterialInstanceData miData); } diff --git a/tests/autodiff/material2/IMaterialInstance.slang b/tests/autodiff/material2/IMaterialInstance.slang index ddf5ca0f6..6b6b617b5 100644 --- a/tests/autodiff/material2/IMaterialInstance.slang +++ b/tests/autodiff/material2/IMaterialInstance.slang @@ -1,8 +1,8 @@ //TEST_IGNORE_FILE: -struct MaterialInstanceData : IDifferentiable +public struct MaterialInstanceData : IDifferentiable { - float data[5]; + public float data[5]; } /* @@ -13,8 +13,8 @@ struct MaterialInstanceSetupResult : IDifferentiable } */ -interface IMaterialInstance +public interface IMaterialInstance { [BackwardDifferentiable] - float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo); + public float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo); } diff --git a/tests/autodiff/material2/MaterialSystem.slang b/tests/autodiff/material2/MaterialSystem.slang index 5e733fad9..9fcee1386 100644 --- a/tests/autodiff/material2/MaterialSystem.slang +++ b/tests/autodiff/material2/MaterialSystem.slang @@ -2,7 +2,7 @@ __exported import IMaterial; -IMaterial createMaterialClassConformance(int type, float3 value) +public IMaterial createMaterialClassConformance(int type, float3 value) { return createDynamicObject(type, value); } diff --git a/tests/autodiff/material2/MxLayeredMaterial.slang b/tests/autodiff/material2/MxLayeredMaterial.slang index 5e2717b60..8c5cd6d3c 100644 --- a/tests/autodiff/material2/MxLayeredMaterial.slang +++ b/tests/autodiff/material2/MxLayeredMaterial.slang @@ -5,54 +5,54 @@ __exported import IMaterialInstance; import MxLayeredMaterialInstance; import MxWeights; -struct LayeredData_mixedLobes : IMxLayeredMaterialData +public struct LayeredData_mixedLobes : IMxLayeredMaterialData { - static const int bsdfCount = 3; - static const int layerCount = 1; + public static const int bsdfCount = 3; + public static const int layerCount = 1; - int getBsdfCount() { return bsdfCount; } - int getLayerCount() { return layerCount; } + public int getBsdfCount() { return bsdfCount; } + public int getLayerCount() { return layerCount; } } #define TMxLayeredMaterialData LayeredData_mixedLobes -struct Layered_mixedLobes_WeightsCalculator : IMxLayeredWeightCalculator +public struct Layered_mixedLobes_WeightsCalculator : IMxLayeredWeightCalculator { - void calculateWeights( + public void calculateWeights( const TLayeredMaterialData data, out MxWeights weights) { for (uint i = 0; i < TBsdfCount; i++) weights.weights[i] = float3(0.f); } } -struct MxLayeredMaterial : IMaterial +public struct MxLayeredMaterial : IMaterial { - float3 baseColor; + public float3 baseColor; - typealias UsedMaterialInstance = MxLayeredMaterialInstance<3, 1, TMxLayeredMaterialData, Layered_mixedLobes_WeightsCalculator>; - typedef UsedMaterialInstance MaterialInstance; + public typealias UsedMaterialInstance = MxLayeredMaterialInstance<3, 1, TMxLayeredMaterialData, Layered_mixedLobes_WeightsCalculator>; + public typedef UsedMaterialInstance MaterialInstance; - float3 getAlbedo(float3 albedo) + public float3 getAlbedo(float3 albedo) { return albedo; } [ForwardDerivativeOf(getAlbedo)] [TreatAsDifferentiable] - DifferentialPair __fwd_d_getAlbedo(DifferentialPair dpAlbedo) + public DifferentialPair __fwd_d_getAlbedo(DifferentialPair dpAlbedo) { return diffPair(dpAlbedo.p, float3(1.f)); } [BackwardDerivativeOf(getAlbedo)] [TreatAsDifferentiable] - void __bwd_d_getAlbedo(inout DifferentialPair dpAlbedo, float3 dOut) + public void __bwd_d_getAlbedo(inout DifferentialPair dpAlbedo, float3 dOut) { [unroll] for (int j = 0; j < 3; j++) outputBuffer[j + 6] += dOut[j]; } - + [Differentiable] - UsedMaterialInstance setupMaterialInstance(out MaterialInstanceData miData) + public UsedMaterialInstance setupMaterialInstance(out MaterialInstanceData miData) { float3 albedo = getAlbedo(baseColor); UsedMaterialInstance mi; diff --git a/tests/autodiff/material2/MxLayeredMaterialInstance.slang b/tests/autodiff/material2/MxLayeredMaterialInstance.slang index 53a319d6f..ed2ef1cb0 100644 --- a/tests/autodiff/material2/MxLayeredMaterialInstance.slang +++ b/tests/autodiff/material2/MxLayeredMaterialInstance.slang @@ -5,17 +5,17 @@ __exported import IBSDF; import MxWeights; -struct MxLayeredMaterialInstance< +public struct MxLayeredMaterialInstance< let TBsdfCount : int, let TLayerCount : int, TLayeredMaterialData : IMxLayeredMaterialData, TWeightsCalc : IMxLayeredWeightCalculator> : IMaterialInstance { - TLayeredMaterialData data; - TWeightsCalc calculator; - typealias Weights = MxWeights; + public TLayeredMaterialData data; + public TWeightsCalc calculator; + public typealias Weights = MxWeights; - Weights calculateWeights() + public Weights calculateWeights() { Weights result; calculator.calculateWeights(data, result); @@ -23,7 +23,7 @@ struct MxLayeredMaterialInstance< } [Differentiable] - float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo) + public float3 eval(const MaterialInstanceData miData, const float3 wi, const float3 wo) { float3 albedo; for (uint i = 0; i < 3; i++) albedo[i] = miData.data[i]; diff --git a/tests/autodiff/material2/MxWeights.slang b/tests/autodiff/material2/MxWeights.slang index 1d1a9f543..1e9c7d36c 100644 --- a/tests/autodiff/material2/MxWeights.slang +++ b/tests/autodiff/material2/MxWeights.slang @@ -1,18 +1,18 @@ //TEST_IGNORE_FILE: -struct MxWeights +public struct MxWeights { - float3 weights[TBsdfCount]; + public float3 weights[TBsdfCount]; } -interface IMxLayeredMaterialData +public interface IMxLayeredMaterialData { - int getBsdfCount(); - int getLayerCount(); + public int getBsdfCount(); + public int getLayerCount(); } -interface IMxLayeredWeightCalculator +public interface IMxLayeredWeightCalculator { - void calculateWeights( + public void calculateWeights( const TLayeredMaterialData data, out MxWeights weights); } diff --git a/tests/bindings/multi-file-defines.h b/tests/bindings/multi-file-defines.h index 52a5826b6..db0261158 100644 --- a/tests/bindings/multi-file-defines.h +++ b/tests/bindings/multi-file-defines.h @@ -5,12 +5,13 @@ #define BEGIN_CBUFFER(NAME) cbuffer NAME #define END_CBUFFER(NAME, REG) /**/ #define CBUFFER_REF(NAME, FIELD) FIELD +#define PUBLIC public #else #define R(X) X #define BEGIN_CBUFFER(NAME) struct SLANG_ParameterGroup_##NAME #define END_CBUFFER(NAME, REG) ; cbuffer NAME : REG { SLANG_ParameterGroup_##NAME NAME; } #define CBUFFER_REF(NAME, FIELD) NAME.FIELD - +#define PUBLIC #define sharedC sharedC_0 #define sharedCA sharedCA_0 #define sharedCB sharedCB_0 diff --git a/tests/bindings/multi-file-shared.slang b/tests/bindings/multi-file-shared.slang index af91d5251..dcfa6251c 100644 --- a/tests/bindings/multi-file-shared.slang +++ b/tests/bindings/multi-file-shared.slang @@ -3,23 +3,23 @@ #include "multi-file-defines.h" -float4 use(float val) { return val; }; -float4 use(float2 val) { return float4(val,0.0,0.0); }; -float4 use(float3 val) { return float4(val,0.0); }; -float4 use(float4 val) { return val; }; -float4 use(Texture2D t, SamplerState s) { return t.SampleLevel(s, 0.0, 0.0); } +PUBLIC float4 use(float val) { return val; }; +PUBLIC float4 use(float2 val) { return float4(val,0.0,0.0); }; +PUBLIC float4 use(float3 val) { return float4(val,0.0); }; +PUBLIC float4 use(float4 val) { return val; }; +PUBLIC float4 use(Texture2D t, SamplerState s) { return t.SampleLevel(s, 0.0, 0.0); } -Texture2D sharedT R(: register(t2)); -SamplerState sharedS R(: register(s2)); +PUBLIC Texture2D sharedT R(: register(t2)); +PUBLIC SamplerState sharedS R(: register(s2)); -BEGIN_CBUFFER(sharedC) +PUBLIC BEGIN_CBUFFER(sharedC) { - float3 sharedCA; - float sharedCB; - float3 sharedCC; - float2 sharedCD; + PUBLIC float3 sharedCA; + PUBLIC float sharedCB; + PUBLIC float3 sharedCC; + PUBLIC float2 sharedCD; } END_CBUFFER(sharedC, register(b2)) -Texture2D sharedTV R(: register(t3)); -Texture2D sharedTF R(: register(t4)); +PUBLIC Texture2D sharedTV R(: register(t3)); +PUBLIC Texture2D sharedTF R(: register(t4)); diff --git a/tests/bugs/gl-33-ext.slang b/tests/bugs/gl-33-ext.slang index ae70cfaf0..fccde99a3 100644 --- a/tests/bugs/gl-33-ext.slang +++ b/tests/bugs/gl-33-ext.slang @@ -1,8 +1,8 @@ // gl-33-ext.slang //TEST_IGNORE_FILE: -struct A +public struct A { - int state; - [mutating] int next() { return state; } + public int state; + [mutating] public int next() { return state; } }; diff --git a/tests/bugs/interface-lvalue.slang b/tests/bugs/interface-lvalue.slang index 87cfa54ed..e80a130d1 100644 --- a/tests/bugs/interface-lvalue.slang +++ b/tests/bugs/interface-lvalue.slang @@ -23,7 +23,7 @@ void createFoo(out IFoo val) val = resuult; } -public __extern_cpp int main() +export __extern_cpp int main() { IFoo v; createFoo(v); diff --git a/tests/bugs/split-nested-types.slang b/tests/bugs/split-nested-types.slang index 3bd4e239f..b9bfb9e62 100644 --- a/tests/bugs/split-nested-types.slang +++ b/tests/bugs/split-nested-types.slang @@ -1,14 +1,14 @@ //TEST_IGNORE_FILE: -struct A { int x; }; +public struct A { public int x; }; -struct B { float y; }; +public struct B { public float y; }; -struct CC { Texture2D t; SamplerState s; }; +public struct CC { public Texture2D t; public SamplerState s; }; -struct M +public struct M { - A a; - B b; - CC c; + public A a; + public B b; + public CC c; }; diff --git a/tests/compute/array-existential-parameter.slang b/tests/compute/array-existential-parameter.slang index 813c5a40b..b7473e447 100644 --- a/tests/compute/array-existential-parameter.slang +++ b/tests/compute/array-existential-parameter.slang @@ -38,7 +38,7 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) //TEST_INPUT: globalExistentialType __Dynamic // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct MyImpl : IInterface +export struct MyImpl : IInterface { int val; int run(int input) diff --git a/tests/compute/dynamic-dispatch-11.slang b/tests/compute/dynamic-dispatch-11.slang index d6f64aa99..59e7ce581 100644 --- a/tests/compute/dynamic-dispatch-11.slang +++ b/tests/compute/dynamic-dispatch-11.slang @@ -34,7 +34,7 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) // No type input for dynamic dispatch //TEST_INPUT: globalExistentialType MyImpl // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct MyImpl : IInterface +export struct MyImpl : IInterface { int val; int run(int input) diff --git a/tests/compute/dynamic-dispatch-12.slang b/tests/compute/dynamic-dispatch-12.slang index 906a5da0e..28f9c4c14 100644 --- a/tests/compute/dynamic-dispatch-12.slang +++ b/tests/compute/dynamic-dispatch-12.slang @@ -37,14 +37,14 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) } // Specialize gCb1, but not gCb2 -//TEST_INPUT: globalExistentialType MyImpl -//TEST_INPUT: globalExistentialType __Dynamic +// TEST_INPUT: globalExistentialType MyImpl +// TEST_INPUT: globalExistentialType __Dynamic // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct MyImpl : IInterface +export struct MyImpl : IInterface { int val; int run(int input) { return input + val; } -}; \ No newline at end of file +}; diff --git a/tests/compute/dynamic-dispatch-13.slang b/tests/compute/dynamic-dispatch-13.slang index f9ad9683b..20d78e0ad 100644 --- a/tests/compute/dynamic-dispatch-13.slang +++ b/tests/compute/dynamic-dispatch-13.slang @@ -35,7 +35,7 @@ void computeMain(int3 dispatchThreadID : SV_DispatchThreadID) } // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct MyImpl : IInterface +export struct MyImpl : IInterface { int val; int run(int input) @@ -43,7 +43,7 @@ public struct MyImpl : IInterface return input + val; } }; -public struct MyImpl2 : IInterface +export struct MyImpl2 : IInterface { int val; int run(int input) diff --git a/tests/compute/dynamic-dispatch-14.slang b/tests/compute/dynamic-dispatch-14.slang index e605132c6..0dc99b432 100644 --- a/tests/compute/dynamic-dispatch-14.slang +++ b/tests/compute/dynamic-dispatch-14.slang @@ -46,10 +46,10 @@ void computeMain(int3 dispatchThreadID : SV_DispatchThreadID) } // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct MyImpl : IInterface +export struct MyImpl : IInterface { int val; - public struct TAssoc : IAssoc + export struct TAssoc : IAssoc { int val; int eval() { return val; } @@ -62,10 +62,10 @@ public struct MyImpl : IInterface } }; -public struct MyImpl2 : IInterface +export struct MyImpl2 : IInterface { int val; - public struct TAssoc : IAssoc + export struct TAssoc : IAssoc { int val; int eval() { return val; } @@ -76,4 +76,4 @@ public struct MyImpl2 : IInterface rs.val = input - val; return rs; } -}; \ No newline at end of file +}; diff --git a/tests/compute/dynamic-dispatch-15.slang b/tests/compute/dynamic-dispatch-15.slang index 5e2be1a4c..2ab169281 100644 --- a/tests/compute/dynamic-dispatch-15.slang +++ b/tests/compute/dynamic-dispatch-15.slang @@ -46,7 +46,7 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) } // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct FloatVal : IInterface +export struct FloatVal : IInterface { float val; float run() @@ -56,7 +56,7 @@ public struct FloatVal : IInterface }; interface ISomething{void g();} struct Float4Struct : ISomething { float4 val; void g() {} } -public struct Float4Val : IInterface +export struct Float4Val : IInterface { Float4Struct val; float run() @@ -64,7 +64,7 @@ public struct Float4Val : IInterface return val.val.x; } }; -public struct IntVal : IInterface +export struct IntVal : IInterface { int val; float run() @@ -72,11 +72,11 @@ public struct IntVal : IInterface return val; } }; -public struct Int4Val : IInterface +export struct Int4Val : IInterface { int4 val; float run() { return val.x; } -}; \ No newline at end of file +}; diff --git a/tests/compute/dynamic-dispatch-bindless-texture.slang b/tests/compute/dynamic-dispatch-bindless-texture.slang index 04c1f1766..34ef67d1e 100644 --- a/tests/compute/dynamic-dispatch-bindless-texture.slang +++ b/tests/compute/dynamic-dispatch-bindless-texture.slang @@ -28,7 +28,7 @@ void computeMain(int3 dispatchThreadID : SV_DispatchThreadID) //TEST_INPUT: globalExistentialType __Dynamic // Type must be marked `public` to ensure it is visible in the generated DLL. -public struct MyImpl : IInterface +export struct MyImpl : IInterface { Texture2D tex; SamplerState sampler; diff --git a/tests/compute/interface-assoc-type-param.slang b/tests/compute/interface-assoc-type-param.slang index 805f673a2..a234d457f 100644 --- a/tests/compute/interface-assoc-type-param.slang +++ b/tests/compute/interface-assoc-type-param.slang @@ -16,7 +16,7 @@ interface IEval uint eval(); } -public struct Impl : IInterface +export struct Impl : IInterface { uint val; struct TEval : IEval @@ -55,4 +55,4 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID, { uint tid = dispatchThreadID.x; compute(tid, params); -} \ No newline at end of file +} diff --git a/tests/compute/interface-func-param-in-struct.slang b/tests/compute/interface-func-param-in-struct.slang index c47b25d70..01e4aa111 100644 --- a/tests/compute/interface-func-param-in-struct.slang +++ b/tests/compute/interface-func-param-in-struct.slang @@ -9,7 +9,7 @@ interface IInterface uint eval(); } -public struct Impl : IInterface +export struct Impl : IInterface { uint val; uint eval() @@ -38,4 +38,4 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID, { uint tid = dispatchThreadID.x; compute(tid, params); -} \ No newline at end of file +} diff --git a/tests/compute/interface-param-partial-specialize.slang b/tests/compute/interface-param-partial-specialize.slang index 9be22c6c4..1d09c9f55 100644 --- a/tests/compute/interface-param-partial-specialize.slang +++ b/tests/compute/interface-param-partial-specialize.slang @@ -12,7 +12,7 @@ interface IInterface uint eval(); } -public struct Impl : IInterface +export struct Impl : IInterface { uint val; uint eval() @@ -48,4 +48,4 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID, { uint tid = dispatchThreadID.x; compute(tid, params); -} \ No newline at end of file +} diff --git a/tests/cpu-program/class-com.slang b/tests/cpu-program/class-com.slang index e2812f586..2dd84d696 100644 --- a/tests/cpu-program/class-com.slang +++ b/tests/cpu-program/class-com.slang @@ -20,7 +20,7 @@ class MyClass : IFoo }; [DllExport] -IFoo createFoo() +export IFoo createFoo() { IFoo result = new MyClass(); return result; @@ -30,8 +30,8 @@ IFoo createFoo() [DllImport("", "createFoo")] IFoo createFooImported(); -public __extern_cpp int main() +export __extern_cpp int main() { var obj = createFooImported(); return obj.method(); -} \ No newline at end of file +} diff --git a/tests/cpu-program/class.slang b/tests/cpu-program/class.slang index 1a510118a..8935b157a 100644 --- a/tests/cpu-program/class.slang +++ b/tests/cpu-program/class.slang @@ -13,8 +13,8 @@ class MyClass } } -public __extern_cpp int main() +export __extern_cpp int main() { MyClass obj = new MyClass(); return obj.method(); -} \ No newline at end of file +} diff --git a/tests/cpu-program/cpu-hello-world.slang b/tests/cpu-program/cpu-hello-world.slang index f1285f889..a44761e8f 100644 --- a/tests/cpu-program/cpu-hello-world.slang +++ b/tests/cpu-program/cpu-hello-world.slang @@ -1,6 +1,6 @@ //TEST:EXECUTABLE: -public __extern_cpp int main() +export __extern_cpp int main() { printf("Hello World.\n"); return 0; diff --git a/tests/cpu-program/gfx-smoke.slang b/tests/cpu-program/gfx-smoke.slang index d0acb83db..41c87ca19 100644 --- a/tests/cpu-program/gfx-smoke.slang +++ b/tests/cpu-program/gfx-smoke.slang @@ -2,7 +2,7 @@ import gfx; import slang; -public __extern_cpp int main() +export __extern_cpp int main() { gfx.DeviceDesc deviceDesc = {}; deviceDesc.deviceType = gfx.DeviceType.CPU; @@ -98,4 +98,4 @@ public __extern_cpp int main() printf("%.1f\n", val); } return 0; -} \ No newline at end of file +} diff --git a/tests/cpu-program/pointer-basics.slang b/tests/cpu-program/pointer-basics.slang index e38d866eb..db705d507 100644 --- a/tests/cpu-program/pointer-basics.slang +++ b/tests/cpu-program/pointer-basics.slang @@ -1,5 +1,5 @@ //TEST:EXECUTABLE: -public __extern_cpp int main() +export __extern_cpp int main() { uint2 value; int *pValue = (int*)&value; @@ -20,4 +20,4 @@ public __extern_cpp int main() else printf("Fail\n"); return 0; -} \ No newline at end of file +} diff --git a/tests/cpu-program/pointer-deref.slang b/tests/cpu-program/pointer-deref.slang index 79f1b2cd0..f5bad5b09 100644 --- a/tests/cpu-program/pointer-deref.slang +++ b/tests/cpu-program/pointer-deref.slang @@ -11,7 +11,7 @@ struct Record SubRecord sub; } -public __extern_cpp int main() +export __extern_cpp int main() { Record rec; Record *pRec = &rec; @@ -27,4 +27,4 @@ public __extern_cpp int main() printf("fail\n"); } return 0; -} \ No newline at end of file +} diff --git a/tests/current-bugs/resource-dynamic-dispatch.slang b/tests/current-bugs/resource-dynamic-dispatch.slang index 0a987b1cf..f344448dd 100644 --- a/tests/current-bugs/resource-dynamic-dispatch.slang +++ b/tests/current-bugs/resource-dynamic-dispatch.slang @@ -26,13 +26,13 @@ interface IInterface }; // Need public to make these conformances available -public struct A : IInterface +export struct A : IInterface { typedef SomeData Type; IGetTexture getType() { Type t = { gTexA }; return t; } }; -public struct B : IInterface +export struct B : IInterface { typedef SomeData Type; IGetTexture getType() { Type t = { gTexB }; return t; } @@ -64,4 +64,4 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) let tex = getTex.getTexture(); outputBuffer[tid] = tex.Load(int3(tid, tid, 0)); -} \ No newline at end of file +} diff --git a/tests/diagnostics/extension-visibility-a.slang b/tests/diagnostics/extension-visibility-a.slang index 97e4ef85c..162320d51 100644 --- a/tests/diagnostics/extension-visibility-a.slang +++ b/tests/diagnostics/extension-visibility-a.slang @@ -1,17 +1,17 @@ // extension-visibility-a.slang -interface IThing +public interface IThing { - int getValue(); + public int getValue(); } // Note: not implementing the interface here! -struct MyThing +public struct MyThing { - int value; + public int value; } -int helper(T thing) +public int helper(T thing) { return thing.getValue(); -} \ No newline at end of file +} diff --git a/tests/diagnostics/extension-visibility-b.slang b/tests/diagnostics/extension-visibility-b.slang index 7848f2a56..dbefe66e7 100644 --- a/tests/diagnostics/extension-visibility-b.slang +++ b/tests/diagnostics/extension-visibility-b.slang @@ -2,7 +2,7 @@ import extension_visibility_a; -extension MyThing : IThing +public extension MyThing : IThing { - int getValue() { return value; } + public int getValue() { return value; } } diff --git a/tests/diagnostics/extension-visibility-c.slang b/tests/diagnostics/extension-visibility-c.slang index 2d7a5224d..71680b31e 100644 --- a/tests/diagnostics/extension-visibility-c.slang +++ b/tests/diagnostics/extension-visibility-c.slang @@ -3,7 +3,7 @@ import extension_visibility_a; import extension_visibility_b; -int works(MyThing thing) +public int works(MyThing thing) { return helper(thing); } diff --git a/tests/diagnostics/extension-visibility.slang.expected b/tests/diagnostics/extension-visibility.slang.expected index 732ff4189..eed86715d 100644 --- a/tests/diagnostics/extension-visibility.slang.expected +++ b/tests/diagnostics/extension-visibility.slang.expected @@ -3,9 +3,9 @@ standard error = { tests/diagnostics/extension-visibility.slang(17): error 39999: could not specialize generic for arguments of type (MyThing) return helper(thing); ^ -tests/diagnostics/extension-visibility-a.slang(14): note 39999: see declaration of func helper(T) -> int -int helper(T thing) - ^~~~~~ +tests/diagnostics/extension-visibility-a.slang(14): note 39999: see declaration of public func helper(T) -> int +public int helper(T thing) + ^~~~~~ } standard output = { } diff --git a/tests/diagnostics/internal-visibility/that-module-impl.slang b/tests/diagnostics/internal-visibility/that-module-impl.slang new file mode 100644 index 000000000..893510afe --- /dev/null +++ b/tests/diagnostics/internal-visibility/that-module-impl.slang @@ -0,0 +1,17 @@ +implementing "that-module"; + + +public void publicFunc2() { } + +internal void internalFunc1() {} + +public struct PublicStruct +{ + int x; + public int y; +} + +public namespace Namespace +{ + public static int publicVar2; +} diff --git a/tests/diagnostics/internal-visibility/that-module.slang b/tests/diagnostics/internal-visibility/that-module.slang new file mode 100644 index 000000000..b072c7231 --- /dev/null +++ b/tests/diagnostics/internal-visibility/that-module.slang @@ -0,0 +1,25 @@ +module "that-module"; + +__include "that-module-impl"; + +void internalMethod(); + +public void publicMethod(); + +public namespace Namespace +{ + static int internalVar; + + public static int publicVar; +} + +enum InternalEnum +{ + A,B,C +} +public enum PublicEnum +{ + D,E,F +} + +struct InternalStruct { int x; } diff --git a/tests/diagnostics/internal-visibility/this-module.slang b/tests/diagnostics/internal-visibility/this-module.slang new file mode 100644 index 000000000..98e0f3d08 --- /dev/null +++ b/tests/diagnostics/internal-visibility/this-module.slang @@ -0,0 +1,26 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): + +module "this-module"; + +import "that-module"; + +void test() +{ + PublicStruct s; + // CHECK:{{.*}}(11): error 30600: + s.x = 1; // Error. + // CHECK-NOT:{{.*}}error + s.y = 2; // OK. + publicMethod(); // OK. + publicFunc2(); // OK. + Namespace.publicVar = 1; // OK. + Namespace.publicVar2 = 1; // OK. + // CHECK:{{.*}}(19): error 30600: + Namespace.internalVar = 1; // error. + // CHECK:{{.*}}(21): error 30600: + InternalEnum e; // Error. + // CHECK:{{.*}}(23): error 30600: + InternalStruct s1; // Error. + // CHECK:{{.*}}(25): error 30600: + internalMethod(); // Error. +} diff --git a/tests/diagnostics/private-visibility.slang b/tests/diagnostics/private-visibility.slang new file mode 100644 index 000000000..7c0bad970 --- /dev/null +++ b/tests/diagnostics/private-visibility.slang @@ -0,0 +1,44 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): + +struct MyType +{ + private int member; + private int func() + { + return member; + } + struct SubType + { + int member2; + int func2(MyType m) + { + return m.member + member2; // OK. + } + } + int func1() { return member; } + + private __init() { member = 0; } + + private __subscript(int i)->int + { + get { return member; } + set { member = newValue; } + } + // CHECK:{{.*}}(28): error 30601: + public void publicMethod() {} // ERROR. +} + +void test() +{ + // CHECK:{{.*}}(34): error 30600: + MyType t; // ERROR. + // CHECK-NOT:{{.*}}error + t.func1(); // OK. + // CHECK:{{.*}}(38): error 30600: + t.func(); // ERROR. + // CHECK:{{.*}}(40): error 30600: + t[0] = 1; // ERROR. + + // CHECK:{{.*}}(43): error 30600: + t.member = 2; +} diff --git a/tests/diagnostics/visibility.slang b/tests/diagnostics/visibility.slang new file mode 100644 index 000000000..724ec30e7 --- /dev/null +++ b/tests/diagnostics/visibility.slang @@ -0,0 +1,23 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): + +// CHECK-DAG:{{.*}}(4): error 30603 +private struct MyStruct +{} + +struct IS +{ + // CHECK-DAG:{{.*}}(10): error 30601 + public void fp(); +} + +// CHECK-DAG:{{.*}}(14): error 30604 +public IS f() {} + +public struct PS +{ + // CHECK-DAG:{{.*}}(19): error 30604 + public IS ii; + + // CHECK-DAG:{{.*}}(22): error 30604 + public property IS ii2 { get { return {}; } }; +} diff --git a/tests/front-end/import-exported-b.slang b/tests/front-end/import-exported-b.slang index 144f62060..e7bc49510 100644 --- a/tests/front-end/import-exported-b.slang +++ b/tests/front-end/import-exported-b.slang @@ -2,4 +2,4 @@ // This file defines the code that will be (transitively) imported into `import-exported.slang` -float foo(float x) { return x; } \ No newline at end of file +public float foo(float x) { return x; } diff --git a/tests/front-end/import-subdir-search-path.slang b/tests/front-end/import-subdir-search-path.slang index 9b7c7a520..b35802f14 100644 --- a/tests/front-end/import-subdir-search-path.slang +++ b/tests/front-end/import-subdir-search-path.slang @@ -7,4 +7,4 @@ __import import_subdir_a; // Should realize it's the same thing __import subdir.import_subdir_a; -float bar(float x) { return foo(x); } \ No newline at end of file +float bar(float x) { return foo(x); } diff --git a/tests/front-end/raw-string-literal.slang b/tests/front-end/raw-string-literal.slang index 321f183c7..7297c27e1 100644 --- a/tests/front-end/raw-string-literal.slang +++ b/tests/front-end/raw-string-literal.slang @@ -17,7 +17,7 @@ void printLines(NativeString text) } } -public __extern_cpp int main() +export __extern_cpp int main() { printLines( R"(This is line 1. @@ -25,4 +25,4 @@ public __extern_cpp int main() "Hello World" )"); return 0; -} \ No newline at end of file +} diff --git a/tests/front-end/subdir/import-subdir-a.slang b/tests/front-end/subdir/import-subdir-a.slang index 3962d4662..e973d5cbf 100644 --- a/tests/front-end/subdir/import-subdir-a.slang +++ b/tests/front-end/subdir/import-subdir-a.slang @@ -2,4 +2,4 @@ // This is the imported code. -float foo(float x) { return x; } \ No newline at end of file +public float foo(float x) { return x; } diff --git a/tests/ir/string-literal-module.slang b/tests/ir/string-literal-module.slang index 96fb62088..245b4b3a2 100644 --- a/tests/ir/string-literal-module.slang +++ b/tests/ir/string-literal-module.slang @@ -1,6 +1,6 @@ //TEST_IGNORE_FILE: -int doSomethingElse() +public int doSomethingElse() { return getStringHash("Try another"); } diff --git a/tests/language-feature/extensions/extension-import-helper.slang b/tests/language-feature/extensions/extension-import-helper.slang index c1a652645..11265762a 100644 --- a/tests/language-feature/extensions/extension-import-helper.slang +++ b/tests/language-feature/extensions/extension-import-helper.slang @@ -2,12 +2,12 @@ //TEST_IGNORE_FILE: -interface IThing +public interface IThing { - float getValue(); + public float getValue(); } -extension float : IThing +public extension float : IThing { - float getValue() { return this; } -} \ No newline at end of file + public float getValue() { return this; } +} diff --git a/tests/language-feature/generics/struct-generic-value-param-import.slang b/tests/language-feature/generics/struct-generic-value-param-import.slang index edc1b0c39..785b8146f 100644 --- a/tests/language-feature/generics/struct-generic-value-param-import.slang +++ b/tests/language-feature/generics/struct-generic-value-param-import.slang @@ -6,13 +6,13 @@ // for `struct` decalrations in Slang files, including // any `import`ed code. -interface IData {} +public interface IData {} -struct Data : IData +public struct Data : IData { - int state; + public int state; - [mutating] void doStuff() + [mutating] public void doStuff() { state++; } diff --git a/tests/language-feature/inheritance/struct-inheritance-imported.slang b/tests/language-feature/inheritance/struct-inheritance-imported.slang index 8786b851c..16816ec97 100644 --- a/tests/language-feature/inheritance/struct-inheritance-imported.slang +++ b/tests/language-feature/inheritance/struct-inheritance-imported.slang @@ -1,12 +1,12 @@ //TEST_IGNORE_FILE: // struct-inheritance-imported.slang -struct Base +public struct Base { - int a; + public int a; } -struct Derived : Base +public struct Derived : Base {} -int getA(Derived d) { return d.a; } +public int getA(Derived d) { return d.a; } diff --git a/tests/library/library.slang b/tests/library/library.slang index fafb1018c..3deaeab99 100644 --- a/tests/library/library.slang +++ b/tests/library/library.slang @@ -2,12 +2,12 @@ // library.slang -int doThing(int b) +export int doThing(int b) { return b + b + 1; } -public int foo(int a) +public export int foo(int a) { return a * a + 1 + doThing(a + 2); } diff --git a/tests/reflection/multi-file-defines.h b/tests/reflection/multi-file-defines.h index aed4c510a..0948df378 100644 --- a/tests/reflection/multi-file-defines.h +++ b/tests/reflection/multi-file-defines.h @@ -5,12 +5,13 @@ #define BEGIN_CBUFFER(NAME) cbuffer NAME #define END_CBUFFER(NAME, REG) /**/ #define CBUFFER_REF(NAME, FIELD) FIELD +#define PUBLIC public #else #define R(X) /*X*/ #define BEGIN_CBUFFER(NAME) struct SLANG_ParameterGroup_##NAME #define END_CBUFFER(NAME, REG) ; cbuffer NAME /*REG*/ { SLANG_ParameterGroup_##NAME NAME; } #define CBUFFER_REF(NAME, FIELD) NAME.FIELD - +#define PUBLIC #define sharedC sharedC_0 #define sharedCA sharedCA_0 #define sharedCB sharedCB_0 diff --git a/tests/reflection/multi-file-shared.slang b/tests/reflection/multi-file-shared.slang index af91d5251..91a74cd35 100644 --- a/tests/reflection/multi-file-shared.slang +++ b/tests/reflection/multi-file-shared.slang @@ -3,23 +3,23 @@ #include "multi-file-defines.h" -float4 use(float val) { return val; }; -float4 use(float2 val) { return float4(val,0.0,0.0); }; -float4 use(float3 val) { return float4(val,0.0); }; -float4 use(float4 val) { return val; }; -float4 use(Texture2D t, SamplerState s) { return t.SampleLevel(s, 0.0, 0.0); } +PUBLIC float4 use(float val) { return val; }; +PUBLIC float4 use(float2 val) { return float4(val,0.0,0.0); }; +PUBLIC float4 use(float3 val) { return float4(val,0.0); }; +PUBLIC float4 use(float4 val) { return val; }; +PUBLIC float4 use(Texture2D t, SamplerState s) { return t.SampleLevel(s, 0.0, 0.0); } -Texture2D sharedT R(: register(t2)); -SamplerState sharedS R(: register(s2)); +PUBLIC Texture2D sharedT R(: register(t2)); +PUBLIC SamplerState sharedS R(: register(s2)); -BEGIN_CBUFFER(sharedC) +PUBLIC BEGIN_CBUFFER(sharedC) { - float3 sharedCA; - float sharedCB; - float3 sharedCC; - float2 sharedCD; + PUBLIC float3 sharedCA; + PUBLIC float sharedCB; + PUBLIC float3 sharedCC; + PUBLIC float2 sharedCD; } END_CBUFFER(sharedC, register(b2)) -Texture2D sharedTV R(: register(t3)); -Texture2D sharedTF R(: register(t4)); +PUBLIC Texture2D sharedTV R(: register(t3)); +PUBLIC Texture2D sharedTF R(: register(t4)); diff --git a/tests/reflection/reflect-imported-code.slang b/tests/reflection/reflect-imported-code.slang index 20beb94b8..4600a371c 100644 --- a/tests/reflection/reflect-imported-code.slang +++ b/tests/reflection/reflect-imported-code.slang @@ -2,13 +2,13 @@ // Imported code used by `reflect-imported-code.hlsl` -float4 use(float4 val) { return val; }; -float4 use(Texture2D t, SamplerState s) { return t.Sample(s, 0.0); } +public float4 use(float4 val) { return val; }; +public float4 use(Texture2D t, SamplerState s) { return t.Sample(s, 0.0); } -Texture2D t_i; -SamplerState s_i; +public Texture2D t_i; +public SamplerState s_i; -cbuffer C_i +public cbuffer C_i { - float c_i; + public float c_i; } diff --git a/tests/vkray/callable-shared.slang b/tests/vkray/callable-shared.slang index 09e76aab1..85d72a5f7 100644 --- a/tests/vkray/callable-shared.slang +++ b/tests/vkray/callable-shared.slang @@ -1,8 +1,8 @@ // callable-shared.slang //TEST_IGNORE_FILE: -struct MaterialPayload +public struct MaterialPayload { - float4 albedo; - float2 uv; + public float4 albedo; + public float2 uv; }; diff --git a/tools/gfx-unit-test/shader-cache-tests.cpp b/tools/gfx-unit-test/shader-cache-tests.cpp index 3879fd630..4477c4f56 100644 --- a/tools/gfx-unit-test/shader-cache-tests.cpp +++ b/tools/gfx-unit-test/shader-cache-tests.cpp @@ -430,7 +430,7 @@ namespace gfx_test { String importedContentsA = String( R"( - void processElement(RWStructuredBuffer buffer, uint index) + public void processElement(RWStructuredBuffer buffer, uint index) { var input = buffer[index]; buffer[index] = input + 1.0f; @@ -439,7 +439,7 @@ namespace gfx_test String importedContentsB = String( R"( - void processElement(RWStructuredBuffer buffer, uint index) + public void processElement(RWStructuredBuffer buffer, uint index) { var input = buffer[index]; buffer[index] = input + 2.0f; diff --git a/tools/gfx/gfx.slang b/tools/gfx/gfx.slang index e8750ada7..e9a31445b 100644 --- a/tools/gfx/gfx.slang +++ b/tools/gfx/gfx.slang @@ -1,25 +1,25 @@ import slang; -namespace gfx +public namespace gfx { -typedef slang.Result Result; +public typedef slang.Result Result; -typedef intptr_t Int; -typedef uintptr_t UInt; -typedef uint64_t DeviceAddress; -typedef int GfxIndex; -typedef int GfxCount; -typedef intptr_t Size; -typedef intptr_t Offset; +public typedef intptr_t Int; +public typedef uintptr_t UInt; +public typedef uint64_t DeviceAddress; +public typedef int GfxIndex; +public typedef int GfxCount; +public typedef intptr_t Size; +public typedef intptr_t Offset; -const uint64_t kTimeoutInfinite = 0xFFFFFFFFFFFFFFFF; +public const uint64_t kTimeoutInfinite = 0xFFFFFFFFFFFFFFFF; -enum class StructType +public enum class StructType { D3D12ExtendedDesc, }; -enum class StageType +public enum class StageType { Unknown, Vertex, @@ -39,7 +39,7 @@ enum class StageType CountOf, }; -enum class DeviceType +public enum class DeviceType { Unknown, Default, @@ -52,7 +52,7 @@ enum class DeviceType CountOf, }; -enum class ProjectionStyle +public enum class ProjectionStyle { Unknown, OpenGl, @@ -61,7 +61,7 @@ enum class ProjectionStyle CountOf, }; -enum class BindingStyle +public enum class BindingStyle { Unknown, DirectX, @@ -72,17 +72,17 @@ enum class BindingStyle CountOf, }; -enum class AccessFlag +public enum class AccessFlag { None, Read, Write, }; -static const GfxCount kMaxRenderTargetCount = 8; +public static const GfxCount kMaxRenderTargetCount = 8; // Defines how linking should be performed for a shader program. -enum class LinkingStyle +public enum class LinkingStyle { // Compose all entry-points in a single program, then compile all entry-points together with the same // set of root shader arguments. @@ -92,7 +92,7 @@ enum class LinkingStyle SeparateEntryPointCompilation }; -enum class ShaderModuleSourceType +public enum class ShaderModuleSourceType { SlangSource, // a slang source string in memory. SlangModuleBinary, // a slang module binary code in memory. @@ -100,27 +100,27 @@ enum class ShaderModuleSourceType SlangModuleBinaryFile, // a slang module binary code from file. }; -struct ShaderProgramDesc2 +public struct ShaderProgramDesc2 { - ShaderModuleSourceType sourceType; - void *sourceData; - Size sourceDataSize; + public ShaderModuleSourceType sourceType; + public void *sourceData; + public Size sourceDataSize; // Number of entry points to include in the shader program. 0 means include all entry points // defined in the module. - GfxCount entryPointCount = 0; + public GfxCount entryPointCount = 0; // Names of entry points to include in the shader program. The size of the array must be // `entryPointCount`. - NativeString* entryPointNames = nullptr; + public NativeString* entryPointNames = nullptr; }; [COM("9d32d0ad-915c-4ffd-91e2-508554a04a76")] -interface IShaderProgram +public interface IShaderProgram { - slang::TypeReflection* findTypeByName(NativeString name); + public slang::TypeReflection* findTypeByName(NativeString name); }; -enum class Format +public enum class Format { // D3D formats omitted: 19-22, 44-47, 65-66, 68-70, 73, 76, 79, 82, 88-89, 92-94, 97, 100-114 // These formats are omitted due to lack of a corresponding Vulkan format. D24_UNORM_S8_UINT (DXGI_FORMAT 45) @@ -228,49 +228,49 @@ enum class Format _Count, }; -struct FormatInfo +public struct FormatInfo { - GfxCount channelCount; ///< The amount of channels in the format. Only set if the channelType is set - uint8_t channelType; ///< One of SlangScalarType None if type isn't made up of elements of type. TODO: Change to uint32_t? + public GfxCount channelCount; ///< The amount of channels in the format. Only set if the channelType is set + public uint8_t channelType; ///< One of SlangScalarType None if type isn't made up of elements of type. TODO: Change to uint32_t? - Size blockSizeInBytes; ///< The size of a block in bytes. - GfxCount pixelsPerBlock; ///< The number of pixels contained in a block. - GfxCount blockWidth; ///< The width of a block in pixels. - GfxCount blockHeight; ///< The height of a block in pixels. + public Size blockSizeInBytes; ///< The size of a block in bytes. + public GfxCount pixelsPerBlock; ///< The number of pixels contained in a block. + public GfxCount blockWidth; ///< The width of a block in pixels. + public GfxCount blockHeight; ///< The height of a block in pixels. }; -enum class InputSlotClass +public enum class InputSlotClass { PerVertex, PerInstance }; -struct InputElementDesc +public struct InputElementDesc { - NativeString semanticName; ///< The name of the corresponding parameter in shader code. - GfxIndex semanticIndex; ///< The index of the corresponding parameter in shader code. Only needed if multiple parameters share a semantic name. - Format format; ///< The format of the data being fetched for this element. - Offset offset; ///< The offset in bytes of this element from the start of the corresponding chunk of vertex stream data. - GfxIndex bufferSlotIndex; ///< The index of the vertex stream to fetch this element's data from. + public NativeString semanticName; ///< The name of the corresponding parameter in shader code. + public GfxIndex semanticIndex; ///< The index of the corresponding parameter in shader code. Only needed if multiple parameters share a semantic name. + public Format format; ///< The format of the data being fetched for this element. + public Offset offset; ///< The offset in bytes of this element from the start of the corresponding chunk of vertex stream data. + public GfxIndex bufferSlotIndex; ///< The index of the vertex stream to fetch this element's data from. }; -struct VertexStreamDesc +public struct VertexStreamDesc { - Size stride; ///< The stride in bytes for this vertex stream. - InputSlotClass slotClass; ///< Whether the stream contains per-vertex or per-instance data. - GfxCount instanceDataStepRate; ///< How many instances to draw per chunk of data. + public Size stride; ///< The stride in bytes for this vertex stream. + public InputSlotClass slotClass; ///< Whether the stream contains per-vertex or per-instance data. + public GfxCount instanceDataStepRate; ///< How many instances to draw per chunk of data. }; -enum class PrimitiveType +public enum class PrimitiveType { Point, Line, Triangle, Patch }; -enum class PrimitiveTopology +public enum class PrimitiveTopology { TriangleList, TriangleStrip, PointList, LineList, LineStrip }; -enum class ResourceState +public enum class ResourceState { Undefined, General, @@ -295,19 +295,19 @@ enum class ResourceState _Count }; -struct ResourceStateSet +public struct ResourceStateSet { - uint64_t m_bitFields; + public uint64_t m_bitFields; [mutating] - void add(ResourceState state) { m_bitFields |= (1LL << (uint32_t)state); } + public void add(ResourceState state) { m_bitFields |= (1LL << (uint32_t)state); } - bool contains(ResourceState state) { return (m_bitFields & (1LL << (uint32_t)state)) != 0; } - __init() { m_bitFields = 0; } - __init(ResourceState state) { add(state); } + public bool contains(ResourceState state) { return (m_bitFields & (1LL << (uint32_t)state)) != 0; } + public __init() { m_bitFields = 0; } + public __init(ResourceState state) { add(state); } }; -ResourceStateSet operator &(ResourceStateSet val, ResourceStateSet that) +public ResourceStateSet operator &(ResourceStateSet val, ResourceStateSet that) { ResourceStateSet result; result.m_bitFields = val.m_bitFields & that.m_bitFields; @@ -315,14 +315,14 @@ ResourceStateSet operator &(ResourceStateSet val, ResourceStateSet that) } /// Describes how memory for the resource should be allocated for CPU access. -enum class MemoryType +public enum class MemoryType { DeviceLocal, Upload, ReadBack, }; -enum class InteropHandleAPI +public enum class InteropHandleAPI { Unknown, D3D12, // A D3D12 object pointer. @@ -334,29 +334,29 @@ enum class InteropHandleAPI D3D12CpuDescriptorHandle, // A D3D12_CPU_DESCRIPTOR_HANDLE value. }; -struct InteropHandle +public struct InteropHandle { - InteropHandleAPI api = InteropHandleAPI::Unknown; - uint64_t handleValue; + public InteropHandleAPI api = InteropHandleAPI::Unknown; + public uint64_t handleValue; }; // Declare opaque type -struct InputLayoutDesc +public struct InputLayoutDesc { - InputElementDesc *inputElements; - GfxCount inputElementCount; - VertexStreamDesc *vertexStreams; - GfxCount vertexStreamCount; + public InputElementDesc *inputElements; + public GfxCount inputElementCount; + public VertexStreamDesc *vertexStreams; + public GfxCount vertexStreamCount; }; [COM("45223711-a84b-455c-befa-4937421e8e2e")] -interface IInputLayout +public interface IInputLayout { }; /// The type of resource. /// NOTE! The order needs to be such that all texture types are at or after Texture1D (otherwise isTexture won't work correctly) -enum class ResourceType +public enum class ResourceType { Unknown, ///< Unknown Buffer, ///< A buffer (like a constant/index/vertex buffer) @@ -368,86 +368,86 @@ enum class ResourceType }; /// Base class for Descs -struct ResourceDescBase +public struct ResourceDescBase { - ResourceType type; - ResourceState defaultState; - ResourceStateSet allowedStates; - MemoryType memoryType; - InteropHandle existingHandle; - bool isShared; + public ResourceType type; + public ResourceState defaultState; + public ResourceStateSet allowedStates; + public MemoryType memoryType; + public InteropHandle existingHandle; + public bool isShared; }; [COM("a0e39f34-8398-4522-95c2-ebc0f984ef3f")] -interface IResource +public interface IResource { - ResourceType getType(); - Result getNativeResourceHandle(out InteropHandle outHandle); - Result getSharedHandle(out InteropHandle outHandle); - Result setDebugName(NativeString name); - NativeString getDebugName(); + public ResourceType getType(); + public Result getNativeResourceHandle(out InteropHandle outHandle); + public Result getSharedHandle(out InteropHandle outHandle); + public Result setDebugName(NativeString name); + public NativeString getDebugName(); }; -struct MemoryRange +public struct MemoryRange { // TODO: Change to Offset/Size? - uint64_t offset; - uint64_t size; + public uint64_t offset; + public uint64_t size; }; -struct BufferResourceDesc : ResourceDescBase +public struct BufferResourceDesc : ResourceDescBase { - Size sizeInBytes = 0; ///< Total size in bytes - Size elementSize = 0; ///< Get the element stride. If > 0, this is a structured buffer - Format format = Format::Unknown; + public Size sizeInBytes = 0; ///< Total size in bytes + public Size elementSize = 0; ///< Get the element stride. If > 0, this is a structured buffer + public Format format = Format::Unknown; }; [COM("1b274efe-5e37-492b-826e-7ee7e8f5a49b")] -interface IBufferResource : IResource +public interface IBufferResource : IResource { - BufferResourceDesc *getDesc(); - DeviceAddress getDeviceAddress(); - Result map(MemoryRange* rangeToRead, void** outPointer); - Result unmap(MemoryRange* writtenRange); + public BufferResourceDesc *getDesc(); + public DeviceAddress getDeviceAddress(); + public Result map(MemoryRange *rangeToRead, void **outPointer); + public Result unmap(MemoryRange* writtenRange); }; -struct DepthStencilClearValue +public struct DepthStencilClearValue { - float depth = 1.0f; - uint32_t stencil = 0; + public float depth = 1.0f; + public uint32_t stencil = 0; }; -struct ColorClearValue +public struct ColorClearValue { - float4 values; + public float4 values; [mutating] - void setValue(uint4 uintVal) + public void setValue(uint4 uintVal) { values = reinterpret(uintVal); } [mutating] - void setValue(float4 floatVal) + public void setValue(float4 floatVal) { values = floatVal; } }; -struct ClearValue +public struct ClearValue { - ColorClearValue color; - DepthStencilClearValue depthStencil; + public ColorClearValue color; + public DepthStencilClearValue depthStencil; }; -struct BufferRange +public struct BufferRange { // TODO: Change to Index and Count? - uint64_t firstElement; - uint64_t elementCount; + public uint64_t firstElement; + public uint64_t elementCount; }; -enum class TextureAspect : uint32_t +public enum class TextureAspect : uint32_t { Default = 0, Color = 0x00000001, @@ -461,32 +461,32 @@ enum class TextureAspect : uint32_t DepthStencil = 0x6, }; -struct SubresourceRange +public struct SubresourceRange { - TextureAspect aspectMask; - GfxIndex mipLevel; - GfxCount mipLevelCount; - GfxIndex baseArrayLayer; // For Texture3D, this is WSlice. - GfxCount layerCount; // For cube maps, this is a multiple of 6. + public TextureAspect aspectMask; + public GfxIndex mipLevel; + public GfxCount mipLevelCount; + public GfxIndex baseArrayLayer; // For Texture3D, this is WSlice. + public GfxCount layerCount; // For cube maps, this is a multiple of 6. }; -static const Size kRemainingTextureSize = 0xFFFFFFFF; -struct TextureResourceSampleDesc +public static const Size kRemainingTextureSize = 0xFFFFFFFF; +public struct TextureResourceSampleDesc { - GfxCount numSamples; ///< Number of samples per pixel - int quality; ///< The quality measure for the samples + public GfxCount numSamples; ///< Number of samples per pixel + public int quality; ///< The quality measure for the samples }; -struct TextureResourceDesc : ResourceDescBase +public struct TextureResourceDesc : ResourceDescBase { - int3 size; + public int3 size; - GfxCount arraySize = 0; ///< Array size + public GfxCount arraySize = 0; ///< Array size - GfxCount numMipLevels = 0; ///< Number of mip levels - if 0 will create all mip levels - Format format; ///< The resources format - TextureResourceSampleDesc sampleDesc; ///< How the resource is sampled - ClearValue* optimalClearValue; + public GfxCount numMipLevels = 0; ///< Number of mip levels - if 0 will create all mip levels + public Format format; ///< The resources format + public TextureResourceSampleDesc sampleDesc; ///< How the resource is sampled + public ClearValue* optimalClearValue; }; /// Data for a single subresource of a texture. @@ -508,10 +508,10 @@ struct TextureResourceDesc : ResourceDescBase /// the index of a subresoruce for mip level `m` and array /// index `a` is `m + a*mipLevelCount`. /// -struct SubresourceData +public struct SubresourceData { /// Pointer to texel data for the subresource tensor. - void *data; + public void *data; /// Stride in bytes between rows of the subresource tensor. /// @@ -521,7 +521,7 @@ struct SubresourceData /// Devices may not support all possible values for `strideY`. /// In particular, they may only support strictly positive strides. /// - gfx::Size strideY; + public gfx::Size strideY; /// Stride in bytes between layers of the subresource tensor. /// @@ -531,16 +531,16 @@ struct SubresourceData /// Devices may not support all possible values for `strideZ`. /// In particular, they may only support strictly positive strides. /// - gfx::Size strideZ; + public gfx::Size strideZ; }; [COM("cf88a31c-6187-46c5-a4b7-eb-58-c7-33-40-17")] -interface ITextureResource : IResource +public interface ITextureResource : IResource { - TextureResourceDesc* getDesc(); + public TextureResourceDesc* getDesc(); }; -enum class ComparisonFunc : uint8_t +public enum class ComparisonFunc : uint8_t { Never = 0x0, Less = 0x1, @@ -552,13 +552,13 @@ enum class ComparisonFunc : uint8_t Always = 0x7, }; -enum class TextureFilteringMode +public enum class TextureFilteringMode { Point, Linear, }; -enum class TextureAddressingMode +public enum class TextureAddressingMode { Wrap, ClampToEdge, @@ -567,7 +567,7 @@ enum class TextureAddressingMode MirrorOnce, }; -enum class TextureReductionOp +public enum class TextureReductionOp { Average, Comparison, @@ -575,22 +575,22 @@ enum class TextureReductionOp Maximum, }; -struct SamplerStateDesc -{ - TextureFilteringMode minFilter; - TextureFilteringMode magFilter; - TextureFilteringMode mipFilter; - TextureReductionOp reductionOp; - TextureAddressingMode addressU; - TextureAddressingMode addressV; - TextureAddressingMode addressW; - float mipLODBias; - uint32_t maxAnisotropy; - ComparisonFunc comparisonFunc; - float4 borderColor; - float minLOD; - float maxLOD; - __init() +public struct SamplerStateDesc +{ + public TextureFilteringMode minFilter; + public TextureFilteringMode magFilter; + public TextureFilteringMode mipFilter; + public TextureReductionOp reductionOp; + public TextureAddressingMode addressU; + public TextureAddressingMode addressV; + public TextureAddressingMode addressW; + public float mipLODBias; + public uint32_t maxAnisotropy; + public ComparisonFunc comparisonFunc; + public float4 borderColor; + public float minLOD; + public float maxLOD; + public __init() { minFilter = TextureFilteringMode::Linear; magFilter = TextureFilteringMode::Linear; @@ -609,15 +609,15 @@ struct SamplerStateDesc }; [COM("8b8055df-9377-401d-91ff-3f-a3-bf-66-64-f4")] -interface ISamplerState +public interface ISamplerState { /// Returns a native API handle representing this sampler state object. /// When using D3D12, this will be a D3D12_CPU_DESCRIPTOR_HANDLE. /// When using Vulkan, this will be a VkSampler. - Result getNativeHandle(InteropHandle *outNativeHandle); + public Result getNativeHandle(InteropHandle *outNativeHandle); }; -enum class ResourceViewType +public enum class ResourceViewType { Unknown, @@ -630,49 +630,49 @@ enum class ResourceViewType CountOf_, }; -struct RenderTargetDesc +public struct RenderTargetDesc { // The resource shape of this render target view. - ResourceType shape; + public ResourceType shape; }; -struct ResourceViewDesc +public struct ResourceViewDesc { - ResourceViewType type; - Format format; + public ResourceViewType type; + public Format format; // Required fields for `RenderTarget` and `DepthStencil` views. - RenderTargetDesc renderTarget; + public RenderTargetDesc renderTarget; // Specifies the range of a texture resource for a ShaderRsource/UnorderedAccess/RenderTarget/DepthStencil view. - SubresourceRange subresourceRange; + public SubresourceRange subresourceRange; // Specifies the range of a buffer resource for a ShaderResource/UnorderedAccess view. - BufferRange bufferRange; + public BufferRange bufferRange; // Specifies the element size in bytes of a structured buffer. Pass 0 for a raw buffer view. - Size bufferElementSize; + public Size bufferElementSize; }; [COM("7b6c4926-0884-408c-ad8a-50-3a-8e-23-98-a4")] -interface IResourceView +public interface IResourceView { - ResourceViewDesc* getViewDesc(); + public ResourceViewDesc* getViewDesc(); /// Returns a native API handle representing this resource view object. /// When using D3D12, this will be a D3D12_CPU_DESCRIPTOR_HANDLE or a buffer device address depending /// on the type of the resource view. /// When using Vulkan, this will be a VkImageView, VkBufferView, VkAccelerationStructure or a VkBuffer /// depending on the type of the resource view. - Result getNativeHandle(InteropHandle *outNativeHandle); + public Result getNativeHandle(InteropHandle *outNativeHandle); }; -enum class AccelerationStructureKind +public enum class AccelerationStructureKind { TopLevel, BottomLevel }; -// The enum values are intentionally consistent with +// The public enum values are intentionally consistent with // D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS. -enum AccelerationStructureBuildFlags +public enum AccelerationStructureBuildFlags { None, AllowUpdate = 1, @@ -683,16 +683,16 @@ enum AccelerationStructureBuildFlags PerformUpdate = 32 }; -enum class GeometryType +public enum class GeometryType { Triangles, ProcedurePrimitives }; -struct GeometryFlags +public struct GeometryFlags { - // The enum values are intentionally consistent with + // The public enum values are intentionally consistent with // D3D12_RAYTRACING_GEOMETRY_FLAGS. - enum Enum + public enum Enum { None, Opaque = 1, @@ -700,55 +700,55 @@ struct GeometryFlags }; }; -struct TriangleDesc +public struct TriangleDesc { - DeviceAddress transform3x4; - Format indexFormat; - Format vertexFormat; - GfxCount indexCount; - GfxCount vertexCount; - DeviceAddress indexData; - DeviceAddress vertexData; - Size vertexStride; + public DeviceAddress transform3x4; + public Format indexFormat; + public Format vertexFormat; + public GfxCount indexCount; + public GfxCount vertexCount; + public DeviceAddress indexData; + public DeviceAddress vertexData; + public Size vertexStride; }; -struct ProceduralAABB +public struct ProceduralAABB { - float minX; - float minY; - float minZ; - float maxX; - float maxY; - float maxZ; + public float minX; + public float minY; + public float minZ; + public float maxX; + public float maxY; + public float maxZ; }; -struct ProceduralAABBDesc +public struct ProceduralAABBDesc { /// Number of AABBs. - GfxCount count; + public GfxCount count; /// Pointer to an array of `ProceduralAABB` values in device memory. - DeviceAddress data; + public DeviceAddress data; /// Stride in bytes of the AABB values array. - Size stride; + public Size stride; }; -struct GeometryDesc +public struct GeometryDesc { - GeometryType type; - GeometryFlags::Enum flags; - TriangleDesc triangles; - property ProceduralAABBDesc proceduralAABBs + public GeometryType type; + public GeometryFlags::Enum flags; + public TriangleDesc triangles; + public property ProceduralAABBDesc proceduralAABBs { get { return reinterpret(triangles); } set { triangles = reinterpret(newValue); } } }; -// The enum values are kept consistent with D3D12_RAYTRACING_INSTANCE_FLAGS +// The public enum values are kept consistent with D3D12_RAYTRACING_INSTANCE_FLAGS // and VkGeometryInstanceFlagBitsKHR. -enum GeometryInstanceFlags +public enum GeometryInstanceFlags { None = 0, TriangleFacingCullDisable = 0x00000001, @@ -758,141 +758,141 @@ enum GeometryInstanceFlags }; // TODO: Should any of these be changed? -// The layout of this struct is intentionally consistent with D3D12_RAYTRACING_INSTANCE_DESC +// The layout of this public struct is intentionally consistent with D3D12_RAYTRACING_INSTANCE_DESC // and VkAccelerationStructureInstanceKHR. -struct InstanceDesc +public struct InstanceDesc { - float transform[3][4]; - uint32_t instanceID24_mask8; - property uint32_t instanceID { get { return instanceID24_mask8 & 0xFFFFFF; } set { instanceID24_mask8 = (instanceID24_mask8 & 0xFF000000) | (newValue & 0xFFFFFF); } } - property uint32_t instanceMask { get { return instanceID24_mask8 >> 24; } set { instanceID24_mask8 = (newValue << 24) | (instanceID24_mask8 & 0x00FFFFFF); } } + public float transform[3][4]; + public uint32_t instanceID24_mask8; + public property uint32_t instanceID { get { return instanceID24_mask8 & 0xFFFFFF; } set { instanceID24_mask8 = (instanceID24_mask8 & 0xFF000000) | (newValue & 0xFFFFFF); } } + public property uint32_t instanceMask { get { return instanceID24_mask8 >> 24; } set { instanceID24_mask8 = (newValue << 24) | (instanceID24_mask8 & 0x00FFFFFF); } } - uint32_t instanceContributionToHitGroupIndex24_flags8; - property uint32_t instanceContributionToHitGroupIndex + public uint32_t instanceContributionToHitGroupIndex24_flags8; + public property uint32_t instanceContributionToHitGroupIndex { get { return instanceContributionToHitGroupIndex24_flags8 & 0xFFFFFF; } set { instanceContributionToHitGroupIndex24_flags8 = (instanceContributionToHitGroupIndex24_flags8 & 0xFF000000) | (newValue & 0xFFFFFF); } } - property GeometryInstanceFlags flags + public property GeometryInstanceFlags flags { get { return (GeometryInstanceFlags)(instanceContributionToHitGroupIndex24_flags8 >> 24); } set { instanceContributionToHitGroupIndex24_flags8 = ((uint32_t)newValue << 24) | (instanceContributionToHitGroupIndex24_flags8 & 0x00FFFFFF); } } - DeviceAddress accelerationStructure; + public DeviceAddress accelerationStructure; }; -struct AccelerationStructurePrebuildInfo +public struct AccelerationStructurePrebuildInfo { - Size resultDataMaxSize; - Size scratchDataSize; - Size updateScratchDataSize; + public Size resultDataMaxSize; + public Size scratchDataSize; + public Size updateScratchDataSize; }; -struct AccelerationStructureBuildInputs +public struct AccelerationStructureBuildInputs { - AccelerationStructureKind kind; + public AccelerationStructureKind kind; - AccelerationStructureBuildFlags flags; + public AccelerationStructureBuildFlags flags; - GfxCount descCount; + public GfxCount descCount; /// Array of `InstanceDesc` values in device memory. /// Used when `kind` is `TopLevel`. - DeviceAddress instanceDescs; + public DeviceAddress instanceDescs; /// Array of `GeometryDesc` values. /// Used when `kind` is `BottomLevel`. - GeometryDesc *geometryDescs; + public GeometryDesc *geometryDescs; }; -struct AccelerationStructureCreateDesc +public struct AccelerationStructureCreateDesc { - AccelerationStructureKind kind; - NativeRef buffer; - Offset offset; - Size size; + public AccelerationStructureKind kind; + public NativeRef buffer; + public Offset offset; + public Size size; }; -struct AccelerationStructureBuildDesc +public struct AccelerationStructureBuildDesc { - AccelerationStructureBuildInputs inputs; - NativeRef source; - NativeRef dest; - DeviceAddress scratchData; + public AccelerationStructureBuildInputs inputs; + public NativeRef source; + public NativeRef dest; + public DeviceAddress scratchData; }; [COM("a5cdda3c-1d4e-4df7-8ef2-b7-3f-ce-04-de-3b")] -interface IAccelerationStructure : IResourceView +public interface IAccelerationStructure : IResourceView { - DeviceAddress getDeviceAddress(); + public DeviceAddress getDeviceAddress(); }; -struct FenceDesc +public struct FenceDesc { - uint64_t initialValue; - bool isShared; + public uint64_t initialValue; + public bool isShared; }; [COM("7fe1c283-d3f4-48ed-aaf3-01-51-96-4e-7c-b5")] -interface IFence +public interface IFence { /// Returns the currently signaled value on the device. - Result getCurrentValue(uint64_t *outValue); + public Result getCurrentValue(uint64_t *outValue); /// Signals the fence from the host with the specified value. - Result setCurrentValue(uint64_t value); + public Result setCurrentValue(uint64_t value); - Result getSharedHandle(InteropHandle *outHandle); - Result getNativeHandle(InteropHandle *outNativeHandle); + public Result getSharedHandle(InteropHandle *outHandle); + public Result getNativeHandle(InteropHandle *outNativeHandle); }; -struct ShaderOffset +public struct ShaderOffset { - Int uniformOffset = 0; // TODO: Change to Offset? - GfxIndex bindingRangeIndex = 0; - GfxIndex bindingArrayIndex = 0; + public Int uniformOffset = 0; // TODO: Change to Offset? + public GfxIndex bindingRangeIndex = 0; + public GfxIndex bindingArrayIndex = 0; } -enum class ShaderObjectContainerType +public enum class ShaderObjectContainerType { None, Array, StructuredBuffer }; [COM("c1fa997e-5ca2-45ae-9bcb-c4-35-9e-85-05-85")] -interface IShaderObject -{ - slang::TypeLayoutReflection* getElementTypeLayout(); - ShaderObjectContainerType getContainerType(); - GfxCount getEntryPointCount(); - Result getEntryPoint(GfxIndex index, out Optional entryPoint); - Result setData(ShaderOffset *offset, void *data, Size size); - Result getObject(ShaderOffset *offset, out Optional object); - Result setObject(ShaderOffset* offset, IShaderObject object); - Result setResource(ShaderOffset* offset, IResourceView resourceView); - Result setSampler(ShaderOffset* offset, ISamplerState sampler); - Result setCombinedTextureSampler(ShaderOffset* offset, IResourceView textureView, ISamplerState sampler); +public interface IShaderObject +{ + public slang::TypeLayoutReflection* getElementTypeLayout(); + public ShaderObjectContainerType getContainerType(); + public GfxCount getEntryPointCount(); + public Result getEntryPoint(GfxIndex index, out Optional entryPoint); + public Result setData(ShaderOffset *offset, void *data, Size size); + public Result getObject(ShaderOffset *offset, out Optional object); + public Result setObject(ShaderOffset* offset, IShaderObject object); + public Result setResource(ShaderOffset* offset, IResourceView resourceView); + public Result setSampler(ShaderOffset* offset, ISamplerState sampler); + public Result setCombinedTextureSampler(ShaderOffset* offset, IResourceView textureView, ISamplerState sampler); /// Manually overrides the specialization argument for the sub-object binding at `offset`. /// Specialization arguments are passed to the shader compiler to specialize the type /// of interface-typed shader parameters. - Result setSpecializationArgs( + public Result setSpecializationArgs( ShaderOffset* offset, slang::SpecializationArg *args, GfxCount count); - Result getCurrentVersion( + public Result getCurrentVersion( ITransientResourceHeap transientHeap, out IShaderObject outObject); - void* getRawData(); + public void* getRawData(); - Size getSize(); + public Size getSize(); /// Use the provided constant buffer instead of the internally created one. - Result setConstantBufferOverride(IBufferResource constantBuffer); + public Result setConstantBufferOverride(IBufferResource constantBuffer); }; -enum class StencilOp : uint8_t +public enum class StencilOp : uint8_t { Keep, Zero, @@ -904,32 +904,32 @@ enum class StencilOp : uint8_t DecrementWrap, }; -enum class FillMode : uint8_t +public enum class FillMode : uint8_t { Solid, Wireframe, }; -enum class CullMode : uint8_t +public enum class CullMode : uint8_t { None, Front, Back, }; -enum class FrontFaceMode : uint8_t +public enum class FrontFaceMode : uint8_t { CounterClockwise, Clockwise, }; -struct DepthStencilOpDesc +public struct DepthStencilOpDesc { - StencilOp stencilFailOp = StencilOp::Keep; - StencilOp stencilDepthFailOp = StencilOp::Keep; - StencilOp stencilPassOp = StencilOp::Keep; - ComparisonFunc stencilFunc = ComparisonFunc::Always; - __init() + public StencilOp stencilFailOp = StencilOp::Keep; + public StencilOp stencilDepthFailOp = StencilOp::Keep; + public StencilOp stencilPassOp = StencilOp::Keep; + public ComparisonFunc stencilFunc = ComparisonFunc::Always; + public __init() { stencilFailOp = StencilOp::Keep; stencilDepthFailOp = StencilOp::Keep; @@ -938,21 +938,21 @@ struct DepthStencilOpDesc } }; -struct DepthStencilDesc +public struct DepthStencilDesc { - bool depthTestEnable = false; - bool depthWriteEnable = true; - ComparisonFunc depthFunc = ComparisonFunc::Less; + public bool depthTestEnable = false; + public bool depthWriteEnable = true; + public ComparisonFunc depthFunc = ComparisonFunc::Less; - bool stencilEnable = false; - uint32_t stencilReadMask = 0xFFFFFFFF; - uint32_t stencilWriteMask = 0xFFFFFFFF; - DepthStencilOpDesc frontFace; - DepthStencilOpDesc backFace; + public bool stencilEnable = false; + public uint32_t stencilReadMask = 0xFFFFFFFF; + public uint32_t stencilWriteMask = 0xFFFFFFFF; + public DepthStencilOpDesc frontFace; + public DepthStencilOpDesc backFace; - uint32_t stencilRef = 0; + public uint32_t stencilRef = 0; - __init() + public __init() { depthTestEnable = false; depthWriteEnable = true; @@ -964,22 +964,22 @@ struct DepthStencilDesc } }; -struct RasterizerDesc +public struct RasterizerDesc { - FillMode fillMode = FillMode::Solid; - CullMode cullMode = CullMode::None; - FrontFaceMode frontFace = FrontFaceMode::CounterClockwise; - int32_t depthBias = 0; - float depthBiasClamp = 0.0f; - float slopeScaledDepthBias = 0.0f; - bool depthClipEnable = true; - bool scissorEnable = false; - bool multisampleEnable = false; - bool antialiasedLineEnable = false; - bool enableConservativeRasterization = false; - uint32_t forcedSampleCount = 0; + public FillMode fillMode = FillMode::Solid; + public CullMode cullMode = CullMode::None; + public FrontFaceMode frontFace = FrontFaceMode::CounterClockwise; + public int32_t depthBias = 0; + public float depthBiasClamp = 0.0f; + public float slopeScaledDepthBias = 0.0f; + public bool depthClipEnable = true; + public bool scissorEnable = false; + public bool multisampleEnable = false; + public bool antialiasedLineEnable = false; + public bool enableConservativeRasterization = false; + public uint32_t forcedSampleCount = 0; - __init() + public __init() { fillMode = FillMode::Solid; cullMode = CullMode::None; @@ -996,12 +996,12 @@ struct RasterizerDesc } }; -enum class LogicOp +public enum class LogicOp { NoOp, }; -enum class BlendOp +public enum class BlendOp { Add, Subtract, @@ -1010,7 +1010,7 @@ enum class BlendOp Max, }; -enum class BlendFactor +public enum class BlendFactor { Zero, One, @@ -1031,7 +1031,7 @@ enum class BlendFactor InvSecondarySrcAlpha, }; -enum RenderTargetWriteMask +public enum RenderTargetWriteMask { EnableNone = 0, EnableRed = 0x01, @@ -1041,11 +1041,11 @@ enum RenderTargetWriteMask EnableAll = 0x0F, }; -struct AspectBlendDesc +public struct AspectBlendDesc { - BlendFactor srcFactor = BlendFactor::One; - BlendFactor dstFactor = BlendFactor::Zero; - BlendOp op = BlendOp::Add; + public BlendFactor srcFactor = BlendFactor::One; + public BlendFactor dstFactor = BlendFactor::Zero; + public BlendOp op = BlendOp::Add; __init() { @@ -1054,14 +1054,14 @@ struct AspectBlendDesc } }; -struct TargetBlendDesc +public struct TargetBlendDesc { - AspectBlendDesc color; - AspectBlendDesc alpha; - bool enableBlend; - LogicOp logicOp; - RenderTargetWriteMask writeMask; - __init() + public AspectBlendDesc color; + public AspectBlendDesc alpha; + public bool enableBlend; + public LogicOp logicOp; + public RenderTargetWriteMask writeMask; + public __init() { enableBlend = false; logicOp = LogicOp::NoOp; @@ -1069,168 +1069,168 @@ struct TargetBlendDesc } }; -struct BlendDesc +public struct BlendDesc { - TargetBlendDesc targets[kMaxRenderTargetCount]; - GfxCount targetCount; + public TargetBlendDesc targets[kMaxRenderTargetCount]; + public GfxCount targetCount; - bool alphaToCoverageEnable; + public bool alphaToCoverageEnable; }; -struct FramebufferTargetLayout +public struct FramebufferTargetLayout { - Format format; - GfxCount sampleCount; + public Format format; + public GfxCount sampleCount; }; -struct FramebufferLayoutDesc +public struct FramebufferLayoutDesc { - GfxCount renderTargetCount; - FramebufferTargetLayout *renderTargets; - FramebufferTargetLayout *depthStencil; + public GfxCount renderTargetCount; + public FramebufferTargetLayout *renderTargets; + public FramebufferTargetLayout *depthStencil; }; [COM("0a838785-c13a-4832-ad88-64-06-b5-4b-5e-ba")] -interface IFramebufferLayout +public interface IFramebufferLayout { }; -struct GraphicsPipelineStateDesc +public struct GraphicsPipelineStateDesc { - NativeRef program; + public NativeRef program; - NativeRef inputLayout; - NativeRef framebufferLayout; - PrimitiveType primitiveType; - DepthStencilDesc depthStencil; - RasterizerDesc rasterizer; - BlendDesc blend; + public NativeRef inputLayout; + public NativeRef framebufferLayout; + public PrimitiveType primitiveType; + public DepthStencilDesc depthStencil; + public RasterizerDesc rasterizer; + public BlendDesc blend; - __init() + public __init() { primitiveType = PrimitiveType::Triangle; } }; -struct ComputePipelineStateDesc +public struct ComputePipelineStateDesc { - NativeRef program; - void *d3d12RootSignatureOverride; + public NativeRef program; + public void *d3d12RootSignatureOverride; }; -enum RayTracingPipelineFlags +public enum RayTracingPipelineFlags { None = 0, SkipTriangles = 1, SkipProcedurals = 2, }; -struct HitGroupDesc +public struct HitGroupDesc { - NativeString hitGroupName; - NativeString closestHitEntryPoint; - NativeString anyHitEntryPoint; - NativeString intersectionEntryPoint; + public NativeString hitGroupName; + public NativeString closestHitEntryPoint; + public NativeString anyHitEntryPoint; + public NativeString intersectionEntryPoint; }; -struct RayTracingPipelineStateDesc +public struct RayTracingPipelineStateDesc { - NativeRef program; - GfxCount hitGroupCount = 0; - HitGroupDesc *hitGroups; - int maxRecursion = 0; - Size maxRayPayloadSize = 0; - Size maxAttributeSizeInBytes = 8; - RayTracingPipelineFlags flags = RayTracingPipelineFlags::None; + public NativeRef program; + public GfxCount hitGroupCount = 0; + public HitGroupDesc *hitGroups; + public int maxRecursion = 0; + public Size maxRayPayloadSize = 0; + public Size maxAttributeSizeInBytes = 8; + public RayTracingPipelineFlags flags = RayTracingPipelineFlags::None; }; // Specifies the bytes to overwrite into a record in the shader table. -struct ShaderRecordOverwrite +public struct ShaderRecordOverwrite { - Offset offset; // Offset within the shader record. - Size size; // Number of bytes to overwrite. - uint8_t data[8]; // Content to overwrite. + public Offset offset; // Offset within the shader record. + public Size size; // Number of bytes to overwrite. + public uint8_t data[8]; // Content to overwrite. }; -struct ShaderTableDesc +public struct ShaderTableDesc { - GfxCount rayGenShaderCount; - NativeString* rayGenShaderEntryPointNames; - ShaderRecordOverwrite *rayGenShaderRecordOverwrites; + public GfxCount rayGenShaderCount; + public NativeString* rayGenShaderEntryPointNames; + public ShaderRecordOverwrite *rayGenShaderRecordOverwrites; - GfxCount missShaderCount; - NativeString* missShaderEntryPointNames; - ShaderRecordOverwrite *missShaderRecordOverwrites; + public GfxCount missShaderCount; + public NativeString *missShaderEntryPointNames; + public ShaderRecordOverwrite *missShaderRecordOverwrites; - GfxCount hitGroupCount; - NativeString* hitGroupNames; - ShaderRecordOverwrite *hitGroupRecordOverwrites; + public GfxCount hitGroupCount; + public NativeString *hitGroupNames; + public ShaderRecordOverwrite *hitGroupRecordOverwrites; NativeRef program; }; [COM("a721522c-df31-4c2f-a5e7-3b-e0-12-4b-31-78")] -interface IShaderTable +public interface IShaderTable { }; [COM("0ca7e57d-8a90-44f3-bdb1-fe-9b-35-3f-5a-72")] -interface IPipelineState +public interface IPipelineState { Result getNativeHandle(InteropHandle *outHandle); }; -struct ScissorRect +public struct ScissorRect { - int32_t minX; - int32_t minY; - int32_t maxX; - int32_t maxY; + public int32_t minX; + public int32_t minY; + public int32_t maxX; + public int32_t maxY; }; -struct Viewport +public struct Viewport { - float originX = 0.0f; - float originY = 0.0f; - float extentX = 0.0f; - float extentY = 0.0f; - float minZ = 0.0f; - float maxZ = 1.0f; + public float originX = 0.0f; + public float originY = 0.0f; + public float extentX = 0.0f; + public float extentY = 0.0f; + public float minZ = 0.0f; + public float maxZ = 1.0f; }; -struct FramebufferDesc +public struct FramebufferDesc { - GfxCount renderTargetCount; - NativeRef *renderTargetViews; - NativeRef depthStencilView; - NativeRef layout; + public GfxCount renderTargetCount; + public NativeRef *renderTargetViews; + public NativeRef depthStencilView; + public NativeRef layout; }; [COM("0f0c0d9a-4ef3-4e18-9ba9-34-60-ea-69-87-95")] -interface IFramebuffer +public interface IFramebuffer { }; -enum class WindowHandleType +public enum class WindowHandleType { Unknown, Win32Handle, XLibHandle, }; -struct WindowHandle +public struct WindowHandle { - WindowHandleType type; - void* handleValues[2]; - static WindowHandle fromHwnd(void *hwnd) + public WindowHandleType type; + public void* handleValues[2]; + public static WindowHandle fromHwnd(void *hwnd) { WindowHandle handle = {}; handle.type = WindowHandleType::Win32Handle; handle.handleValues[0] = hwnd; return handle; } - static WindowHandle fromXWindow(void *xdisplay, uint32_t xwindow) + public static WindowHandle fromXWindow(void *xdisplay, uint32_t xwindow) { WindowHandle handle = {}; handle.type = WindowHandleType::XLibHandle; @@ -1240,42 +1240,42 @@ struct WindowHandle } }; -enum FaceMask +public enum FaceMask { Front = 1, Back = 2 }; -enum class TargetLoadOp +public enum class TargetLoadOp { Load, Clear, DontCare }; -enum class TargetStoreOp +public enum class TargetStoreOp { Store, DontCare }; -struct TargetAccessDesc +public struct TargetAccessDesc { - TargetLoadOp loadOp; - TargetLoadOp stencilLoadOp; - TargetStoreOp storeOp; - TargetStoreOp stencilStoreOp; - ResourceState initialState; - ResourceState finalState; + public TargetLoadOp loadOp; + public TargetLoadOp stencilLoadOp; + public TargetStoreOp storeOp; + public TargetStoreOp stencilStoreOp; + public ResourceState initialState; + public ResourceState finalState; }; -struct RenderPassLayoutDesc +public struct RenderPassLayoutDesc { - NativeRef framebufferLayout; - GfxCount renderTargetCount; - TargetAccessDesc *renderTargetAccess; - TargetAccessDesc *depthStencilAccess; + public NativeRef framebufferLayout; + public GfxCount renderTargetCount; + public TargetAccessDesc *renderTargetAccess; + public TargetAccessDesc *depthStencilAccess; }; [COM("daab0b1a-f45d-4ae9-bf2c-e0-bb-76-7d-fa-d1")] -interface IRenderPassLayout +public interface IRenderPassLayout { }; -enum class QueryType +public enum class QueryType { Timestamp, AccelerationStructureCompactedSize, @@ -1283,57 +1283,57 @@ enum class QueryType AccelerationStructureCurrentSize, }; -struct QueryPoolDesc +public struct QueryPoolDesc { - QueryType type; - GfxCount count; + public QueryType type; + public GfxCount count; }; [COM("c2cc3784-12da-480a-a874-8b-31-96-1c-a4-36")] -interface IQueryPool +public interface IQueryPool { - Result getResult(GfxIndex queryIndex, GfxCount count, uint64_t *data); - Result reset(); + public Result getResult(GfxIndex queryIndex, GfxCount count, uint64_t *data); + public Result reset(); }; [COM("77ea6383-be3d-40aa-8b45-fd-f0-d7-5b-fa-34")] -interface ICommandEncoder +public interface ICommandEncoder { - void endEncoding(); - void writeTimestamp(IQueryPool queryPool, GfxIndex queryIndex); + public void endEncoding(); + public void writeTimestamp(IQueryPool queryPool, GfxIndex queryIndex); }; -struct IndirectDispatchArguments +public struct IndirectDispatchArguments { - GfxCount ThreadGroupCountX; - GfxCount ThreadGroupCountY; - GfxCount ThreadGroupCountZ; + public GfxCount ThreadGroupCountX; + public GfxCount ThreadGroupCountY; + public GfxCount ThreadGroupCountZ; }; -struct IndirectDrawArguments +public struct IndirectDrawArguments { - GfxCount VertexCountPerInstance; - GfxCount InstanceCount; - GfxIndex StartVertexLocation; - GfxIndex StartInstanceLocation; + public GfxCount VertexCountPerInstance; + public GfxCount InstanceCount; + public GfxIndex StartVertexLocation; + public GfxIndex StartInstanceLocation; }; -struct IndirectDrawIndexedArguments +public struct IndirectDrawIndexedArguments { - GfxCount IndexCountPerInstance; - GfxCount InstanceCount; - GfxIndex StartIndexLocation; - GfxIndex BaseVertexLocation; - GfxIndex StartInstanceLocation; + public GfxCount IndexCountPerInstance; + public GfxCount InstanceCount; + public GfxIndex StartIndexLocation; + public GfxIndex BaseVertexLocation; + public GfxIndex StartInstanceLocation; }; -struct SamplePosition +public struct SamplePosition { - int8_t x; - int8_t y; + public int8_t x; + public int8_t y; }; -enum ClearResourceViewFlags +public enum ClearResourceViewFlags { None = 0, ClearDepth = 1, @@ -1342,9 +1342,9 @@ enum ClearResourceViewFlags }; [COM("F99A00E9-ED50-4088-8A0E-3B26755031EA")] -interface IResourceCommandEncoder : ICommandEncoder +public interface IResourceCommandEncoder : ICommandEncoder { - void copyBuffer( + public void copyBuffer( IBufferResource dst, Offset dstOffset, IBufferResource src, @@ -1353,7 +1353,7 @@ interface IResourceCommandEncoder : ICommandEncoder /// Copies texture from src to dst. If dstSubresource and srcSubresource has mipLevelCount = 0 /// and layerCount = 0, the entire resource is being copied and dstOffset, srcOffset and extent /// arguments are ignored. - void copyTexture( + public void copyTexture( ITextureResource dst, ResourceState dstState, SubresourceRange dstSubresource, @@ -1365,7 +1365,7 @@ interface IResourceCommandEncoder : ICommandEncoder int3 extent); /// Copies texture to a buffer. Each row is aligned to kTexturePitchAlignment. - void copyTextureToBuffer( + public void copyTextureToBuffer( IBufferResource dst, Offset dstOffset, Size dstSize, @@ -1375,89 +1375,89 @@ interface IResourceCommandEncoder : ICommandEncoder SubresourceRange srcSubresource, int3 srcOffset, int3 extent); - void uploadTextureData( + public void uploadTextureData( ITextureResource dst, SubresourceRange subResourceRange, int3 offset, int3 extent, SubresourceData *subResourceData, GfxCount subResourceDataCount); - void uploadBufferData(IBufferResource dst, Offset offset, Size size, void *data); - void textureBarrier( + public void uploadBufferData(IBufferResource dst, Offset offset, Size size, void *data); + public void textureBarrier( GfxCount count, NativeRef *textures, ResourceState src, ResourceState dst); - void textureSubresourceBarrier( + public void textureSubresourceBarrier( ITextureResource texture, SubresourceRange subresourceRange, ResourceState src, ResourceState dst); - void bufferBarrier( - GfxCount count, NativeRef* buffers, ResourceState src, ResourceState dst); - void clearResourceView( + public void bufferBarrier( + GfxCount count, NativeRef *buffers, ResourceState src, ResourceState dst); + public void clearResourceView( IResourceView view, ClearValue *clearValue, ClearResourceViewFlags flags); - void resolveResource( + public void resolveResource( ITextureResource source, ResourceState sourceState, SubresourceRange sourceRange, ITextureResource dest, ResourceState destState, SubresourceRange destRange); - void resolveQuery( + public void resolveQuery( IQueryPool queryPool, GfxIndex index, GfxCount count, IBufferResource buffer, Offset offset); - void beginDebugEvent(NativeString name, float rgbColor[3]); - void endDebugEvent(); + public void beginDebugEvent(NativeString name, float rgbColor[3]); + public void endDebugEvent(); }; [COM("7A8D56D0-53E6-4AD6-85F7-D14DC110FDCE")] -interface IRenderCommandEncoder : IResourceCommandEncoder +public interface IRenderCommandEncoder : IResourceCommandEncoder { // Sets the current pipeline state. This method returns a transient shader object for // writing shader parameters. This shader object will not retain any resources or // sub-shader-objects bound to it. The user must be responsible for ensuring that any // resources or shader objects that is set into `outRootShaderObject` stays alive during // the execution of the command buffer. - Result bindPipeline(IPipelineState state, out IShaderObject outRootShaderObject); + public Result bindPipeline(IPipelineState state, out IShaderObject outRootShaderObject); // Sets the current pipeline state along with a pre-created mutable root shader object. - Result bindPipelineWithRootObject(IPipelineState state, NativeRef rootObject); + public Result bindPipelineWithRootObject(IPipelineState state, NativeRef rootObject); - void setViewports(GfxCount count, Viewport *viewports); - void setScissorRects(GfxCount count, ScissorRect *scissors); + public void setViewports(GfxCount count, Viewport *viewports); + public void setScissorRects(GfxCount count, ScissorRect *scissors); - void setPrimitiveTopology(PrimitiveTopology topology); - void setVertexBuffers( + public void setPrimitiveTopology(PrimitiveTopology topology); + public void setVertexBuffers( GfxIndex startSlot, GfxCount slotCount, NativeRef* buffers, Offset *offsets); - void setIndexBuffer(IBufferResource buffer, Format indexFormat, Offset offset); - void draw(GfxCount vertexCount, GfxIndex startVertex); - void drawIndexed(GfxCount indexCount, GfxIndex startIndex = 0, GfxIndex baseVertex = 0); - void drawIndirect( + public void setIndexBuffer(IBufferResource buffer, Format indexFormat, Offset offset); + public void draw(GfxCount vertexCount, GfxIndex startVertex); + public void drawIndexed(GfxCount indexCount, GfxIndex startIndex = 0, GfxIndex baseVertex = 0); + public void drawIndirect( GfxCount maxDrawCount, IBufferResource argBuffer, Offset argOffset, NativeRef countBuffer, Offset countOffset = 0); - void drawIndexedIndirect( + public void drawIndexedIndirect( GfxCount maxDrawCount, IBufferResource argBuffer, Offset argOffset, NativeRef countBuffer, Offset countOffset = 0); - void setStencilReference(uint32_t referenceValue); - Result setSamplePositions( + public void setStencilReference(uint32_t referenceValue); + public Result setSamplePositions( GfxCount samplesPerPixel, GfxCount pixelCount, SamplePosition *samplePositions); - void drawInstanced( + public void drawInstanced( GfxCount vertexCount, GfxCount instanceCount, GfxIndex startVertex, GfxIndex startInstanceLocation); - void drawIndexedInstanced( + public void drawIndexedInstanced( GfxCount indexCount, GfxCount instanceCount, GfxIndex startIndexLocation, @@ -1466,62 +1466,62 @@ interface IRenderCommandEncoder : IResourceCommandEncoder }; [COM("88AA9322-82F7-4FE6-A68A-29C7FE798737")] -interface IComputeCommandEncoder : IResourceCommandEncoder +public interface IComputeCommandEncoder : IResourceCommandEncoder { // Sets the current pipeline state. This method returns a transient shader object for // writing shader parameters. This shader object will not retain any resources or // sub-shader-objects bound to it. The user must be responsible for ensuring that any // resources or shader objects that is set into `outRooShaderObject` stays alive during // the execution of the command buffer. - Result bindPipeline(IPipelineState state, out Optional outRootShaderObject); - + public Result bindPipeline(IPipelineState state, out Optional outRootShaderObject); + // Sets the current pipeline state along with a pre-created mutable root shader object. - Result bindPipelineWithRootObject(IPipelineState state, IShaderObject rootObject); + public Result bindPipelineWithRootObject(IPipelineState state, IShaderObject rootObject); - void dispatchCompute(int x, int y, int z); - void dispatchComputeIndirect(IBufferResource cmdBuffer, Offset offset); + public void dispatchCompute(int x, int y, int z); + public void dispatchComputeIndirect(IBufferResource cmdBuffer, Offset offset); }; -enum class AccelerationStructureCopyMode +public enum class AccelerationStructureCopyMode { Clone, Compact }; -struct AccelerationStructureQueryDesc +public struct AccelerationStructureQueryDesc { - QueryType queryType; + public QueryType queryType; - NativeRef queryPool; + public NativeRef queryPool; - GfxIndex firstQueryIndex; + public GfxIndex firstQueryIndex; }; [COM("9a672b87-5035-45e3-967c-1f-85-cd-b3-63-4f")] -interface IRayTracingCommandEncoder : IResourceCommandEncoder +public interface IRayTracingCommandEncoder : IResourceCommandEncoder { - void buildAccelerationStructure( - AccelerationStructureBuildDesc *desc, - GfxCount propertyQueryCount, - AccelerationStructureQueryDesc *queryDescs); - void copyAccelerationStructure( + public void buildAccelerationStructure( + AccelerationStructureBuildDesc *desc, + GfxCount propertyQueryCount, + AccelerationStructureQueryDesc *queryDescs); + public void copyAccelerationStructure( NativeRef dest, NativeRef src, AccelerationStructureCopyMode mode); - void queryAccelerationStructureProperties( + public void queryAccelerationStructureProperties( GfxCount accelerationStructureCount, NativeRef *accelerationStructures, GfxCount queryCount, AccelerationStructureQueryDesc *queryDescs); - void serializeAccelerationStructure(DeviceAddress dest, IAccelerationStructure source); - void deserializeAccelerationStructure(IAccelerationStructure dest, DeviceAddress source); + public void serializeAccelerationStructure(DeviceAddress dest, IAccelerationStructure source); + public void deserializeAccelerationStructure(IAccelerationStructure dest, DeviceAddress source); - void bindPipeline(IPipelineState state, out IShaderObject rootObject); + public void bindPipeline(IPipelineState state, out IShaderObject rootObject); // Sets the current pipeline state along with a pre-created mutable root shader object. - Result bindPipelineWithRootObject(IPipelineState state, IShaderObject rootObject); + public Result bindPipelineWithRootObject(IPipelineState state, IShaderObject rootObject); /// Issues a dispatch command to start ray tracing workload with a ray tracing pipeline. /// `rayGenShaderIndex` specifies the index into the shader table that identifies the ray generation shader. - void dispatchRays( + public void dispatchRays( GfxIndex rayGenShaderIndex, NativeRef shaderTable, GfxCount width, @@ -1530,241 +1530,241 @@ interface IRayTracingCommandEncoder : IResourceCommandEncoder }; [COM("5d56063f-91d4-4723-a7a7-7a-15-af-93-eb-48")] -interface ICommandBuffer +public interface ICommandBuffer { // Only one encoder may be open at a time. User must call `ICommandEncoder::endEncoding` // before calling other `encode*Commands` methods. // Once `endEncoding` is called, the `ICommandEncoder` object becomes obsolete and is // invalid for further use. To continue recording, the user must request a new encoder // object by calling one of the `encode*Commands` methods again. - void encodeRenderCommands( + public void encodeRenderCommands( IRenderPassLayout renderPass, IFramebuffer framebuffer, out IRenderCommandEncoder outEncoder); - void encodeComputeCommands(out Optional encoder); + public void encodeComputeCommands(out Optional encoder); - void encodeResourceCommands(out Optional outEncoder); + public void encodeResourceCommands(out Optional outEncoder); - void encodeRayTracingCommands(out Optional outEncoder); + public void encodeRayTracingCommands(out Optional outEncoder); - void close(); + public void close(); - Result getNativeHandle(out InteropHandle outHandle); + public Result getNativeHandle(out InteropHandle outHandle); }; -enum class QueueType +public enum class QueueType { Graphics }; -struct CommandQueueDesc +public struct CommandQueueDesc { - QueueType type; + public QueueType type; }; [COM("14e2bed0-0ad0-4dc8-b341-06-3f-e7-2d-bf-0e")] -interface ICommandQueue +public interface ICommandQueue { - const CommandQueueDesc* getDesc(); + public const CommandQueueDesc* getDesc(); - void executeCommandBuffers( + public void executeCommandBuffers( GfxCount count, NativeRef *commandBuffers, Optional fenceToSignal, uint64_t newFenceValue); - Result getNativeHandle(out InteropHandle outHandle); + public Result getNativeHandle(out InteropHandle outHandle); - void waitOnHost(); + public void waitOnHost(); /// Queues a device side wait for the given fences. - Result waitForFenceValuesOnDevice(GfxCount fenceCount, NativeRef *fences, uint64_t *waitValues); + public Result waitForFenceValuesOnDevice(GfxCount fenceCount, NativeRef *fences, uint64_t *waitValues); }; -enum TransientResourceHeapFlags +public enum TransientResourceHeapFlags { None = 0, AllowResizing = 0x1, }; -struct TransientResourceHeapDesc +public struct TransientResourceHeapDesc { - TransientResourceHeapFlags flags; - Size constantBufferSize; - GfxCount samplerDescriptorCount; - GfxCount uavDescriptorCount; - GfxCount srvDescriptorCount; - GfxCount constantBufferDescriptorCount; - GfxCount accelerationStructureDescriptorCount; + public TransientResourceHeapFlags flags; + public Size constantBufferSize; + public GfxCount samplerDescriptorCount; + public GfxCount uavDescriptorCount; + public GfxCount srvDescriptorCount; + public GfxCount constantBufferDescriptorCount; + public GfxCount accelerationStructureDescriptorCount; }; [COM("cd48bd29-ee72-41b8-bcff-0a-2b-3a-aa-6d-0b")] -interface ITransientResourceHeap +public interface ITransientResourceHeap { // Waits until GPU commands issued before last call to `finish()` has been completed, and resets // all transient resources holds by the heap. // This method must be called before using the transient heap to issue new GPU commands. // In most situations this method should be called at the beginning of each frame. - Result synchronizeAndReset(); + public Result synchronizeAndReset(); // Must be called when the application has done using this heap to issue commands. In most situations // this method should be called at the end of each frame. - Result finish(); + public Result finish(); // Command buffers are one-time use. Once it is submitted to the queue via // `executeCommandBuffers` a command buffer is no longer valid to be used any more. Command // buffers must be closed before submission. The current D3D12 implementation has a limitation // that only one command buffer maybe recorded at a time. User must finish recording a command // buffer before creating another command buffer. - Result createCommandBuffer(out Optional outCommandBuffer); + public Result createCommandBuffer(out Optional outCommandBuffer); }; -struct SwapchainDesc +public struct SwapchainDesc { - Format format; - GfxCount width, height; - GfxCount imageCount; - NativeRef queue; - bool enableVSync; + public Format format; + public GfxCount width, height; + public GfxCount imageCount; + public NativeRef queue; + public bool enableVSync; }; [COM("be91ba6c-0784-4308-a1-00-19-c3-66-83-44-b2")] -interface ISwapchain +public interface ISwapchain { - const SwapchainDesc* getDesc(); + public const SwapchainDesc* getDesc(); /// Returns the back buffer image at `index`. - Result getImage(GfxIndex index, out ITextureResource outResource); + public Result getImage(GfxIndex index, out ITextureResource outResource); /// Present the next image in the swapchain. - Result present(); + public Result present(); /// Returns the index of next back buffer image that will be presented in the next /// `present` call. If the swapchain is invalid/out-of-date, this method returns -1. - int acquireNextImage(); + public int acquireNextImage(); /// Resizes the back buffers of this swapchain. All render target views and framebuffers /// referencing the back buffer images must be freed before calling this method. - Result resize(GfxCount width, GfxCount height); + public Result resize(GfxCount width, GfxCount height); // Check if the window is occluded. - bool isOccluded(); + public bool isOccluded(); // Toggle full screen mode. - Result setFullScreenMode(bool mode); + public Result setFullScreenMode(bool mode); }; -struct DeviceInfo +public struct DeviceInfo { - DeviceType deviceType; + public DeviceType deviceType; - BindingStyle bindingStyle; + public BindingStyle bindingStyle; - ProjectionStyle projectionStyle; + public ProjectionStyle projectionStyle; /// An projection matrix that ensures x, y mapping to pixels /// is the same on all targets - float identityProjectionMatrix[16]; + public float identityProjectionMatrix[16]; /// The name of the graphics API being used by this device. - NativeString apiName; + public NativeString apiName; /// The name of the graphics adapter. - NativeString adapterName; + public NativeString adapterName; /// The clock frequency used in timestamp queries. - uint64_t timestampFrequency; + public uint64_t timestampFrequency; }; -enum class DebugMessageType +public enum class DebugMessageType { Info, Warning, Error }; -enum class DebugMessageSource +public enum class DebugMessageSource { Layer, Driver, Slang }; [COM("B219D7E8-255A-2572-D46C-A0E5D99CEB90")] -interface IDebugCallback +public interface IDebugCallback { - void handleMessage(DebugMessageType type, DebugMessageSource source, NativeString message); + public void handleMessage(DebugMessageType type, DebugMessageSource source, NativeString message); }; -struct SlangDesc +public struct SlangDesc { - NativeRef slangGlobalSession; // (optional) A slang global session object. If null will create automatically. + public NativeRef slangGlobalSession; // (optional) A slang global session object. If null will create automatically. - slang::SlangMatrixLayoutMode defaultMatrixLayoutMode = slang::SlangMatrixLayoutMode::SLANG_MATRIX_LAYOUT_ROW_MAJOR; + public slang::SlangMatrixLayoutMode defaultMatrixLayoutMode = slang::SlangMatrixLayoutMode::SLANG_MATRIX_LAYOUT_ROW_MAJOR; - NativeString *searchPaths; - GfxCount searchPathCount; + public NativeString *searchPaths; + public GfxCount searchPathCount; - slang::PreprocessorMacroDesc* preprocessorMacros; - GfxCount preprocessorMacroCount = 0; + public slang::PreprocessorMacroDesc *preprocessorMacros; + public GfxCount preprocessorMacroCount = 0; - NativeString targetProfile; // (optional) Target shader profile. If null this will be set to platform dependent default. - slang::SlangFloatingPointMode floatingPointMode = slang::SlangFloatingPointMode::SLANG_FLOATING_POINT_MODE_DEFAULT; - slang::SlangOptimizationLevel optimizationLevel = slang::SlangOptimizationLevel::SLANG_OPTIMIZATION_LEVEL_DEFAULT; - slang::SlangTargetFlags targetFlags = slang::SlangTargetFlags.None; - slang::SlangLineDirectiveMode lineDirectiveMode = slang::SlangLineDirectiveMode::SLANG_LINE_DIRECTIVE_MODE_DEFAULT; + public NativeString targetProfile; // (optional) Target shader profile. If null this will be set to platform dependent default. + public slang::SlangFloatingPointMode floatingPointMode = slang::SlangFloatingPointMode::SLANG_FLOATING_POINT_MODE_DEFAULT; + public slang::SlangOptimizationLevel optimizationLevel = slang::SlangOptimizationLevel::SLANG_OPTIMIZATION_LEVEL_DEFAULT; + public slang::SlangTargetFlags targetFlags = slang::SlangTargetFlags.None; + public slang::SlangLineDirectiveMode lineDirectiveMode = slang::SlangLineDirectiveMode::SLANG_LINE_DIRECTIVE_MODE_DEFAULT; }; -struct ShaderCacheDesc +public struct ShaderCacheDesc { // The root directory for the shader cache. If not set, shader cache is disabled. - NativeString shaderCachePath; + public NativeString shaderCachePath; // The maximum number of entries stored in the cache. - GfxCount maxEntryCount = 0; + public GfxCount maxEntryCount = 0; }; -struct DeviceInteropHandles +public struct DeviceInteropHandles { - InteropHandle handles[3] = {}; + public InteropHandle handles[3] = {}; }; -struct DeviceDesc +public struct DeviceDesc { // The underlying API/Platform of the device. - DeviceType deviceType = DeviceType::Default; + public DeviceType deviceType = DeviceType::Default; // The device's handles (if they exist) and their associated API. For D3D12, this contains a single InteropHandle // for the ID3D12Device. For Vulkan, the first InteropHandle is the VkInstance, the second is the VkPhysicalDevice, // and the third is the VkDevice. For CUDA, this only contains a single value for the CUDADevice. - DeviceInteropHandles existingDeviceHandles; + public DeviceInteropHandles existingDeviceHandles; // Name to identify the adapter to use - NativeString adapter; + public NativeString adapter; // Number of required features. - GfxCount requiredFeatureCount = 0; + public GfxCount requiredFeatureCount = 0; // Array of required feature names, whose size is `requiredFeatureCount`. - NativeString *requiredFeatures = nullptr; + public NativeString *requiredFeatures = nullptr; // A command dispatcher object that intercepts and handles actual low-level API call. void *apiCommandDispatcher = nullptr; // The slot (typically UAV) used to identify NVAPI intrinsics. If >=0 NVAPI is required. - GfxIndex nvapiExtnSlot = -1; + public GfxIndex nvapiExtnSlot = -1; // Configurations for the shader cache. - ShaderCacheDesc shaderCache = {}; + public ShaderCacheDesc shaderCache = {}; // Configurations for Slang compiler. - SlangDesc slang = {}; + public SlangDesc slang = {}; - GfxCount extendedDescCount = 0; - void **extendedDescs = nullptr; + public GfxCount extendedDescCount = 0; + public void **extendedDescs = nullptr; }; [COM("715bdf26-5135-11eb-AE93-02-42-AC-13-00-02")] -interface IDevice +public interface IDevice { - Result getNativeDeviceHandles(out DeviceInteropHandles outHandles); + public Result getNativeDeviceHandles(out DeviceInteropHandles outHandles); - bool hasFeature(NativeString feature); + public bool hasFeature(NativeString feature); /// Returns a list of features supported by the renderer. - Result getFeatures(NativeString *outFeatures, Size bufferSize, GfxCount *outFeatureCount); + public Result getFeatures(NativeString *outFeatures, Size bufferSize, GfxCount *outFeatureCount); - Result getFormatSupportedResourceStates(Format format, ResourceStateSet *outStates); + public Result getFormatSupportedResourceStates(Format format, ResourceStateSet *outStates); - Result getSlangSession(NativeRef* outSlangSession); + public Result getSlangSession(NativeRef* outSlangSession); - Result createTransientResourceHeap( + public Result createTransientResourceHeap( TransientResourceHeapDesc *desc, out Optional outHeap); @@ -1782,194 +1782,194 @@ interface IDevice /// /// effectiveElementCount = (isArray ? arrayElementCount : 1) * (isCube ? 6 : 1); /// - Result createTextureResource( + public Result createTextureResource( TextureResourceDesc* desc, SubresourceData *initData, out ITextureResource outResource); - Result createTextureFromNativeHandle( + public Result createTextureFromNativeHandle( InteropHandle handle, TextureResourceDesc* srcDesc, out ITextureResource outResource); - Result createTextureFromSharedHandle( + public Result createTextureFromSharedHandle( InteropHandle handle, TextureResourceDesc *srcDesc, Size size, out ITextureResource outResource); /// Create a buffer resource - Result createBufferResource( + public Result createBufferResource( BufferResourceDesc* desc, void *initData, out Optional outResource); - Result createBufferFromNativeHandle( + public Result createBufferFromNativeHandle( InteropHandle handle, BufferResourceDesc* srcDesc, out IBufferResource outResource); - Result createBufferFromSharedHandle( + public Result createBufferFromSharedHandle( InteropHandle handle, BufferResourceDesc* srcDesc, out IBufferResource outResource); - Result createSamplerState(SamplerStateDesc* desc, out ISamplerState outSampler); + public Result createSamplerState(SamplerStateDesc* desc, out ISamplerState outSampler); - Result createTextureView( + public Result createTextureView( ITextureResource texture, ResourceViewDesc* desc, out IResourceView outView); - Result createBufferView( + public Result createBufferView( IBufferResource buffer, Optional counterBuffer, ResourceViewDesc* desc, out Optional outView); - Result createFramebufferLayout(FramebufferLayoutDesc* desc, out IFramebufferLayout outFrameBuffer); - - Result createFramebuffer(FramebufferDesc* desc, out IFramebuffer outFrameBuffer); + public Result createFramebufferLayout(FramebufferLayoutDesc* desc, out IFramebufferLayout outFrameBuffer); + + public Result createFramebuffer(FramebufferDesc* desc, out IFramebuffer outFrameBuffer); - Result createRenderPassLayout( + public Result createRenderPassLayout( RenderPassLayoutDesc* desc, out IRenderPassLayout outRenderPassLayout); - Result createSwapchain( + public Result createSwapchain( SwapchainDesc* desc, WindowHandle window, out ISwapchain outSwapchain); - Result createInputLayout( + public Result createInputLayout( InputLayoutDesc* desc, out IInputLayout outLayout); - Result createCommandQueue(CommandQueueDesc* desc, out Optional outQueue); + public Result createCommandQueue(CommandQueueDesc* desc, out Optional outQueue); - Result createShaderObject( + public Result createShaderObject( slang::TypeReflection *type, ShaderObjectContainerType container, out IShaderObject outObject); - Result createMutableShaderObject( + public Result createMutableShaderObject( slang::TypeReflection *type, ShaderObjectContainerType container, out IShaderObject outObject); - Result createShaderObjectFromTypeLayout( + public Result createShaderObjectFromTypeLayout( slang::TypeLayoutReflection *typeLayout, out IShaderObject outObject); - Result createMutableShaderObjectFromTypeLayout( + public Result createMutableShaderObjectFromTypeLayout( slang::TypeLayoutReflection *typeLayout, out IShaderObject outObject); - Result createMutableRootShaderObject( + public Result createMutableRootShaderObject( IShaderProgram program, out IShaderObject outObject); - Result createShaderTable(ShaderTableDesc* desc, out IShaderTable outTable); + public Result createShaderTable(ShaderTableDesc* desc, out IShaderTable outTable); - Result createProgram( + public Result createProgram( void *desc, out IShaderProgram outProgram, out slang::ISlangBlob outDiagnosticBlob); - Result createProgram2( + public Result createProgram2( ShaderProgramDesc2 *desc, out Optional outProgram, out Optional outDiagnosticBlob); - Result createGraphicsPipelineState( + public Result createGraphicsPipelineState( GraphicsPipelineStateDesc *desc, out Optional outState); - Result createComputePipelineState( + public Result createComputePipelineState( ComputePipelineStateDesc* desc, out Optional outState); - Result createRayTracingPipelineState( + public Result createRayTracingPipelineState( RayTracingPipelineStateDesc *desc, out Optional outState); /// Read back texture resource and stores the result in `outBlob`. - Result readTextureResource( + public Result readTextureResource( ITextureResource resource, ResourceState state, out slang::ISlangBlob outBlob, out Size outRowPitch, out Size outPixelSize); - Result readBufferResource( + public Result readBufferResource( IBufferResource buffer, Offset offset, Size size, out Optional outBlob); /// Get the type of this renderer - DeviceInfo* getDeviceInfo(); + public DeviceInfo* getDeviceInfo(); - Result createQueryPool( + public Result createQueryPool( QueryPoolDesc* desc, out IQueryPool outPool); - Result getAccelerationStructurePrebuildInfo( + public Result getAccelerationStructurePrebuildInfo( AccelerationStructureBuildInputs* buildInputs, out AccelerationStructurePrebuildInfo outPrebuildInfo); - Result createAccelerationStructure( + public Result createAccelerationStructure( AccelerationStructureCreateDesc* desc, out IAccelerationStructure outView); - Result createFence(FenceDesc* desc, out IFence outFence); + public Result createFence(FenceDesc* desc, out IFence outFence); /// Wait on the host for the fences to signals. /// `timeout` is in nanoseconds, can be set to `kTimeoutInfinite`. - Result waitForFences( + public Result waitForFences( GfxCount fenceCount, NativeRef* fences, uint64_t *values, bool waitForAll, uint64_t timeout); - Result getTextureAllocationInfo( + public Result getTextureAllocationInfo( TextureResourceDesc* desc, out Size outSize, out Size outAlignment); - Result getTextureRowAlignment(out Size outAlignment); + public Result getTextureRowAlignment(out Size outAlignment); }; -struct ShaderCacheStats +public struct ShaderCacheStats { - GfxCount hitCount; - GfxCount missCount; - GfxCount entryCount; + public GfxCount hitCount; + public GfxCount missCount; + public GfxCount entryCount; }; [COM("715bdf26-5135-11eb-AE93-02-42-AC-13-00-02")] -interface IShaderCache +public interface IShaderCache { - Result clearShaderCache(); - Result getShaderCacheStats(out ShaderCacheStats outStats); - Result resetShaderCacheStats(); + public Result clearShaderCache(); + public Result getShaderCacheStats(out ShaderCacheStats outStats); + public Result resetShaderCacheStats(); }; #define SLANG_GFX_IMPORT [DllImport("gfx")] /// Checks if format is compressed -SLANG_GFX_IMPORT bool gfxIsCompressedFormat(Format format); +SLANG_GFX_IMPORT public bool gfxIsCompressedFormat(Format format); /// Checks if format is typeless -SLANG_GFX_IMPORT bool gfxIsTypelessFormat(Format format); +SLANG_GFX_IMPORT public bool gfxIsTypelessFormat(Format format); /// Gets information about the format -SLANG_GFX_IMPORT Result gfxGetFormatInfo(Format format, FormatInfo *outInfo); +SLANG_GFX_IMPORT public Result gfxGetFormatInfo(Format format, FormatInfo *outInfo); -/// Given a type returns a function that can construct it, or nullptr if there isn't one -SLANG_GFX_IMPORT Result gfxCreateDevice(const DeviceDesc* desc, out Optional outDevice); +/// Given a type returns a function that can conpublic struct it, or nullptr if there isn't one +SLANG_GFX_IMPORT public Result gfxCreateDevice(const DeviceDesc* desc, out Optional outDevice); /// Reports current set of live objects in gfx. /// Currently this only calls D3D's ReportLiveObjects. -SLANG_GFX_IMPORT Result gfxReportLiveObjects(); +SLANG_GFX_IMPORT public Result gfxReportLiveObjects(); /// Sets a callback for receiving debug messages. /// The layer does not hold a strong reference to the callback object. /// The user is responsible for holding the callback object alive. -SLANG_GFX_IMPORT Result gfxSetDebugCallback(IDebugCallback callback); +SLANG_GFX_IMPORT public Result gfxSetDebugCallback(IDebugCallback callback); /// Enables debug layer. The debug layer will check all `gfx` calls and verify that uses are valid. -SLANG_GFX_IMPORT void gfxEnableDebugLayer(); +SLANG_GFX_IMPORT public void gfxEnableDebugLayer(); -SLANG_GFX_IMPORT NativeString gfxGetDeviceTypeName(DeviceType type); +SLANG_GFX_IMPORT public NativeString gfxGetDeviceTypeName(DeviceType type); public bool succeeded(Result code) { diff --git a/tools/gfx/slang.slang b/tools/gfx/slang.slang index aa28fae82..4e2700c2c 100644 --- a/tools/gfx/slang.slang +++ b/tools/gfx/slang.slang @@ -1,10 +1,10 @@ -namespace slang +public namespace slang { -typedef int32_t Result; -typedef uint64_t Size; -typedef int64_t Int; -typedef uint64_t UInt; +public typedef int32_t Result; +public typedef uint64_t Size; +public typedef int64_t Int; +public typedef uint64_t UInt; /*! @brief Severity of a diagnostic generated by the compiler. @@ -12,7 +12,7 @@ Values come from the enum below, with higher values representing more severe conditions, and all values >= SLANG_SEVERITY_ERROR indicating compilation failure. */ -enum SlangSeverity +public enum SlangSeverity { SLANG_SEVERITY_DISABLED = 0, /**< A message that is disabled, filtered out. */ SLANG_SEVERITY_NOTE, /**< An informative message. */ @@ -22,13 +22,13 @@ enum SlangSeverity SLANG_SEVERITY_INTERNAL, /**< An internal error, indicating a logic error in the compiler. */ }; -enum SlangDiagnosticFlags +public enum SlangDiagnosticFlags { SLANG_DIAGNOSTIC_FLAG_VERBOSE_PATHS = 0x01, SLANG_DIAGNOSTIC_FLAG_TREAT_WARNINGS_AS_ERRORS = 0x02 }; -enum SlangBindableResourceType +public enum SlangBindableResourceType { SLANG_NON_BINDABLE = 0, SLANG_TEXTURE, @@ -37,7 +37,7 @@ enum SlangBindableResourceType SLANG_STORAGE_BUFFER, }; -enum SlangCompileTarget +public enum SlangCompileTarget { SLANG_TARGET_UNKNOWN, SLANG_TARGET_NONE, @@ -68,7 +68,7 @@ enum SlangCompileTarget /* A "container format" describes the way that the outputs for multiple files, entry points, targets, etc. should be combined into a single artifact for output. */ -enum SlangContainerFormat +public enum SlangContainerFormat { /* Don't generate a container. */ SLANG_CONTAINER_FORMAT_NONE, @@ -78,7 +78,7 @@ enum SlangContainerFormat SLANG_CONTAINER_FORMAT_SLANG_MODULE, }; -enum SlangPassThrough : int +public enum SlangPassThrough : int { SLANG_PASS_THROUGH_NONE, SLANG_PASS_THROUGH_FXC, @@ -96,7 +96,7 @@ enum SlangPassThrough : int }; /* Defines an archive type used to holds a 'file system' type structure. */ -enum SlangArchiveType : int +public enum SlangArchiveType : int { SLANG_ARCHIVE_TYPE_UNDEFINED, SLANG_ARCHIVE_TYPE_ZIP, @@ -109,7 +109,7 @@ enum SlangArchiveType : int /*! Flags to control compilation behavior. */ -enum SlangCompileFlags +public enum SlangCompileFlags { /* Do as little mangling of names as possible, to try to preserve original names */ SLANG_COMPILE_FLAG_NO_MANGLING = 1 << 3, @@ -129,7 +129,7 @@ enum SlangCompileFlags /*! @brief Flags to control code generation behavior of a compilation target */ -enum SlangTargetFlags +public enum SlangTargetFlags { None = 0, @@ -156,7 +156,7 @@ enum SlangTargetFlags /*! @brief Options to control floating-point precision guarantees for a target. */ -enum SlangFloatingPointMode +public enum SlangFloatingPointMode { SLANG_FLOATING_POINT_MODE_DEFAULT = 0, SLANG_FLOATING_POINT_MODE_FAST, @@ -166,7 +166,7 @@ enum SlangFloatingPointMode /*! @brief Options to control emission of `#line` directives */ -enum SlangLineDirectiveMode +public enum SlangLineDirectiveMode { SLANG_LINE_DIRECTIVE_MODE_DEFAULT = 0, /**< Default behavior: pick behavior base on target. */ SLANG_LINE_DIRECTIVE_MODE_NONE, /**< Don't emit line directives at all. */ @@ -174,7 +174,7 @@ enum SlangLineDirectiveMode SLANG_LINE_DIRECTIVE_MODE_GLSL, /**< Emit GLSL-style directives with file *number* instead of name */ }; -enum SlangSourceLanguage : int +public enum SlangSourceLanguage : int { SLANG_SOURCE_LANGUAGE_UNKNOWN, SLANG_SOURCE_LANGUAGE_SLANG, @@ -186,24 +186,24 @@ enum SlangSourceLanguage : int SLANG_SOURCE_LANGUAGE_COUNT_OF, }; -enum SlangProfileID +public enum SlangProfileID { SLANG_PROFILE_UNKNOWN, }; -enum SlangCapabilityID +public enum SlangCapabilityID { SLANG_CAPABILITY_UNKNOWN = 0, }; -enum SlangMatrixLayoutMode +public enum SlangMatrixLayoutMode { SLANG_MATRIX_LAYOUT_MODE_UNKNOWN = 0, SLANG_MATRIX_LAYOUT_ROW_MAJOR, SLANG_MATRIX_LAYOUT_COLUMN_MAJOR, }; -enum SlangStage +public enum SlangStage { SLANG_STAGE_NONE, SLANG_STAGE_VERTEX, @@ -222,7 +222,7 @@ enum SlangStage SLANG_STAGE_AMPLIFICATION, }; -enum SlangDebugInfoLevel +public enum SlangDebugInfoLevel { SLANG_DEBUG_INFO_LEVEL_NONE = 0, /**< Don't emit debug information at all. */ SLANG_DEBUG_INFO_LEVEL_MINIMAL, /**< Emit as little debug information as possible, while still supporting stack trackes. */ @@ -230,14 +230,14 @@ enum SlangDebugInfoLevel SLANG_DEBUG_INFO_LEVEL_MAXIMAL, /**< Emit as much debug infromation as possible for each target. */ }; -enum SlangOptimizationLevel +public enum SlangOptimizationLevel { SLANG_OPTIMIZATION_LEVEL_NONE = 0, /**< Don't optimize at all. */ SLANG_OPTIMIZATION_LEVEL_DEFAULT, /**< Default optimization level: balance code quality and compilation time. */ SLANG_OPTIMIZATION_LEVEL_HIGH, /**< Optimize aggressively. */ SLANG_OPTIMIZATION_LEVEL_MAXIMAL, /**< Include optimizations that may take a very long time, or may involve severe space-vs-speed tradeoffs */ }; -enum SlangTypeKind +public enum SlangTypeKind { NONE, STRUCT, @@ -259,7 +259,7 @@ enum SlangTypeKind COUNT, }; -enum SlangScalarType +public enum SlangScalarType { NONE, VOID, @@ -277,59 +277,59 @@ enum SlangScalarType UINT16, }; -struct TypeReflection +public struct TypeReflection { }; -enum CompileStdLibFlags +public enum CompileStdLibFlags { WriteDocumentation = 0x1, }; [COM("8BA5FB08-5195-40e2-AC58-0D-98-9C-3A-01-02")] -interface ISlangBlob +public interface ISlangBlob { - void *getBufferPointer(); - Size getBufferSize(); + public void *getBufferPointer(); + public Size getBufferSize(); }; /** Description of a code generation target. */ -struct TargetDesc +public struct TargetDesc { /** The size of this structure, in bytes. */ - Size structureSize = 40; + public Size structureSize = 40; /** The target format to generate code for (e.g., SPIR-V, DXIL, etc.) */ - SlangCompileTarget format = SlangCompileTarget.SLANG_TARGET_UNKNOWN; + public SlangCompileTarget format = SlangCompileTarget.SLANG_TARGET_UNKNOWN; /** The compilation profile supported by the target (e.g., "Shader Model 5.1") */ - SlangProfileID profile = SlangProfileID.SLANG_PROFILE_UNKNOWN; + public SlangProfileID profile = SlangProfileID.SLANG_PROFILE_UNKNOWN; /** Flags for the code generation target. Currently unused. */ - SlangTargetFlags flags = SlangTargetFlags.None; + public SlangTargetFlags flags = SlangTargetFlags.None; /** Default mode to use for floating-point operations on the target. */ - SlangFloatingPointMode floatingPointMode = SlangFloatingPointMode.SLANG_FLOATING_POINT_MODE_DEFAULT; + public SlangFloatingPointMode floatingPointMode = SlangFloatingPointMode.SLANG_FLOATING_POINT_MODE_DEFAULT; /** Optimization level to use for the target. */ - SlangOptimizationLevel optimizationLevel = SlangOptimizationLevel.SLANG_OPTIMIZATION_LEVEL_DEFAULT; + public SlangOptimizationLevel optimizationLevel = SlangOptimizationLevel.SLANG_OPTIMIZATION_LEVEL_DEFAULT; /** The line directive mode for output source code. */ - SlangLineDirectiveMode lineDirectiveMode = SlangLineDirectiveMode.SLANG_LINE_DIRECTIVE_MODE_DEFAULT; + public SlangLineDirectiveMode lineDirectiveMode = SlangLineDirectiveMode.SLANG_LINE_DIRECTIVE_MODE_DEFAULT; /** Whether to force `scalar` layout for glsl shader storage buffers. */ - bool forceGLSLScalarBufferLayout = false; + public bool forceGLSLScalarBufferLayout = false; }; -enum SessionFlags +public enum SessionFlags { kSessionFlags_None = 0, @@ -344,40 +344,40 @@ enum SessionFlags kSessionFlag_FalcorCustomSharedKeywordSemantics = 1 << 0, }; -struct PreprocessorMacroDesc +public struct PreprocessorMacroDesc { - NativeString name; - NativeString value; + public NativeString name; + public NativeString value; }; -struct SessionDesc +public struct SessionDesc { /** The size of this structure, in bytes. */ - Size structureSize = 72; + public Size structureSize = 72; /** Code generation targets to include in the session. */ - TargetDesc *targets = nullptr; - Int targetCount = 0; + public TargetDesc *targets = nullptr; + public Int targetCount = 0; /** Flags to configure the session. */ - SessionFlags flags = SessionFlags.kSessionFlags_None; + public SessionFlags flags = SessionFlags.kSessionFlags_None; /** Default layout to assume for variables with matrix types. */ - SlangMatrixLayoutMode defaultMatrixLayoutMode = SlangMatrixLayoutMode.SLANG_MATRIX_LAYOUT_ROW_MAJOR; + public SlangMatrixLayoutMode defaultMatrixLayoutMode = SlangMatrixLayoutMode.SLANG_MATRIX_LAYOUT_ROW_MAJOR; /** Paths to use when searching for `#include`d or `import`ed files. */ - NativeString *searchPaths = nullptr; - Int searchPathCount = 0; + public NativeString *searchPaths = nullptr; + public Int searchPathCount = 0; - PreprocessorMacroDesc *preprocessorMacros = nullptr; - Int preprocessorMacroCount = 0; + public PreprocessorMacroDesc *preprocessorMacros = nullptr; + public Int preprocessorMacroCount = 0; - void *fileSystem = nullptr; + public void *fileSystem = nullptr; }; /** A global session for interaction with the Slang library. @@ -391,11 +391,11 @@ a single global session should only be used from a single thread at a time. */ [COM("c140b5fd-0c78-452e-ba7c-1a-1e-70-c7-f7-1c")] -interface IGlobalSession +public interface IGlobalSession { }; -enum class ContainerType +public enum class ContainerType { None, UnsizedArray, StructuredBuffer, ConstantBuffer, ParameterBlock }; @@ -426,29 +426,29 @@ Applications wishing to control the memory usage for compiled and loaded code should use multiple sessions. */ [COM("67618701-d116-468f-ab3b-47-4b-ed-ce-0e-3d")] -interface ISession +public interface ISession { }; [COM("5bc42be8-5c50-4929-9e5e-d15e7c24015f")] -interface IComponentType +public interface IComponentType { } -struct TypeLayoutReflection { } +public struct TypeLayoutReflection { } /** The kind of specialization argument. */ -enum class SpecializationArgKind : int32_t +public enum class SpecializationArgKind : int32_t { Unknown, /**< An invalid specialization argument. */ Type, /**< Specialize to a type. */ }; -struct SpecializationArg +public struct SpecializationArg { - SpecializationArgKind kind; + public SpecializationArgKind kind; /** A type specialization argument, used for `Kind::Type`. */ - TypeReflection *type; + public TypeReflection *type; } } diff --git a/tools/slang-unit-test/unit-test-com-host-callable.slang b/tools/slang-unit-test/unit-test-com-host-callable.slang index 75c985040..e41eed8ef 100644 --- a/tools/slang-unit-test/unit-test-com-host-callable.slang +++ b/tools/slang-unit-test/unit-test-com-host-callable.slang @@ -2,19 +2,19 @@ // Example of using 'NativeString' -public __extern_cpp NativeString getString(NativeString in) +export __extern_cpp NativeString getString(NativeString in) { return in; } -public __extern_cpp __global int intGlobal = 10; +export __extern_cpp __global int intGlobal = 10; -public __extern_cpp void setGlobal(int v) +export __extern_cpp void setGlobal(int v) { intGlobal = v; } -public __extern_cpp int getGlobal() +export __extern_cpp int getGlobal() { return intGlobal; } @@ -32,19 +32,19 @@ interface ICountGood int nextCount(); } -public __extern_cpp __global ICountGood globalCounter; +export __extern_cpp __global ICountGood globalCounter; -public __extern_cpp void setCounter(ICountGood counter) +export __extern_cpp void setCounter(ICountGood counter) { globalCounter = counter; } -public __extern_cpp int nextCount() +export __extern_cpp int nextCount() { return globalCounter.nextCount(); } -public __extern_cpp int calcHash(NativeString text, IDoThings doThings) +export __extern_cpp int calcHash(NativeString text, IDoThings doThings) { return doThings.calcHash(text); } diff --git a/tools/slang-unit-test/unit-test-translation-unit-import.cpp b/tools/slang-unit-test/unit-test-translation-unit-import.cpp index 3a0a98e17..b8108644e 100644 --- a/tools/slang-unit-test/unit-test-translation-unit-import.cpp +++ b/tools/slang-unit-test/unit-test-translation-unit-import.cpp @@ -18,7 +18,7 @@ SLANG_UNIT_TEST(translationUnitImport) { // Source for the first translation unit. const char* generatedSource = - "int f() {" + "public int f() {" " return 5;" "};"; @@ -28,7 +28,7 @@ SLANG_UNIT_TEST(translationUnitImport) R"( import generatedUnit; - int g(){ return f(); } + public int g(){ return f(); } )"; // Source for a module that transitively uses the generated source via a file. -- cgit v1.2.3