• Slang User's Guide
    • Introduction
      • Why use Slang?
      • Who is Slang for?
      • Who is this guide for?
      • Goals and Non-Goals
    • Getting Started with Slang
      • Installation
      • Your first Slang shader
      • The full example
    • Conventional Language Features
      • Types
      • Expressions
      • Statements
      • Functions
      • Preprocessor
      • Attributes
      • Global Variables and Shader Parameters
      • Shader Entry Points
      • Mixed Shader Entry Points
    • Basic Convenience Features
      • Type Inference in Variable Definitions
      • Immutable Values
      • Namespaces
      • Member functions
      • Properties
      • Initializers
      • Operator Overloading
      • Subscript Operator
      • `Optional<T>` type
      • `reinterpret<T>` operation
      • Pointers (limited)
      • `struct` inheritance (limited)
      • Extensions
      • Multi-level break
      • Force inlining
      • Special Scoping Syntax
    • Modules and Access Control
      • Defining a Module
      • Importing a Module
      • Access Control
      • Legacy Modules
    • Capabilities
      • Capability Atoms and Capability Requirements
      • Conflicting Capabilities
      • Requirements in Parent Scope
      • Inferrence of Capability Requirements
      • Inferrence on target_switch
      • Capability Aliases
      • Validation of Capability Requirements
    • 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
      • Builtin Interfaces
    • 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
    • Compiling Code with Slang
      • Concepts
      • Command-Line Compilation with `slangc`
      • Using the Compilation API
      • Multithreading
      • Compiler Options
      • Debugging
    • Using the Reflection API
      • Program Reflection
      • Variable Layouts
      • Type Layouts
      • Arrays
      • Structures
      • Entry Points
    • Supported Compilation Targets
      • Background and Terminology
      • Direct3D 11
      • Direct3D 12
      • Vulkan
      • OpenGL
      • CUDA and OptiX
      • CPU Compute
      • Summary
    • Link-time Specialization and Module Precompilation
      • Link-time Constants
      • Link-time Types
      • Providing Default Settings
      • Restrictions
      • Using Precompiling Modules with the API
      • Additional Remarks
    • Special Topics
      • Handling Matrix Layout Differences on Different Platforms
        • Two conventions of matrix transform math
        • Discussion
        • Matrix Layout
        • Overriding default matrix layout
      • Using Slang to Write PyTorch Kernels
        • Getting Started with slangpy
        • Specializing shaders using slangpy
        • Back-propagating Derivatives through Complex Access Patterns
        • Manually binding kernels
        • Builtin Library Support for PyTorch Interop
        • Type Marshalling Between Slang and Python
      • Obfuscation
        • Obfuscation in Slang
        • Using An Obfuscated Module
        • Accessing Source Maps
        • Accessing Source Maps without Files
        • Emit Source Maps
        • Issues/Future Work
      • Interoperation with Target-Specific Code
        • Defining Intrinsic Functions for Textual Targets
        • Defining Intrinsic Types
        • Injecting Preludes
        • Managing Cross-Platform Code
        • Inline SPIRV Assembly
      • Uniformity Analysis
        • Treat Values as Uniform
        • Treat Function Return Values as Non-uniform

Modules and Access Control

While the preprocessor #includes 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 <module-name> 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, #defines before a __include is not visible to the included file, and #defines 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 __includes 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 __includes.

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.

void f_c()
{
    // OK, `c.slang` is part of module `m` because it is `__include`'d by
    // `m.slang`.
    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";

Also note that a file is considered a part of a module only if the file can be discovered via transitive __includes from the primary module file. It is possible to have a dangling file with the implementing declaration that is not __include‘d by any other files in the module. Such dangling files will not be considered as part of the module and will not be compiled. The implementing declaration is for the purpose of verification and language server code assisting, and does not carry any other semantics that affect compilation.

Note

When using the identifier token syntax, Slang will translate any underscores(_) to hyphens(“-“) 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:

// 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 imports 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 imported 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:

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:

// a.slang
module a;
__include b;
public struct PS
{
    internal int internalMember;
    public int publicMember;
}
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`.
public void publicFunc();

// m.slang
module m;
import a;
void main()
{
    f(); // Error, f is not visible here.
    publicFunc(); // OK.
    PS p; // OK.
    p.internalMember = 1; // Error, internalMember is not visible.
    p.publicMember = 1; // OK.
}

internal is the default visibility if no other access modifiers are specified, an exception is for interface members, where the default visibility is the visibility of the interface.

Additional Validation Rules

The Slang compiler enforces the following rules regarding access control:

  • A more visible entity should not expose less visible entities through its signature. For example, a public function cannot have a return type that is internal.
  • A member of a struct, interface and other aggregate types cannot have a higher visibility than its parent.
  • If a struct type has visibility Vs, and one of its member has visibility Vm, and the member is used to satisfy an interface requirement that has visibility Vr, then Vm must not be lower (less visible) than min(Vs, Vr).
  • Type definitions themselves cannot be private, for example, private struct S {} is not valid code.
  • interface requirements cannot be private.

Legacy Modules

Slang used to not have support for access control, and all symbols were treated as having public visibility. To provide compatibility with existing code, the Slang compiler will detect if the module is written in the legacy language, and treat all symbols as public if so.

A module is determined to be written in legacy language if all the following conditions are met:

  • The module is lacking module declaration at the beginning.
  • There is no use of __include.
  • There is no use of any visibility modifiers – public, private or internal.

The user is advised that this legacy mode is for compatibility only. This mode may be deprecated in the future, and it is strongly recommended that new code should not rely on this compiler behavior.