summaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorTheresa Foley <10618364+tangent-vector@users.noreply.github.com>2022-05-17 10:56:36 -0700
committerGitHub <noreply@github.com>2022-05-17 10:56:36 -0700
commit39fb45484996f9d71b6551239dbf55eaaaf8db1f (patch)
treed31f4e26866ab3bb0be219ed21d3dd2974c18dff /docs
parent5a3aa6159e0ef0241b528812e1d138f0d7055f22 (diff)
More proposals (#2232)
Checking in more in-progress proposals in the hopes of sparking discussion and/or guiding future implementation work.
Diffstat (limited to 'docs')
-rw-r--r--docs/proposals/002-api-headers.md952
-rw-r--r--docs/proposals/003-error-handling.md296
-rw-r--r--docs/proposals/004-com-support.md240
-rw-r--r--docs/proposals/005-components.md507
4 files changed, 1995 insertions, 0 deletions
diff --git a/docs/proposals/002-api-headers.md b/docs/proposals/002-api-headers.md
new file mode 100644
index 000000000..66b649228
--- /dev/null
+++ b/docs/proposals/002-api-headers.md
@@ -0,0 +1,952 @@
+
+Revise Slang/GFX API Headers
+============================
+
+The public C/C++ API headers for Slang (and GFX) are in need of cleanup and refactoring for us to reach a "1.0" API.
+This document attempts to document the guidelines that we will follow in such a refactor.
+
+Status
+------
+
+In discussion.
+
+Background
+----------
+
+The Slang API header (`slang.h`) has evolved over many years, going back as far as the "Spire" research project, which predates Slang (Spire is the reason for the `sp` prefix on functions in the C API).
+
+At some point, we made a conscious decision to move toward a COM-based API, both because it would simplify our story around binary compatibility and because it would allow us to provide more convenient API models in cases where subtyping/inheritance is fundamental to the domain.
+Unfortunately, the net result has been that we have two different APIs cluttering up the same header file (the old C one, and the newer C++/COM one), and the one that is presented *first* is actually the one we would rather users didn't reach for.
+The two APIs are sometimes out of sync, with one providing services the other doesn't.
+
+While the GFX project started later and was thus able to start using COM interfaces and a C++ API from the start, it still faces some challenges around API evolution and binary compatibility.
+As support for GPU features (whether pre-existing or new) gets added, we find that the various interfaces want to grow and the various `Desc` structures want to add new fields.
+Without care, neither of those is a binary-compatible change for user code.
+
+A concern across both Slang and GFX is that we have tended to design our APIs around the *most complicated* use cases we intend to support, at the expense of the *simplest* cases.
+We know that we cannot remove support for difficult cases, but it would be good to support concise code for simple use cases, and to support a "progressive disclosure" style that allows users to gradually adopt more involved API constructs as they become necessary.
+
+Related Work
+------------
+
+There are obviously far too many C/C++ APIs and approachs to design for C/C++ APIs for us to review them all.
+We will simply note a few key examples that can be relevant for comparison.
+
+The gold standard for C/C++ APIs is ultimately plain C. Plain C is easy for most systems programmers to understand and benefits from having a well-defined ABI on almost every interesting platform. FFI systems for other languages tend to work with plain C APIs. Clarity around ABI makes it easy to know what changes/additions to a plain C API will and will not break binary compatibility. The Cg compiler API and the Vulkan GPU API are good examples of C-based APIs in the same domains as Slang and GFX, respectively. These APIs reveal some of the challenges of using plain C for large and complicated APIs:
+
+* The lack of subtype polymorphism is a problem when a domain fundamentally has subtyping. The Cg reflection API uses a single `CGtype` type to represent all types, so that the an operation like `cgGetMatrixSize` is applicable to any type, not just matrix types. The API cannot guide a programmer toward correct usage, and must define what happens in all possible incorrect cases.
+
+* C has no built-in model for error signalling or handling. Error codes and out-of-band values (`NULL`, `-1`) are the norm, and there are a multitude of API-specific conventions for how they are applied.
+
+* C has no built-in model for memory and lifetime management. Most APIs either expose create/delete pairs or some kind of per-type reference-counting retain/release operations. In either case, the application developer is left to ensure that the operations are correctly invoked, often by writing their own C++ wrapper around the raw C API.
+
+Some developers opt for a "Modern C++" philosophy where the public API of a system makes direct use of standard C++ library types where possible.
+For example, strings are passed as `std::string`s, cases that need polymoprhism expose `class` hierarchies, types that benefit from reference-counted lifetime management, explicitly uses `std::shared_ptr<...>`, and errors are signaled by `throw`ing exceptions.
+The Falcor API ascribes to aspects of this approach.
+The biggest challenges with Modern C++ APIs are:
+
+* Source compatibility can usually be achieved, but binary compatibility is hard to achieve even *within* a version of a system, must less across versions. The central problem is that C++ ABIs are often compiler-specific (rather than standard on a platform), and even for a single compiler like gcc or Visual Studio, the binary interface to the C++ standard library can and does break between versions.
+
+* While C++ exceptions are a built-in error-handling scheme, they are almost universally disliked among the kinds of developers who use APIs like Slang. Enabling exceptions in most compilers adds overhead, and actually using exceptions for their intended purpose (catching and handling errors) tends to be onerous.
+
+* Reference-counted lifetime management in Modern C++ relies on standard library types like `std::shared_ptr` - a type that is both inefficient and inconvenient. Most developers in our target demographic end up using "intrusive" reference counts (when they use reference-counting at all) because they are more efficient and convenient.
+
+COM is first and foremost an idiomatic way of using C++ to define APIs that are reasonably convenient while also dealing with the recurring problems of typical C and C++ approaches.
+COM defines rules for error handling, memory management, and interface versioning that all compatible APIs can use.
+While code using COM-based APIs is often verbose, it is largely consistent across all such APIs.
+
+A key place where COM does *not* provide a complete answer is around fine-grained "extensibility" of APIs, of the kind that commonly occurs with GPU APIs like OpenGL, D3D, and Vulkan.
+Across such APIs, we see a wide variety of strategies to dealing with extensibility:
+
+* OpenGL uses an approach where objects are typically opaque but mutable, and a large number of fine-grained operations are used to massage an object into the correct state for use. Often the fine-grained state-setting operations are all able to use a single API entry point for key-value parameter setting (e.g., `glTexParameteri`), and a new feature can be exposed simply by defining constants for new keys and/or values. When new operations are required, they need to be queried using string-based lookup of functions.
+
+* D3D11 uses COM interfaces and "desc" structures (called "descriptors" at the time). For example, a mutable `D3D11_RASTERIZER_DESC` structure is filled in and used to create an *immutable* `ID3D11RasterizerState`. If extended features are required, a new interface like `ID3D11RasterizerState1` and/or a new descriptor type like `D3D11_RASTERIZER_DESC1` is defined. In all cases, the "desc" structure holds the union of all state that a given type supports.
+
+* Vulkan uses "desc" structures (usually called "info" or "create info" structures), which contain a baseline set of state/fields, along with a linked list of dynamically-typed/-tagged extension structures. New functionality that only requires changes to "desc" structures can be added by defining a new tag and extension structure. New operations are added in a manner similar to OpenGL.
+
+* D3D12 also uses COM interfaces and "desc" structures (although now officialy called "descriptions" to not overload the use of "descriptor" in descriptor tables), much like D3D11, and sometimes uses the same approach to extensibility (e.g., there are currently `ID3D12Device`, `ID3D12Device`, ... `ID2D12Device9`). In addition, D3D12 has also added two variations on Vulkan-like models for creating pipeline state (`ID3D12Device2::CreatePipelineState` and `ID3D12Device5::CreateStateObject`), using a notion of more fine-grained "subojects" that are dynamically-typed/-tagged and each have their own "desc".
+
+It is important to note that even with the nominal flexibility that COM provides around versioning, D3D12 has opted for a more fine-grained approach when dealing with something as complicated as GPU pipeline state.
+
+Proposed Approach
+-----------------
+
+The long/short of the proposal is:
+
+* The primary API interface to Slang (and GFX) should be COM-based and use C++. Convenient C++ features like `enum class` are usable when they do not constrain binary compatibility.
+
+* Extensibility and versioning (where appropriate) should make use of "desc"-style tagged structures. Each of Slang and GFX will define its own `enum class` for the space of tags, rather than us trying to coordinate across the APIs.
+
+* We will focus on providing "shortcut" operations in the public API that allow developers to optimize for common cases and reduce the amount of boilerplate.
+
+* We will expose a C API that wraps the COM using `inline` functions. We will attempt to make the C API idiomatic when/as possible.
+
+* We can eventually/graduatelly provide a set of C++ wrapper/utility types that can further streamline the experience of using Slang, by hiding some of the details of COM reference counting, and "desc" structs". The utility code could also translate COM-style result codes into C++ exceptions, if we find that this is desired by some users.
+
+Detailed Explanation
+--------------------
+
+At the end of this document there is a lengthy code block that sketches a possible outline for what the `slang.h` header *could* look like.
+
+Questions
+---------
+
+### Will we generate all or some of the API header? If so, what will be the "ground truth" verison?
+
+Note that Vulkan and SPIR-V benefit from having ground-truth computer-readable definitions, allowing both header files and tooling code to be generated.
+
+### Can we actually make a reasonably idiomatic C API that wraps a COM one, or should we admit defeat and have everything look like `slang_<InterfaceName>_<methodName>(...)`?
+
+Alternatives Considered
+-----------------------
+
+We haven't seriously considered many alternatives in detail, other than the possibility of a plain C API (which we have tried, but not been able to make work).
+
+Appendix: A Header of Notes
+---------------------------
+
+The following code represents an sketch of a header that tries to match this proposal (and includes a lot of its own discussion/comentary).
+
+```
+/* Slang API Header (Proposed)
+
+This file is an attempt to sketch how the API headers for both
+Slang and gfx could be organized in order to provide a nice
+experience for developers who belong to different camps in terms
+of what they want to see.
+
+Goals:
+
+* Support both C and C++ access to the API, with matching types, etc.
+
+* Able to use COM interfaces including use of inheritance/polymorphism
+
+* When compiling as C++, it should be possible to mix-and-match both C and C++ APIs
+
+Constraints:
+
+
+Questions:
+
+* Do we actually need to restrict to block comments for pedantic compatibility
+ with old C versions? Are line comments close enough to universally-supported?
+
+*/
+
+#ifdef __cplusplus
+
+/* Because of our goals above, we will actually end up with what amounts to
+two copies of the C API, depend on whether or not we are compiling as C++.
+
+We start with the C++ COM-lite API, since that is the baseline. Note that
+in this proposal, everything is being defined in the `slang` namespace,
+rather than first declaring many things as C types and then mapping that
+over to C++.
+*/
+
+namespace slang
+{
+ /* Note that in this proposal, everything is being declared in the `slang`
+ namespace first, rather than the old model of declaring various things
+ in C and then importing them into the namespace.
+ */
+
+/* Basic Types */
+
+ typedef int32_t Int32;
+ /* typedefs for basic types, as needed ... */
+
+
+/* Enumerations and Constants */
+
+ /* Non-flag enumerations will use `enum class`. If we need to support clients
+ using older C++ versions/compilers, we can discuss macro-based ways to try
+ to work around this.
+
+ Except in cases where there is an *extremely* compelling reason to do something
+ different, all enumerations use the `int` tag type that is the default for
+ `enum class`.
+ */
+
+
+ /** Severity of a diagnostic generated by the compiler.
+ ...
+ */
+ enum class Severity
+ {
+ Note, /**< An informative message. */
+ /* ... */
+ };
+
+ /* TODO: We need a clearly-defined policy for how to handle "flags" enumerations.
+
+ My strong opinion is that we should generally avoid flags in public API just
+ because of the tendency to run out of bits sooner or later, but I also understand
+ the appeal...
+ */
+
+ struct SlangTargetFlag
+ {
+ enum : UInt32
+ {
+ DumpIR = 1 << 0,
+ /* ... */
+ };
+ };
+ typedef UInt32 SlangTargetFlags;
+
+ /* I'm note sure about whether the `Result` type ought to be declared with `enum class`.
+ It would be nice to have the extra level of type safety, but it would also make our
+ `Result` incompatible with macros and template types that are intended to work with
+ `HRESULT`s.
+ */
+ enum Result : Int32
+ {
+ /* I *do* think we should go ahead and define all the cases of `Result`s
+ that we expect our API to traffic in right here in the `enum`, so that users
+ can easily inspect result codes in the debugger.
+ */
+
+ OK = 0,
+ /* ... */
+ };
+
+/* Forward Declarations */
+
+ /* Simple Types */
+ struct UUID;
+ /* ... */
+
+ /* "Desc" Types */
+ struct SessionDesc;
+ /* ... */
+
+ /* Interface Types */
+ struct ISession;
+
+
+/* Structure Types */
+
+ /* Theres's not much to say for the easy case... */
+
+ struct UUID
+ {
+ uint32_t data1;
+ uint16_t data2;
+ uint16_t data3;
+ uint8_t data4[8];
+ };
+ /* ... */
+
+ /* The more interesting bit is "descriptor" sorts of structures, which
+ we've done a lot of back-and-forth on how best to support.
+
+ I'm going to write up something here while also acknowledging that picking
+ a good policy for how to handle this stuff is an orthogonal design choice.
+ */
+
+ enum class DescType : UInt32
+ {
+ None,
+ SessionDesc,
+ /* ... */
+ };
+ enum class DescTag : UInt64;
+
+ #define SLANG_MAKE_DESC_TAG(TYPE) DescTag(UInt64(slang::DescType::TYPE) << 32 | sizeof(slang::##TYPE))
+
+ struct SessionDesc
+ {
+ DescTag tag = SLANG_MAKE_DESC_TAG(SessionDesc);
+
+ TargetDesc const* targets = nullptr;
+ Int targetCount = 0;
+ /* ... */
+
+ /* Note: There is some subtlety here if we use default member
+ initializers here, but also want to expose these types directly
+ via the C API (in cases where somebody is using the C API but
+ a C++ compiler).
+
+ The tag approach here is intended to support something akin to
+ the Vulkan style, when using the C API:
+
+ SlangSessionDesc sessionDesc = { SLANG_DESC_TAG_SESSION_DESC };
+ ...
+
+ That code will not compile under C++11, because of the default
+ members initializers in `slang::SessionDesc`, but it *will* compile
+ under C++14 and later.
+
+ If we want to deal with C++11 compatiblity in that case, we can, but
+ it would slightly clutter up the way we declare these things. Realistically,
+ we'd just split the two types:
+
+ struct _SessionDesc { ... data but no initialization ... };
+ struct SessionDec : _SessionDesc { ... put a default constructor here ... };
+
+ I'm not a fan of that option if we can avoid it.
+ */
+ };
+
+ /* ... */
+
+ /* *After* all the "desc" types are defined, we can actually define the enum
+ for their tags (just to make life easier for users looking at things in their
+ debugger.
+ */
+ enum class DescTag : UInt64
+ {
+ None = 0,
+ SessionDesc = SLANG_MAKE_DESC_TAG(SessionDesc),
+ /* ... */
+ };
+
+ /* Versioning: If we are in a situation where we'd like to change a type that
+ has already been tagged, we should first consider just creating an additional
+ "extension" desc type, to be used together with the original. By adding
+ suitable convenience APIs, we can make this easy to work with.
+
+ If we really do decide that we want a new version of a specific desc, we should
+ start by doing the thing D3D does, and make a new numbered type:
+
+ struct SessionDesc { ... the original ... };
+ struct SessionDesc1 { ... the new one ... };
+
+ When possible, the new type should use matching field names/types and ordering.
+ Even if we are just adding fields, we should not try using inheritance (just
+ because the C++ spec doesn't guarantee enough about how inheritance is implemented).
+
+ The new structure types should get its own desc type/tag, distinct from the original.
+
+ If we decide that we want clients to compile against the latest version of these
+ types by default, we can shuffle around the names, but we need to be careful to
+ *also* shuffle the `DescType` cases (so that the binary values stay the same):
+
+ struct SessionDesc0 { ... the original ... };
+ struct SessionDesc1 { ... the new one ... };
+ typedef SessionDesc SesssionDesc1;
+
+ At the point where we introduce a second version, it is probably the right time
+ to enable developers to lock in to any version they choose. In the code above
+ the user can always just use `SessionDesc0` or `SessionDesc1` explicitly, or they
+ can just stick with `SessionDesc` in the case where they always want the latest
+ at the point they compile.
+
+ (If we wanted to get really "future-proof" we'd define every struct with the `0`
+ prefix right out of the gate, and always have the `typedef` in place. I'm not conviced
+ that would ever pay off.)
+
+ I expect most of this to be a non-issue if we are zealous about using fine-grained
+ rather than catch-all descriptors at this level of the API.
+
+ (There's more I could talk about here, but this isn't supposed to be the topic at hand)
+ */
+
+
+/* Interfaces */
+
+ /* There's an open question of how to name the `IUnknown` equivalent
+ once things are all namespaced. We could use `slang::IUnknown`, but I fear
+ that could lead to complications or confusion for codebases that also use
+ MS-provided COM-ish APIs.
+ */
+
+ struct ISession : public ISlangUnknown
+ {
+ SLANG_COM_INTERFACE(...);
+
+ /* In order to maximize our ability to evolve the API while maintaining
+ binary compatibility, I'm going to recommend the somewhat bold step
+ of defaulting to making interface entry points that are "implementation
+ details" rather than intended for direct use in most cases.
+ */
+
+ virtual SLANG_NO_THROW Result SLANG_MCALL _createCompileRequest(
+ void const* const* descs,
+ Int descCount,
+ UUID const& uuid,
+ void** outObject) = 0;
+
+ /* Instead, most users will direclty call the operations only through
+ wrappers that provide conveniently type-safe behavior:
+ */
+ inline Result createCompileRequest(
+ CompileRequestDesc const& desc,
+ ICompileRequest** outCompileRequest)
+ {
+ return _createCompileRequest(
+ &desc, 1, SLANG_UUID_OF(ICompileRequest),
+ (void**)outCompileRequest);
+ }
+
+ /* An important property of this design is that we can easily define
+ convenience overloads that take direct parameters for common cases:
+ */
+ inline Result createCompileRequestForPath(
+ const char* path,
+ ICompileRequest** outCompileRequest)
+ {
+ ...;
+ }
+
+ /* Versioning: Note that we can easily define convenience overloads
+ for multiple versions of descriptor types (`CompileRequest` and
+ `CompileRequest`), or for *combinations* of descriptor types:
+ */
+ inline Result createCompileRequest(
+ CompileRequestDesc const& requestDesc,
+ ExtraFeatureDesc const*& extraFeatureDesc,
+ ICompileRequest** outCompileRequest)
+ {
+ void* descs[] = { &requestDec, &extraFeatureDesc };
+ return _createCompileRequest
+ descs, 2, SLANG_UUID_OF(ICompileRequest),
+ (void**)outCompileRequest);
+ }
+
+ /* As a final detail, we should consider whether to support overloads
+ that work with either our `slang::ComPtr<T>` *or* an application's own
+ smart pointer type.
+
+ The user could override the smart pointer type by defining macros.
+ The defaults would be:
+
+ #define SLANG_SMART_PTR(TYPE) slang::ComPtr<TYPE>
+ #define SLANG_SMART_PTR_WRITE_REF(PTR) ((PTR).writeRef())
+ */
+#ifndef SLANG_DISABLE_SMART_POINTER_OVERLOADS
+ inline Result createCompileRequest(
+ CompileRequestDesc const& desc,
+ SLANG_SMART_PTR(ICompileRequest)* outCompileRequest)
+ {
+ return _createCompileRequest(
+ &desc, 1, SLANG_UUID_OF(ICompileRequest),
+ SLANG_SMART_PTR_WRITE_REF(*outCompileRequest));
+ )
+#endif
+
+ /* If we ever have cases where we want to support utility/wrapper operations
+ of higher complexity than what we feel comfortbale making `inline` (that is,
+ stuff that might be best off in a `slang-utils` static library) we could conceivably
+ handle those via judicious use of `extern`:
+ */
+ inline Result createCompileRequestFromJSON(
+ char const* jsonBlob, //< a serialized form of the compilation state
+ ICompileRequest** outCompileRequest,
+ {
+ extern Result slang_ISession_createCompileRequestFromJSON(
+ char const*, ICompileRequest**);
+ return slang_ISession_createCompileRequestFromJSON(jsonBlob, outCompileRequest);
+ }
+ /* I doubt we'd ever really need that kind of approach, and would always decide
+ that functionality either belongs in core Slang (perhaps as a new derived interface)
+ or can go as global (non-member) functions in a utility library.
+ */
+
+ /* ... */
+ }
+
+ /* Note: I'm assuming here that we continue our implicit contract in terms
+ of versioning of COM interfaces:
+
+ * Every interface is thought of as having a contract about who can *provide*
+ and who can *consume* it. For many (like `ISession`) only the Slang implementation
+ is supposed to provide it and only users consume it. Some callback interfaces
+ go the other way, and a few (like `slang::IBlob`) need to go both directions.
+
+ * For interfaces that Slang provides and the user consumes, we can append new
+ `virtual` methods onto the end. This realistically needs a check somewhere, such
+ that we fail creation of the `IGlobalSession` if the user compiled against a
+ header that exposes the new method but is linking a DLL that doesn't. This is
+ what the `SLANG_API_VERSION` in the original header is supposed to be for: we
+ should increment it when we expand the API contract, and the global-session
+ creation should fail if the application is asking for too new of a version.
+
+ * For interfaces that Slang consumes, we cannot realistically add/remove anything.
+ Theoretically, we could delete some of the `virtual` methods if we no longer
+ expect to call them, but that could still break client code that uses `override`
+ on their definitions.
+
+ * We need to try very hard not to change the interface types of parameters to
+ non-wrapper COM methods, even if the result should be binary-compatible. There
+ are cases where it might be reasonable and "type-safe," but each and every one
+ probably needs clear auditing.
+
+ Ideally the rules we use for Slang-provided interfaces can help us avoid the
+ proliferation of `IThing`, `IThing2`, `IThing3`, etc. We need to be careful about
+ that in the long run, though, because we may find that it causes problems in the wild
+ if software needs to interact with Slang in a context where the developer cannot
+ control the version of the Slang DLL they will be using at runtime.
+
+ In theory, we could solve that problem by letting a user pass `SLANG_API_VERSION`
+ *in* to the header via a `#define`, and have us skip over any declarations introduced
+ after the given version.
+ */
+}
+
+/* Back outside the namespace (but still in the case where we know C++ is
+supported), we can define the C-compatible API by using the C++ API
+as its underlying representation.
+*/
+
+extern "C"
+{
+
+/* Basic Types */
+
+ /* The C-API types can just be `typedef`s of the C++ ones */
+
+ typedef slang::Int32 SlangInt32;
+ /* ... */
+
+/* Enumerations and Constants */
+
+ /* For the case where we *know* a C++ compiler is being used, we
+ can actually use the C++ `enum class` declarations to provide
+ the enumerated types of the C API.
+ */
+ typedef slang::Severity SlangSeverity;
+
+ /* We can use macros to define the C-API enum cases, while still
+ preserving the type safety of the `enum class` approach.
+
+ Note: We could also use `static const`s here, but that seems like
+ overkill.
+ */
+ #define SLANG_SEVERITY_NONE (slang::Severity::None)
+ /* ... */
+
+/* Structure Types */
+
+ /* For the case of providing the C API for a C++ compiler, we can
+ directly use the C++ structure types in all cases. */
+
+ typedef slang::UUID SlangUUID;
+ typedef slang::SessionDesc SlangSessionDesc;
+ /* ... */
+
+/* Interfaces */
+
+ /* Because we are compiling as C++, defining the types for the interfaces
+ is easy, and we can easily pass objects between modules/files that are
+ using the two version of the API without any casting:
+ */
+ typedef slang::ISession SlangSession;
+ /* ... */
+
+ /* In order to have a plain-C API, the user of course needs a way to
+ dispatch into those interfaces.
+
+ Note: There is a big question here of whether the API header should be
+ trying to define the C API functions `inline` here or not.
+
+ The argument for using `inline` is that it doesn't add any additional
+ requirements for somebody using the C API from within C/C++, compared to
+ the C++ API.
+
+ The argument *against* is that for things like binding to other languages,
+ the user would probably prefer that these operations have linkage.
+
+ Realistically, the right thing is for the header to include both declarations
+ *and* definitions, but to allow the application to conditionalize the inclusion
+ of the definitions *and* enable/disable the use of `inline` for declarations/definitions.
+ A user could use that control to compile their own linkable stub with C-compatible
+ functions.
+ */
+
+ /* We need to provide the fully-general version of the function, for clients
+ that might need it, but we probably don't want that to be the first one users
+ reach for.
+ */
+ inline SlangResult _SlangSession_createCompileRequest(
+ SlangSession* session,
+ void const* const* descs,
+ SlangInt descCount,
+ SlangUUID const* uuid,
+ void** outObject)
+ {
+ return session->_createCompileRequest(descs, descCount, uuid, outObject);
+ }
+
+ /* The catch here is that the C++ API used overloading as a way
+ to provide convenient wrappers around the fully-general core operations,
+ and also to provide versioning support.
+
+ We could define the same set of overloads here, with the same names, for
+ use by clients who don't actually care about C compatiblity but just like
+ a C-style API. That is probably worth doing.
+
+ Otherwise, we realistically need to start defining some de-facto naming
+ scheme and/or versioning for stuff in the C API. At least one wrapper
+ should be "blessed" as the default one.
+ */
+ inline SlangResult SlangSession_createCompileRequest(
+ SlangCompileRequestDesc const* desc,
+ SlangCompileRequest** outCompileRequest)
+ {
+ return session->_createCompileRequest(*desc, outCompileRequest);
+ }
+
+ /* Note that we need/want to provide wrappers for *all* the operations
+ on each interface, even the ones they inherit. E.g.:*/
+ inline uint32_t SlangSession_addRef(
+ SlangSession* session)
+ { ... }
+ /* The reason for this is so that a pure-C user doesn't *have* to rely on
+ implicit conversion of these types to their bases (which in this path is
+ made possible via C++ features, but wouldn't be available in a true pure-C
+ world).
+ */
+
+ /* If/when we start to deal with versioning of either the "desc" type or
+ the interface involved in such an operation, we will need to do the numeric-suffix
+ thing or similar stuff to distinguish the old and new functions.
+
+ We can probably do some work to always make the latest version (or at least
+ the one we want users to be using) have the short/clean name. Binary compatibility
+ shouldn't actually break so long as the signature of the new function can technically
+ handle calls of the old form (since the COM-level bottleneck function won't care about
+ the static types of descs - just their tags).
+ */
+
+ /* Finally, the C API level is where we should define the core factory entry
+ point for creating and initializing the Slang global session (just like
+ in the current header). Here we jsut generalize it for creaitng "any" global
+ object, based on a UUID and a bunch of descs.
+ */
+ SLANG_API SlangResult slang_createObject(
+ void const* const* descs,
+ Int descCount,
+ UUID const* uuid,
+ void** outObject);
+
+ /* The actual global session creation is then a wrapper like everything else.
+ */
+ inline SlangResult SlangGlobalSession_create(
+ SlangGlobalSessionDesc const* desc,
+ SlangGlobalSession** outGlobalSession)
+ {
+ return slang_createObject(
+ &dec, 1, SLANG_UUID_OF(slang::IGlobalSession), (void**)outGlobalSession);
+ }
+}
+
+#else
+
+/* All of the above declarations (even the C-level ones) only work if we are
+compiling as C++. Thus we need a distinct strategy to define everything in the
+case where we are compiling as pure C.
+
+The basic strategy isn't that hard: we just do things the raw C way.
+There will be a lot of repetition involved, but this proposal assumes we are
+generating as much of the API as possible anyway.
+*/
+
+/* Basic Types */
+
+ /* We just define the basic types direclty, without the indirection
+ through the declarations in the `slang::` namespace.
+ */
+
+ typedef int32_t SlangInt32;
+ /* ... */
+
+/* Enumerations and Constants */
+
+ /* Every enum in this case is a `typedef` plus an actual `enum`:
+ */
+
+ typedef int SlangSeverity;
+ enum
+ {
+ SLANG_SEVERITY_NONE = 0,
+ /* ... */
+ };
+
+ /* ... */
+
+/* Structure Types */
+
+ /* The simple case stays simple, just with the gross bit of
+ duplicating a *lot* of what we already had in the C++ API.
+
+ (There's a big design question here of whether we can/should try
+ to remove as much duplication as possible in order to reduce
+ boilerplate, even if it comes at the cost of clarity because of
+ heavier reliance on macros, etc.)
+ */
+
+ struct SlangUUID
+ {
+ uint32_t data1;
+ uint16_t data2;
+ uint16_t data3;
+ uint8_t data4[8];
+ };
+ /* ... */
+
+ /* The desc-related stuff is really just a translation of the
+ same basic ideas to plain C: */
+
+ typedef SlangUInt32 SlangDescType;
+ enum
+ {
+ SLANG_DESC_TYPE_NONE = 0,
+ SLANG_DESC_TYPE_SessionDesc,
+ /* ... */
+ };
+ typedf SlangUInt64 SlangDescTag;
+
+ #define SLANG_MAKE_DESC_TAG(TYPE) SlangDescTag(UInt64(SLANG_DESC_TYPE_##TYPE) << 32 | sizeof(Slang##TYPE))
+
+ struct SlangSessionDesc
+ {
+ SlangDescTag tag;
+
+ SlangTargetDesc const* targets;
+ SlangInt targetCount;
+ /* ... */
+ };
+ /* ... */
+
+ #define SLANG_DESC_TAG_SESSION_DESC SLANG_MAKE_DESC_TAG(SessionDesc)
+ /* ... */
+
+/* Forward Declarations */
+
+ typedef struct SlangSession SlangSession;
+ /* ... */
+
+/* Interfaces */
+
+ /* There's already a lot known about how to define COM interfaces for
+ consumption from C, so this is actually mostly straightforward.
+
+ Note: these definitions would *only* be needed in the case where we
+ are compiling the actual implementations of the C API functions. It
+ is possible that we can/should just not bother with these, under
+ the assumption that anybody who wants a true pure-C API probably wants
+ a linkable "stub" library anyway, in which case we can provide that
+ library ourselves, and compile it as C++.
+ */
+
+ /* TODO: The big thing I'm skipping here is setup for the UUIDs.
+ I think we can provide C-compatible macros for those pretty easily,
+ but exactly what that should look like is maybe more complicated. */
+
+ struct SlangSession
+ {
+ /* The long/short is that we define a pointer field to a struct
+ of function pointers, which matches the expected C++ virtual
+ function table layout.
+ */
+
+ struct
+ {
+ /* Note: methods from all base interfaces need to go here... */
+
+ SLANG_NO_THROW SlangResult SLANG_MCALL (*_createCompileRequest)(
+ SlangSession* session,
+ void const* const* descs,
+ SlangInt descCount,
+ SlangUUID const* uuid,
+ void** outObject);
+
+ /* ... */
+
+ } * vtbl;
+ };
+ /* ... */
+
+ /* With the core type declarations out of the way, the actual functions
+ that forward to it are easy enough:
+ */
+ inline SlangResult _SlangSession_createCompileRequest(
+ SlangSession* session,
+ void const* const* descs,
+ SlangInt descCount,
+ SlangUUID const* uuid,
+ void** outObject)
+ {
+ /* The only interesting complications here are the `->vtbl`
+ and the need to pass `session` explicitly. We could probably
+ macro away the difference if we don't want to have distinct
+ C-API-compiled-via-C++ and C-API-compiled-via-C cases.
+ */
+ return session->vtbl->_createCompileRequest(
+ session, descs, descCount, uuid, outObject);
+ }
+
+ /* The declarations of the global session creation stuff are almost
+ identical, so there's no real need to dpulicate it here.
+ */
+
+ /* For the true pure-C users, we probably want to provide convenience
+ functions and/or macros to enable the casts that should be statically
+ possible.*/
+ inline SlangUnknown* SlangSession_asUnknown(SlangSession* session)
+ {
+ return (SlangUnknown*) session;
+ }
+ /* ... */
+
+
+/*
+
+Okay, so that's the basic idea of the proposal for how to expose our API(s).
+
+I realize this didn't get into the actual details of type hierarchies or what
+the actual "desc" types need to be for Slang and gfx. The focus here was much
+more on the syntactic side of things, in terms of how we can define our API
+so that both C and C++ are usable and can be freely intermixed within a codebase.
+
+*/
+
+/* There's probably an entire additional document that could be written about
+utility/wrapper stuff to make the interfaces nicer for C++ users. Some examples
+follow:
+
+We could consider having a hierarchy of wrapper smart-pointer types that codify the
+reference-counting policies without the user having to really think about `ComPtr<T>` stuff:
+
+ struct Unknown
+ {
+ public:
+ // typical stuff...
+
+
+ protected:
+ slang::IUnknown* _ptr = nullptr;
+ }
+
+ struct Session : Unknown
+ {
+ public:
+ ISession* get() const { return (ISession*)_ptr; }
+ operator ISession*() const { return get(); }
+
+ Result createCompileRequest(
+ CompileRequestDesc const& desc,
+ CompileRequest* outCompileRequest)
+ { ... }
+ }
+
+Another thing to consider is whether any of our COM-ish wrappers should allow for
+use of exceptions instead of `Result`s:
+
+ struct ISession : ...
+ {
+ ...
+
+#if SLANG_ENABLE_SMART_PTR
+ ...
+
+ #if SLANG_ENABLE_EXCEPTIONS
+ SLANG_SMART_PTR(ICompileRequest) createCompileRequest(
+ CompileRequestDesc const& desc)
+ {
+ SLANG_SMART_PTR(ICompileReqest) compileRequest;
+ SLANG_THROW_IF_FAIL(_createCompileRequest(
+ &desc, 1, SLANG_UUID_OF(IComileRequest), comileRequest.writeRef()));
+ return compileRequest;
+ }
+
+ ...
+ #endif
+#endif
+ }
+
+Both for the sake of C API and especialy for gfx (both C and C++), we should consider
+defining some coarse-grained aggregate desc types as utilities:
+
+ struct SimpleRasterizationPipelineStateDesc
+ {
+ // sub-descs for all the relevant pieces:
+ //
+ PipelineProgramDec program;
+ DepthStencilDesc depthStencil;
+ MultisampleDesc multisample;
+ PrimitiveTopologyDesc primitiveTopology;
+ NVAPIDesc nvapi;
+ // ...
+
+ // "fluent"-style setters for all the relevant pieces:
+
+ SimpleRasterizationPipelineStateDesc& setEnableDepthTest(bool value)
+ {
+ markDepthStencilDescUsed();
+ depthStencil.enableDepthTest = value;
+ return *this;
+ }
+
+ // ...
+
+ // This is also the logical granularity to provide things like
+ // List<T> members for attachments, etc. rather than just pointer-and-count:
+
+ private: List<AttachmentDesc> colorAttachments;
+ public: AttachmentDesc& addColorAttachement();
+
+ // There should also be convenience constructors common cases
+ // (especially relevant for things like textures).
+
+ // In the simplest implementation strategy, we keep a bitmask for which
+ // of the sub-descs have actually beem used (either requested by the user,
+ // or set to non-default values):
+ //
+ enum class SubDesc { Program, DepthStencil, ... Count };
+ uint32_t usedSubDescs = 0;
+ void markSubDescUsed(SubDesc d)
+ {
+ uint32_t bit = 1 << int(d);
+ if(usedSubDesc & bit) return;
+
+ usedSubDescs |= bit;
+ updatePointers();
+ }
+
+ // We then maintain a compacted array of all the sub-descriptors needed
+ // to form the combined state for passing along to the lower-level API.
+ //
+ void* subDescs[int(SubDesc::Count)];
+ int subDescCount = 0;
+
+ void updatePointers()
+ {
+ subDescCount = 0;
+ if(usedSubDescs & (1 << int(Program)))
+ {
+ subDescs[subDescCount++] = &program;
+ }
+ /// ...
+ }
+ };
+
+While the implementation of this monolithic desc types would not necessarily be pretty,
+it would enable users who want the benefits of the "one big struct" appraoch to get
+what they seem to want.
+
+The next step down this road is to take these aggregate desc types and turn them into
+actual API objects for the purposes of the C API, so that users can more conveniently
+create stuff:
+
+ GFXRasterizationPipelineStateBuilder* GFXDevice_beginCreateRasterizationPipelineState(
+ GFXDevice* device);
+
+ void GFXRasterizationPipelineStateBuilder_setEnableDepthTest(
+ GFXRasterizationPipelineStateBuilder* builder,
+ bool enable);
+
+ // Note: frees the given `builder`, so user doesn't have to do it manually
+ GFXPipelineState* GFXRasterizationPipelineStateBuilder_create(
+ GFXRasterizationPipelineStateBuilder* builder);
+
+Obviously the function names are very verbose there, but they could probably be cleaned
+up a lot if we want to go down this route. Certainly, if we decide that C API users are
+not going to be inclined to use a lot of fine-grained descs, this starts to seem like
+an increasingly attractive way to go.
+*/
+
+#endif
+``` \ No newline at end of file
diff --git a/docs/proposals/003-error-handling.md b/docs/proposals/003-error-handling.md
new file mode 100644
index 000000000..28652824f
--- /dev/null
+++ b/docs/proposals/003-error-handling.md
@@ -0,0 +1,296 @@
+Error Handling
+==============
+
+Slang should support a modern system for functions to signal, propagate, and handle errors.
+
+Status
+------
+
+In discussion.
+
+Background
+----------
+
+Errors happen. It is impossible for any programming language to statically rule out the possibility of unexpected situations arising at runtime.
+There are a wide variety of strategies used in programming, both provided by languages and enforced by idiom in codebases.
+
+Not all errors are alike, in that some are more expected and reasonable to handle than others.
+Most errors can fit into a few broad categories like:
+
+* Unrecoverable or nearly unrecoverable failures like resource exhaustion (out of memory), or an OS-level signal to terminate the process.
+
+* Incorrct usage of an API in ways that violate invariants. For example, passing a negative value to a function that says it only accepts positive values.
+
+* Out-of-range or otherwise invalid data coming from program users. For example, a console program asks the user to type a number, but the user enters some string that does not parse as a number.
+
+* Failure of operation that will usually succeed, but for which exceptional circumstances can lead to failures. For example, when reading from an open file we typically expect success, but failure is possible for many reasons outside of a programmer's control (like network disruption when accessing a remote file). A robust program often wants to recover from such failures, but often the policy for how recovery should occur is at a higher level than the code that first detects the error.
+
+These different categories often benefit from different strategies:
+
+* Typically there is neither a reason nor a desire to do anything about nearly-unrecoverable errors; the program has well and truly crashed.
+
+* When programmers violate the invariants of an API, they typically want to know about it as early as possible (during development) so they can fix their code. Breaking into the debugger is often the best answer, and in many cases trying to propagate or recover from such failures would be wasted effort.
+
+* When an operation could fail due to mal-formed data coming from a user, programmers typically want to be forced to handle the failure case at the point where the error may arise. In languages that have an `Optional<T>` or `Maybe<T>` type, it is often easiest to return that.
+
+* Unpredictable, exceptional, and recoverable errors are among the hardest to deal with, and often benefit from direct language support.
+
+The Slang language currently doesn't have direct support for *any* form of error handling, but this document focuses on errors in the last of the categories above.
+
+Related Work
+------------
+
+In the absence of language support, developers typically signal and propagate errors using *error codes*. The COM `HRESULT` type is a notable example of a well-defined system for using error codes in C/C++ and other languages.
+Error codes have the benefit of being easy to implement, and relatively light-weight.
+The main drawback of error codes is that developers often forget to check and/or propagate them, and when they do remember to do so it adds a lot of boilerplate.
+Additonally, reserving the return value of every function for returning an error code makes code more complex because the *actual* return value must be passed via a function parameter.
+
+C++ uses *exceptions* for errors in various categories, including unpredictable but recoverable failures.
+Propagation of errors up the call stack is entirely automatic, with unwinding of call frames and destruction of their local state occuring as part of the search for a handler.
+Neither functions that may throw nor call sites to such functions are syntactically marked.
+Exceptions in C++ have often been implemented in ways that add overhead and require complicated support in platform ABIs and intermediate languages to support.
+
+Java uses exceptions with similar rules to C++, but adds a restriction that functions must be marked with the types of exceptions they may throw or propagate, except for those that inherit from `RuntimeException`, which are intended to represent some of the other categories of error in our taxonomy (like simple invariant violations and nearly unrecoverable errors).
+The need to mark every function that might fail (or propagate failure) was seen by most developers at the time as unreasonably onerous.
+Developers often smuggled other kinds of exceptions out through `RuntimeException`s, to get them through API layers that were not designed to support exceptions.
+
+Both Rust and Swift try to strike a balance between error codes and languages with exceptions.
+At a high level, each takes an approach where the generated code is comparable to an error-code-based solution (so that no special ABI or IL support is needed), but direct syntactic support makes propagating and/or handling errors more convenient.
+
+In Rust, a function that returns `std::Result<SomeType, SomeError>` either returns successfully with a value of type `SomeType`, or fails with an error of type `SomeError`.
+The `Result` type is itself just a Rust `enum`, so that results can be handled by pattern-matching with `match`, `if let`, etc.
+Direct syntactic support is added so that in the body of a `Result`-returning function, a postfix `?` operator can be applied to an expression of type `Result<X, E>` to implicitly propagate `E` on any failure, and return the `X` value otherwise.
+Some higher-order functions can Just Work with `Result`-returning functions, if their signatures are compatible, but many operations like `map()`, `fold()`, etc. need distinct overloads that support `Result`s.
+Functions that return `X` and those that return `Result<X, ...>` are not directly convertible.
+
+Swift provides more syntactic support for errors than Rust, although the underlying mechanism is similar.
+A Swift function may have `throws` added between the parameter list and return type to indicate that a function may yield an error.
+All errors in Swift must implement the `Error` protocol, and all functions that can `throw` may produce any `Error` (although there are proposals to extend Swift with "typed `throws`").
+Any call site to a `throws` function must have a prefix `try` (e.g., `try f(a, b)`), which works simiarly to Rust's `?`; any error produced by the called function is propagated, and the ordinary result is returned.
+Swift provides an explicit `do { ... } catch ...` construct that allows handlers to be established.
+It also provides for conversion between exceptions and an explicit `Result<T,E>` type, akin to Rust's.
+Higher-order functions may be declared as `rethrows` to indicate that whether or not they throw depends on whether or not any of their function-type parameters is actually a `throws` function at a call site.
+Any non-`throws` function/closure may be implicitly converted to the equivalent `throws` signature, so that non-throwing functions are subtypes of the throwing ones.
+
+
+The model used in Swift is compatible with the more general notion of *effects* in type theory.
+A simple model of function types like `D -> R` can be extended to support zero or more effects `E0`, `E1`, etc. that live "on the arrow": `D -{E0, E1}-> R`.
+Purely functional languages like Haskell sometimes use monads as a way to represent effects: a function `D -> IO R` is effectively a function from `D` to `R` with the addition effect that it may perform IO.
+Making effects more explicit allows a type system to reason about sub-typing in the presence of effects (a function type without effect `E` is a subtype of a function with that effect), and to express code that is generic over effects.
+
+Proposed Approach
+-----------------
+
+We propose a modest starting point for error handling in Slang that can be extended over time.
+The model borrows heavily from Swift, but also focuses on strongly-typed errors.
+
+The standard library will provide a built-in interface for errors, initially empty:
+
+```
+interface IError {}
+```
+
+User code can define their own types (`struct` or `enum`) that conform to `IError`:
+
+```
+enum MyError : IError
+{
+ BadHandle,
+ TimedOut,
+ // ...
+}
+```
+
+User-defined functions (in both traditional and "modern" syntax) will support a `throws ...` clause to specify the type of errors that the function may produce:
+
+```
+float f(int x) throws MyError { ... }
+
+func g(x: int) throws -> float MyError { ... }
+```
+
+Call sites to a `throws` function must wrap any potentially-throwing expression with a `try`:
+
+```
+float g(int y) throws MyError
+{
+ return 1.0f + try f(y-1);
+}
+```
+
+Code can explicitly raise an error using a `throw` expression:
+
+```
+throw MyError.TimedOut;
+```
+
+We will allow `catch` clauses to come at the end of any `{}`-enclosed scope, where they will apply to any errors produced by `throw` or `try` expressions in that scope.
+
+```
+{
+ ...
+ try f(...);
+ ...
+
+ catch( e: MyError ) { ... }
+}
+```
+
+We will also want to add `defer` statements, as they are defined in Go, Rust, Swift, etc.
+The statements under a `defer` will always be run when exiting a scope, even if exiting as part of error propagation.
+
+Detailed Explanation
+--------------------
+
+Consider a function that uses most of the facilities we have defined:
+
+```
+float example(int x) throws MyError
+{
+ if(someCondition)
+ {
+ throw MyError.TimedOut;
+ }
+ ...
+ defer { someCleanup(); }
+ ...
+ {
+ let y : int = 1 + try g(...);
+
+ catch(e : MyError)
+ { ... }
+ }
+ ...
+ return someValue;
+}
+```
+
+We will show how a function in this form can be transformed via incremental steps into something that can be understood and compiled without specific support for errors.
+
+### Change Signature
+
+First, we transform the signature of the function so that it returns something akin to an `Optional<MyError>` and returns its result via an `out` parameter, and modify any `return` points to write the `out` parameter and return `null` (the not-present case of `Optional<T>`):
+
+```
+MyError example_modified(int x, out float _result)
+{
+ ...
+
+ _result = someValue;
+ return null;
+}
+```
+
+### Desugar `try` Expressions
+
+Next we can convert any `try` expressions into a more explicit form, to match the transformation of signature. A statement like this:
+
+```
+let y : int = 1 + try g(...);
+```
+
+transforms into something like:
+
+```
+var _tmp : int;
+let _err : Optional<MyError> = g_modified(..., out _tmp);
+if( _err != null )
+{
+ throw _err.wrappedValue;
+}
+let y : int = 1 + _tmp;
+```
+
+### Desugar `throw` Expressions
+
+For every `throw` site in a function body, there will either be no in-scope `catch` clause that matches the type thrown, or there will be eactly one most-deeply-nested `catch` that statically matches.
+Front-end semantic checking should be able to associate each `throw` with the appropriate `catch` if any.
+
+For `throw` sites with no matching `catch`, the operation simply translates to a `return` of the thrown error (because of the way we transformed the function signature).
+
+For `throw` sites with a matching `catch`, we treat the operation a a "`goto` with argument" that jumps to the `catch` clause and passes it the error.
+Note that our IR structure already has a concept of "`goto` with arguments".
+
+### Desugar `defer` Statements
+
+Handling of `defer` statements is actually the hardest part of this proposal, and as such we should probably handle `defer` as a distinct feature that just happens to overlap with what is being proposed here.
+
+### Subtyping: Front-End
+
+We should (at some point) add a `Never` type to the Slang type system, which would be an uninhabitable type suitable for use as the return type of functions that never return:
+
+```
+func exit(code: int) -> Never; // C `exit()` never returns
+```
+
+`Never` is effectively a subtype of *every* type and, as such, an expression of type `Never` can be implicitly converted to any type.
+
+A `throw` expression has the type `Never`, allowing a user to write code like:
+
+```
+// Because `Never` can convert to `int`, this is valid:
+int x = value > 0 ? value : throw MyError.OutOfBounds;
+```
+
+A function without a `throws` clause is semantically equivalent to a function with `throws Never`.
+If we make that equivalence concrete at the type-system level, then a higher-order function can be generic over both throwing and non-throwing functions:
+
+```
+func map<D,R,E>(
+ f: (D) throws E -> R,
+ l: List<D>)
+ throws E -> List<R>;
+```
+
+A function type with `throws X` is a subtype of a function with `throws Y` if `X` is a subtype of `Y`.
+That includes the case where `X` is `Never`, so that a non-`throws` function type is a subtype of any `throws` function type with the same parameter/result signature.
+
+### Subtyping: Low-Level
+
+The subtyping relationship for `Never` *values* is irrelevant to codegen. Any place in the IR that has a `Never` value available to it represents unreachable code.
+
+The subtyping relationship for `Never` in function types is more challenging, both for result types and error types. At the most basic, we can inject trampoline/thunk functions at any points where we have a `Never`-yielding function and need a function that returns `X` to pass along.
+
+If we were doing low-level code generation for a platform where we can define our ABI, it would be possible to have `throws` and non-`throws` functions use distinct calling conventions, such that:
+
+* The orinary parameters and reuslts are passed in the same registers/locations in both conventions.
+
+* The error value (if any) in the `throws` convention is passed via registers/locations that are callee-save in the non-`throws` convention.
+
+Under that model, a call site to a potentially-`throws` function can initialize the registers/locations for the error result to `null`/zero before dispatching to the callee.
+If the callee is actually a non-`throws` function it would not touch those registers, and no error would be detected.
+In that case, a non-`throws` function/closure could be used directly as a `throws` one with no conversion.
+Such calling-convention trickery isn't really possible to implement when emitting code in a high-level language like HLSL/GLSL or C/C++.
+
+Questions
+--------------
+
+### Should we support the superficially simpler case of "untyped" `throws`?
+
+Having an `IError` interface allows us to eventually decide that `throws` without an explicit type is equivalent to `throw IError`.
+It doesn't seem necessary to implement that convenience for a first pass, especially when there are use cases for `throws` that might not want to get into the mess of existential types.
+
+### Should the transformations described here be implemented during AST->IR lowering, or at the IR level?
+
+That's a great question! My guess is that some desugaring will happen during lowering, but we will probably want to keep `throws` functions more explicitly represented in the IR until fairly late, so that we can desugar them differently for different targets (if desired).
+
+### Do we need `Optional<T>` to be supported to make this work?
+
+It is unlikely that we'd need it to be a user-visible feature in a first pass, but we might want it at the IR level.
+For this feature to work, we really need `sizeof(Optional<X>)` to be the same as `sizeof(X)` for simple cases where `X` is an `enum` or (for suitable targets) a type that is pointer-based.
+
+A first pass at the feature might only support cases where error types are `enum`s and where the zero value is the "no error" case.
+
+### Should we have a `Result<T, E>` type akin to what Rust/Swift have? Should a `throws E` function be equivalent to one that returns `Result<T, E>`?
+
+That all sounds nice, but for now it seems like overkill.
+Slang doesn't really have any facilities for programming with higher-order functions, pattern matching, etc. so adding types that mostly shine in those cases seems like a stretch.
+
+Alternatives Considered
+-----------------------
+
+We could decide that Slang shouldn't be in the business of providing error-handling sugar *at all* and make this a problem for users.
+That isn't really a reasonable plan for any modern language, but it is the status quo and null hypothesis if we don't start in on a better plan.
+
+We could try to focus on C++ interop/compatibility and decide that errors in Slang should use exceptions, and only make "proper" language-supported error handling available to platforms that support exceptions at a suitably low level.
+Doing so would give us all the disadvantages of C++ exceptions, and also mean that most of our users wouldn't end up using our error-handling tools, because doing so would render code non-portable.
diff --git a/docs/proposals/004-com-support.md b/docs/proposals/004-com-support.md
new file mode 100644
index 000000000..e0798281a
--- /dev/null
+++ b/docs/proposals/004-com-support.md
@@ -0,0 +1,240 @@
+COM Support
+===========
+
+When Slang is used as a host/CPU programming language, it is likely that users will want to use interact with COM interfaces, either by consuming them or implementing them.
+The Slang language and compiler should provide some first-class features to make working with COM interfaces feel lightweight and natural.
+
+Status
+------
+
+In discussion.
+
+Background
+----------
+
+COM is not perfect, but it is one of the only real solutions for cross-platform portable C++ APIs that care about binary compatibility and versioning.
+Developers who use Slang are likely to write code that uses COM, whether to interact with Slang itself (and/or GFX), or with platform APIs like D3D.
+
+While COM provides idioms for addressing many practical challenges, it is also inconvenient in that it introduces a lot of *boilerplate*:
+
+* COM types all need to implement the core `IUnknown` operations for casting/querying and reference counting.
+
+* Code using COM interfaces needs to perform `AddRef` and `Release` operations manually, or use smart pointer types to automate lifetime management.
+
+* Code that calls into COM interfaces typically needs to use boilerplate code and/or macros to deal with `HRESULT` error codes, handling or propagating them as needed.
+
+Our in-progress work on supporting CPU programming in Slang emphasizes supporting idiomatic code without a lot of boilerplate.
+Our intended path includes things that are compatible with the COM philosophy, like reference-counted lifetime management and idiomatic use of result/error codes, but those features don't currently align with the more explicit style used by COM in C/C++.
+
+Related Work
+------------
+
+The .NET platform includes some support for allowing .NET `interface`s and COM interfaces to interoperate.
+TODO: Need to study this and learn how it works.
+
+Proposed Approach
+-----------------
+
+We propose to allow COM interfaces to be declared using the Slang `interface` construct, with an appropriate attribute or modifier:
+
+```
+[COM] interface IDevice
+{
+ ITexture createTexture(__read TextureDesc desc) throws HRESULT;
+
+ void setTexture(int index, ITexture texture);
+}
+```
+
+A declaration like the above will translate into output C++ along the lines of:
+
+```
+struct IDevice : public IUnknown
+{
+ virtual HRESULT SLANG_MCALL createTexture(TextureDesc const& desc, ITexture** _result) = 0;
+
+ virtual void SLANG_MCALL setTexture(int index, ITexture* texture) = 0;
+};
+```
+
+Key things to note:
+
+* The `[COM] interface` becomes a C++ `struct` that inherits from `IUnknown`
+* Methods defined in the `[COM] interface` become pure-virtual `SLANG_MCALL` methods in C++
+* Parametes/values of a `[COM] interface` type `IFoo` in Slang translate to `IFoo*` in C++
+* Methods that have a `throws HRESULT` clause are transformed to have an `HRESULT` return type and an output parameter for their result
+
+A Slang `class` can declare that it implements zero or more `[COM] interface`s. Code like this:
+
+```
+class MyTexture : ITexture
+{
+ // ...
+}
+
+class MyDevice : IDevice
+{
+ ITexture createTexture(__read TextureDesc desc) throws HRESULT
+ {
+ return ...;
+ }
+
+ void setTexture(int index, ITexture texture)
+ {
+ ...;
+ }
+}
+```
+
+translates into output C++ like this:
+
+```
+class MyTexture : public slang::Object, public ITexture
+{
+ // ...
+};
+
+class MyDevice : public slang::Object, public IDevice
+{
+ virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) { ... }
+ virtual ULONG AddRef() { ... }
+ virtual ULONG Release() { ... }
+
+ HRESULT createTexture(TextureDesc const& desc, ITexture** _result) SLANG_OVERRIDE
+ {
+ _result = ...;
+ return S_OK;
+ }
+
+ void setTexture(int index, ITexture* texture) SLANG_OVERRIDE
+ {
+ ...
+ }
+}
+```
+
+All Slang `class`es translate to C++ classes that inherit from `slang::Object` (equivalent to the `RefObject` type within the current Slang implementation).
+A `class` that inherits any `[COM]` interfaces includes an implementation of `IUnknown` plus the methods to override all the interface requirements.
+
+In ordinary code that makes use of `[COM] interface` types:
+
+```
+struct Stuff
+{
+ ITexture t;
+}
+ITexture getTexture(Stuff stuff)
+{
+ return stuff.t;
+}
+ITexture someOperations(IDevice device)
+{
+ let t = device.createTexture(...);
+ return t;
+}
+```
+
+the C++ output uses C++ smart-pointer types for local variables, `struct` fields, and function results:
+
+```
+struct Stuff
+{
+ ComPtr<ITexture> t;
+};
+ComPtr<ITexture> getTexture(Stuff stuff)
+{
+ return stuff.t;
+}
+HRESULT someOperations(IDevice* device, ComPtr<ITexture>& _result)
+{
+ ComPtr<ITexture> t;
+ HRESULT _err = device->createTexture(&t);
+ if(_err < 0) return _err;
+
+ _result = t;
+ return 0;
+}
+```
+
+As a small optimization, an `in` parameter of a `[COM] interface` type translates as a C++ parameter of just the matching pointer type (see `device` above).
+
+Note that the translation of idiomatic `HRESULT` return codes into `throws HRESULT` functions in Slang allows code working with COM interfaces to benefit from the convenience of the Slang error handling model.
+
+
+Detailed Explanation
+--------------------
+
+This is a case where the simple explanation above covers most of the interesting stuff.
+
+There are a lot of semantic checks we'd need/want to implement to make sure `[COM]` interfaces are used correctly:
+
+* Any `interface` that inherits from one or more other `[COM]` interfaces must itself be `[COM]`
+
+* Any concrete type that implements a `[COM]` interface must be a `class`
+
+There are also detailed implementation questions to be answered around the in-memory layout of `class` types that implement `[COM]` interfaces.
+In particular, we might want to be able to optimize for the case of a single-inheritance `class` hierarchy that mirrors a `[COM] interface` hierarchy, since this comes up often for COM-based APIs:
+
+```
+interface IBase { void baseFunc(); }
+interface IDerived : IBase { void derivedFunc(); }
+
+class BaseImpl : IBase { ... }
+class DerivedImpl : BaseImpl, IDerived { ... }
+```
+
+Using a naive translation to C++, the `DerivedImpl` type could end up with *three* different virtual function table (`vtbl`) pointers embedded in it: one for `slang::Object`, one for `IBase`, and one for `IDerived`.
+Clearly the `vtbl`s for `IBase` and `IDerived` could be shared, but C++ `class`es can't easily express this.
+Furthermore, if we are able to tune our strategy for layout, we can set things up so that `[COM] interface`s consume `vtbl` slots starting at index `0` and counting up, while any `virtual` methods in `class`es consume slots starting at index `-1` and counting *down*.
+Using such a layout strategy we can actually allow a type like `DerivedImpl` above to use only a *single* `vtbl` pointer.
+
+Questions
+--------------
+
+### Should we emit COM code that works at the plain C level, or idiomatic C++?
+
+I honestly don't know. Emitting idomatic C++ (and using things like smart pointers) certainly makes the output code easier to understand.
+
+### Can we make this work with more advanced features of Slang interfaces?
+
+Some Slang features don't have perfect analogues. For example, given that `[COM] interface`s can only be conformed to by `class` types, the use of `[mutating]` isn't especially meaningful.
+
+There is no reason why a `[COM] interface` couldn't make use of `static` methods, but there would be no way to call those from C++ without an instance of the interface type.
+
+A `[COM] interface` could include `property` declarations, provided that we define the rules for how they translate into getter/setter operations in the generated output.
+
+One interesting case is that a `[COM] interface` could allow use of `This`, as well as `associatedtype`s that are themselves constrained by a `[COM] interface`.
+For example, we could instead device our `IDevice` interface from before as:
+
+```
+[COM] interface IDevice
+{
+ associatedtype Texture : ITexture;
+
+ Texture createTexture(__read TextureDesc desc) throws HRESULT;
+
+ void setTexture(int index, Texture texture);
+}
+```
+
+We could set things up so that the `associatedtype` has no impact on the C++ translation of `IDevice`: all parameter/result types that use the `Texture` associated type would translate to `ITexture*` in C++.
+As such, a more refined `interface` like this would not disrupt the binary interface of a COM-based API, but could be allowed to express more of the constraints of the underlying API at compile time.
+For example, the above use of `associatedtype` would prevent Slang code from mixing up textures across devices:
+
+```
+void someFunc( IDevice a, IDevice b )
+{
+ let x = a.createTexture(...);
+ let y = b.createTexture(...);
+
+ a.setTexture(0, y); // COMPILE TIME ERROR!
+}
+```
+
+In this example, `y` has type `b.Texture`, while `a.setTexture` expects an argument of type `a.Texture` (a distinct type, even if it also conforms to `ITexture`).
+The benefits of this approach are probably purely hypothetical until we make it a lot easier to work with dependent types like `a.Texture` in Slang code.
+
+Alternatives Considered
+-----------------------
+
+The main alternative would be to have Slang's model for interop with C/C++ focus primarily on C alone, and only allow use of COM-based APIs through C-compatible interfaces.
diff --git a/docs/proposals/005-components.md b/docs/proposals/005-components.md
new file mode 100644
index 000000000..b257140a7
--- /dev/null
+++ b/docs/proposals/005-components.md
@@ -0,0 +1,507 @@
+Components
+==========
+
+We propose to extend Slang with a construct for defining coarse-grained *components* that can be used to assemble shader programs.
+
+Status
+------
+
+Under discussion.
+
+Background
+----------
+
+First, a bit of terminology. In the context of a specific language like Slang, a term like "component" or "module" will often have a narrow meaning, but when we want to discuss "modularity" broadly we need to have a way to refer to the *things* we want to have be modular: the units of modularity.
+In this document we will use the term *unit* to refer abstractly to anything that is a unit of modularity for some context/purpose/system, and try to reserve other terms for cases where we mean something more specific.
+
+While Slang has many features that address modularity for "small" units, it is still lacking in constructs that adequately address the needs of "large" units.
+These are qualitative distinctions, but some examples may clarify the kind of distinction we mean.
+An interface `ILight` for light sources, and a `struct` type `OmniLight` that conforms to it are small units.
+An entire `LightSampling` module in a path tracer is a much larger unit.
+
+The main tools that Slang provides for "small" units are: `interface`s, `struct`s, and `ParameterBlock`s.
+Interfaces allow a developer to codify the types and operations that clients of a unit may rely on, as well as the requirements that implementations must provide.
+Structure types are the main way developers can implement an interface, and it is important for GPU efficiency that Slang `struct`s are *value types*.
+By using `ParameterBlock`s, developers can connect the units of modularity used *within* shader code with the parameters passed from *outside* their shaders.
+
+When we talk about "large" units of modularity, the main thing Slang provides are, well, modules.
+A Slang module is basically just a collection of global-scope declarations: types, functions, shader parameters, and entry points.
+The `import` construct allows modules to express a dependency on one another, and if/when we add `public`/`internal` visibility qualifiers it will also be able to restrict clients of a module to its defined interface.
+
+What modules *don't* provide is any of the flexibility that `interface`s provide for "small" units like `struct` types.
+There is no first-class way for a Slang programmer to define a common interface that multiple modules implement, and then to express another piece of code that can work with any of those implementations.
+Aside from falling back to preprocessor hackery (which negates many of the benefits of Slang in terms of separate compilation), the only way for developers to try to recoup those benefits is to use the tools for "small" units.
+
+Let's consider a placeholder/pseudocode set of Slang modules that work together:
+
+```
+// Lighting.slang
+
+interface ILight { ... }
+
+StructuredBuffer<ILight> gLights;
+
+void doLightingStuff() { ... }
+```
+
+```
+// Materials.slang
+import Lighting;
+
+interface IMaterial { ... }
+StructuredBuffer<IMaterial> gMaterials;
+
+void doMaterialStuff()
+{
+ doLightingStuff();
+}
+```
+
+```
+// Integrator.slang
+import Lighting;
+import Materials;
+
+ParameterBlock<IntegratorParams> gIntegratorParams;
+
+void doIntegeratorStuff()
+{
+ doLightingStuff();
+ doMaterialsStuff();
+}
+```
+
+The details of the module implementations is not the important part here.
+The key is that each module defines a collection of types, operations, and shader parameters, and there is a dependency relationship between the modules.
+Note that the `Integrator` module depends on both `Lighting` and `Materials`, but it does *not* need to be actively concerned with the fact that `Materials` *also* depends on `Lighting`.
+
+If we look only at a leaf module like `Lighting` it seems simple enough to translate it into something based on our "small" modularity features:
+
+```
+// Lighting.slang
+
+interface ILight { ... }
+
+interface ILightingSystem
+{
+ void doLightingStuff();
+}
+
+struct DefaultLightingSystem : ILightingSystem
+{
+ StructuredBuffer<ILight> lights;
+
+ void doLightingStuff() { ... }
+}
+```
+
+Here we were able to move most of the global-scope code in `Lighting.slang` into a `struct` type called `DefaultLightingSystem`.
+We were also able to define an explicit `interface` for the system, which makes explicit that we don't consider `gLights` part of the public interface of the system (only `doLighting()`).
+By defining the interface, we also create the possibility of plugging in other implementations of `ILightingSystem` - for example, we can imagine a `NullLightingSystem` that actually doesn't perform any lighting (perhaps useful for performane analysis).
+
+Translating `Materials` in the same way that we did for `Lighting` leads to some immediate questions:
+
+```
+// Materials.slang
+import Lighting;
+
+interface IMaterial { ... }
+
+interface IMaterialSystem
+{
+ void doMaterialStuff();
+}
+
+struct DefaultMaterialSystem
+{
+ StructuredBuffer<IMaterial> materials;
+
+ void doMaterialStuff()
+ {
+ /* ???WHAT GOES HERE??? */.doLightingStuff();
+ }
+}
+```
+
+When our `DefaultMaterialSystem` wants to invoke code for lighting, it needs a way to refer to the lighting system.
+Beyond that, we want it to be able to work with *any* implementation of `ILightingSystem`.
+
+A naive first attempt might be to give `DefaultMaterialSystem` field that refers to an `ILightingSystem`:
+
+```
+struct DefaultMaterialSystem
+{
+ ILightingSystem lighting;
+ ...
+ void doMaterialStuff()
+ {
+ lighting.doLightingStuff();
+ }
+}
+```
+
+An approach light that (or even one using a `ParameterBlock<ILightingSystem>`) runs into the problem that it is going to force the Slang compiler to use its layout strategy for dynamic dispatch, which cannot handle the resource types in `DefaultLightingSystem` when compiling for most current GPU targets.
+There are other problems with directly aggregating an `ILightingSystem` into our material system, but those will need to wait for a bit.
+
+If we want to allow the code in our `DefaultMaterialSystem` to statically specialize to the type of the lighting system, we end up to use generics, either by making the whole type generic:
+
+```
+struct DefaultMaterialSystem< L : ILightingSystem >
+{
+ ParameterBlock<L> lighting;
+ ...
+}
+```
+
+or by just making the `doMaterialStuff()` operation generic, with the lighting system being passed in as a parameter.
+
+```
+struct DefaultMaterialSystem
+{
+ ...
+ void doMaterialStuff< L : ILightingSystem>( L lighting )
+ {
+ lighting.doLightingStuff();
+ }
+}
+```
+
+Each of those options moves the responsibility for managing the lighting system type up a level of abstraction: whatever code works with a material system needs to manage the details.
+
+When we now step up the next level to the `Integrator` module, the approach using `struct`s really starts to show cracks.
+We have the option of making the `DefaultIntegeratorSystem` a generic on the type of *both* subsystems, and reference them via parameter blocks:
+
+```
+struct DefaultIntegratorSystem< L : ILightingSystem, M : IMaterialSystem >
+{
+ ParameterBlock<L> lighting;
+ ParameterBlock<M> material;
+ ...
+}
+```
+
+or we have to make all the relevant operations on the integrator take both subsystems as pass the relevant subsystem instances in as parameters:
+
+```
+struct DefaultIntegratorSystem
+{
+ ...
+ void doIntegeratorStuff< L : ILightingSystem, M : IMaterialSystem >(
+ L lighting,
+ M material)
+ {
+ lighting.doLightingStuff();
+ material.doMaterialsStuff(lighting);
+ }
+}
+```
+
+In each case, more and more responsibiity for configuration of implementation details is being punted up to the next higher level of abstraction.
+In the first case, somebody else is responsible for instantiating a type like:
+
+```
+DefaultIntegeratorSystem<DefaultLightingSystem, DefaultMaterialSystem<DefaultLightingSystem>>
+```
+
+Also, the application code that works with that messy type needs to make sure to fill in *one* parameter block for the lighting system, but set it into *both* the material system and integrator.
+
+In the second case, note how the integrator already has to ensure that it passes along the `lighting` subsystem to the `doMaterialStuff` operation, and anybody who invokes `doIntegratorSutff` would have to do the same, but for *two* subsystems.
+
+The whole thing doesn't scale and becomes intractable with more than a few subsystems.
+Trying to work anything like inheritance into the mix just falls flat completely.
+
+Related Work
+------------
+
+There is a lot of work in general-purpose programming languages around defining larger-scale modularity units.
+
+SML (Standard ML) has both modules and *signatures*, which are effectively interfaces for modules.
+Modules can be parameterized on other modules based on signatures, and instantiated to use different concrete implementations.
+
+Beta and gbeta unify both classes and functions into a single construct called a *pattern*, and show that patterns (including pattern inheritance) can be used for things akin to traditional modules.
+A variety of techniques for *family polymorphism* in world of Java and similar languages followed on from that tradition.
+The Scala language continues in the same vein, with papers and presentations on Scala advocating for using `class`es to model large units of modularity akin to modules.
+
+In the world of "enterprise" software using Java, C#, JavaScript, etc. there is a large family of techniques and system for "dependency inversion" which is used to automate some or all of the process of "wiring up" the concrete implementations of various subsystems/components based on explicit representations of dependencies (often attached as metadata on the fields of a type).
+
+Modern general-purpose game engines like Unity and Unreal often use a "component" concept, where a game entity/object is composed of multiple loosely-coupled sub-objects (components).
+Often these systems allow dependencies between component types to be stated explicitly, with runtime or tools support for ensuring that objects are not created with unsatisifed dependencies.
+
+Note that almost all of the approaches enumerated above rely deeply on the fact that a dependency of unit `X` on unit `Y` can be handily represented as a single pointer/reference in most general-purpose programming languages. For example, in C++:
+
+```
+class Y { ... };
+class X
+{
+ Y* y;
+ ...
+};
+```
+
+In the above, an instance of `X` can always find the `Y` it depends on easily and (relatively) efficiently.
+There is no particularly high overhead to having `X` diretly store an indirect reference to `Y` (at least not for coarse-grained units), and it is trivial for multiple units like `X` to all share the same *instance* of `Y` (potentially even including mutable state, for applications that like that sort of thing).
+
+In general most CPU languages (and especially OOP ones) can express the concepts of "is-a" and "has-a" but they often don't distinguish between when "has-as" means "refers-to-and-depends-on-a" vs. when it means "aggregates-and-owns-a".
+This is important when looking at a GPU language like Slang, where "aggergates-and-owns-a" is easy (we have `struct` types), but "refers-to-and-depends-on-a" is harder (not all of our targets can really support pointers).
+
+Proposed Approach
+-----------------
+
+We propose to introduce a new construct called a *component type* to Slang, which can be used to describe units of modularity larger than what `struct`s are good for, but that is intentionally defined in a way that allows it to be used in cases where fully general `class` types could not be supported.
+
+To render our earlier examples in terms of component types:
+
+```
+// Lighting.slang
+
+interface ILight { ... }
+
+interface ILightingSystem
+{
+ void doLightingStuff();
+}
+
+__component_type DefaultLightingSystem : ILightingSystem
+{
+ StructuredBuffer<ILight> lights;
+
+ void doLightingStuff() { ... }
+}
+```
+
+```
+// Materials.slang
+import Lighting;
+
+interface IMaterial { ... }
+interface IMaterialSystem
+{
+ void doMaterialStuff();
+}
+
+__component_type DefualtMaterialSystem : IMaterialSystem
+{
+ __require lighting : ILightingSystem;
+
+ StructuredBuffer<IMaterial> materials;
+
+ void doMaterialStuff()
+ {
+ lighting.doLightingStuff();
+ }
+}
+```
+
+```
+// Integrator.slang
+import Lighting;
+import Materials;
+
+interface IIntegratorSystem
+{
+ void doIntegratorStuff();
+}
+
+__component_type DefaultIntegratorSystem : IIntegratorSystem
+{
+ __require ILightingSystem;
+ __require IMaterialSystem;
+
+ ParameterBlock<IntegratorParams> params;
+
+ void doIntegeratorStuff()
+ {
+ doLightingStuff();
+ doMaterialsStuff();
+ }
+}
+```
+
+The `__component_type` keyword is akin to `struct` or `class`, but introduces a component type.
+Component types are similar to both structure and class types in that they can:
+
+* Conform to zero or more interfaces
+* Define fields, methods, properties, and nested types
+* Eventually: optionally inherit from one (or more) other component types
+
+The key thing that a `__component_type` can do that a `struct` cannot (but that a `class` might be allowed to) is include nested `__require` declarations.
+In the simplest form, a require declaration is of the form:
+
+```
+__require someName : ISomeInterface;
+```
+
+Within the scope where the `__require` is visible, `someName` will refer to a value that conforms to `ISomeInterface`, but code need not know what the value is (nor what its type is).
+The other form of `__require`:
+
+```
+__require ISomeInterface;
+```
+
+Can be seen as shorthand for something like:
+
+```
+__require _anon : ISomeInterface;
+using anon.*; // NOTE: not actual Slang syntax
+```
+
+One more construct is needed to complete the feature, and it can introduced by illustrating a concerete type that pulls together our default implementations:
+
+```
+__component_type MyProgram
+{
+ __aggregate lighting : DefaultLightingSystem;
+ __aggregate DefaultMaterialSystem;
+ __aggregate DefaultIntegeratorSystem;
+}
+```
+
+An `__aggregate` declaration is only allowed inside a `__component_type` (or a `class`, if we allow it).
+Similar to `__require`, an `__aggregate` can either name the thing being aggregated, or leave it anonymous (and have its members imported into the current scope).
+The semantics of `__aggregate SomeType` are similar to just declaring a field of `SomeType`, but the key distinction is that the aggregated sub-object is conceptually allocated and initialized as *part* of the outer object (one alternative name for the keyword would be `__part`).
+It is not possible to assign to an `__aggregate` member like it is a field (although if the type is a reference type, *its* fields are visible and might still be mutable).
+
+At the point where an `__aggregate SomeType` member is declared, the front-end semantic checking must be able to find/infer the identity of a value to use to satisfy each `__require` member in `SomeType`.
+For example, because `DefaultIntegratorSystem` declares `__require IMaterialSystem`, the compiler searches in the current context for a value that can provide that interface.
+It finds a single suitable value: the value implicitly defined by `__aggregate DefaultMaterialSystem`, and thus "wires up" the input dependency of the `DefaultIntegratorSystem`.
+
+It is posible for a `__require` in an `__aggregate`d member to be satisfied via another `__require` of its outer type:
+
+```
+__component_type MyUnit
+{
+ __require ILightingSystem;
+ __aggregate DefaultMaterialSystem;
+}
+```
+
+In the above example, the `ILightingSystem` requirement in `DefaultMaterialSystem` will be satisfied using the `ILightingSystem` `__require`d by `MyUnit`.
+
+In cases where automatic search and connection of dependencies does not work (or yields an ambiguity error), the user will need some mechanism to be able to explicitly specify how dependencies should be satisfied.
+
+While the above examples do not show it, component types should be allowed to contain shader entry points.
+
+Detailed Explanation
+--------------------
+
+Component types need to be restricted in where and how they can be used, to avoid creating situations that would give them all the flexiblity of arbitrary `class`es.
+The only places where a component type may be used are:
+
+* `__require` and `__aggregate` declarations
+* Function parameters
+* Generic arguments (??? Need to double-check how this can go wrong)
+
+Any given `__component_type` either has no `__require`s and is thus concrete, or it has a nonzero number of `__require`s and is abstract.
+
+We can ignore the `__require`s in a component type (if any) and form an equivalent `struct` type.
+In that `struct` type, `__aggregate`s turn into ordinary fields.
+For example, the `MyProgram` (concrete) and `MyUnit` (abstract) types above become:
+
+```
+struct MyProgram
+{
+ DefaultLightingSystem lighting;
+ DefaultMaterialSystem _anon0;
+ DefaultIntegeratorSystem _anon1;
+};
+struct MyUnit
+{
+ DefaultMaterialSystem _anon2;
+};
+```
+
+When a component type is used as a function parameter (including an implicit `this` parameter) it effectively maps to a function that takes additional (generic) parameters corresponding to each `__require`.
+For example, given:
+
+```
+void doStuff( MyUnit u ) { ... u.doMaterialStuff(); ... }
+```
+
+we would generate something like:
+
+```
+void doStuff< T : ILightingSystem >(
+ T _anon3, // for the `__require : ILightingSystem` in `MyUnit`
+ MyUnit u)
+{
+ ...
+ DefaultMaterialSystem_doMaterialStuff(_anon3, u._anon2);
+ ...
+}
+```
+
+Note that when the generated code invokes an operation through one of the `__aggregate`d members of a component type, where the aggregated type had `__require`ments, the compiler must pass along the additional parameters that represent those requirements in the current context.
+
+Effectively, the compiler generates all of the boilerplate parameter-passing that the programmer would have otherwise had to write by hand.
+
+It might or might not be obvious that the notion of "component type" being described here has a clear correspondance to the `IComponentType` interface provided by the Slang runtime/compilation API.
+It should be possible for us to provide reflection services that allow a programmer to look up a component type by name and get an `IComponentType`.
+The existing APIs for composing, specializing, and linking `IComponentType`s should Just Work for explicit `__component_type`s.
+Large aggregates akin to `MyProgram` above can be defined entirely via the C++ `IComponentType` API at program runtime.
+
+Questions
+---------
+
+### How do we explain to users when to use component types vs. when to use modules? Or `struct`s?
+
+The basic advice should be something like:
+
+* If the thing feels subsystem-y, favor modules or component types. If it feels object-y or value-y, use a `struct`. This is loose, but intuition is good here.
+
+* If the thing wants to depend on other subsystems through `interface`s, to allow mix-and-match flexibility, it should probably be a component type and not a module.
+
+* If the thing wants to have its own shader parameters, then we encourage users to consider that a component type is likely to be what they want, so that they don't pollute the global scope.
+
+That last point is important, since a component type allows users to define a collection of global shader parameters and entry points that use them as a unit, without putting those parameters in the global scope, which is something that was not really possible before.
+
+### Can the `__component_type` construct just be subsumed by either `struct` or `class`?
+
+Maybe. The key challenge is that component types need to provide the "look and feel" of by-refernece re-use rather than by-value copying. A `__require T` should effectively act like a `T*` and not a bare `T` value, so I am reluctant to say that should map to `struct`.
+
+### But what about `[mutating]` operations and writing to fields of component types, then?
+
+Yeah... that's messy. If component types really are by-reference, then they should be implicitly mutable even without passing as `inout`, and should ideally also support aliasing. We need to make sure we get clarity on this.
+
+### Is `__aggregate` really required? Isn't it basically just a field?
+
+An `__aggregate X` acts a lot like a field if `X` is a *value* type, but in cases where `X` is a *reference* type, there is a large semantic distinction.
+
+### How does all this stuff relate to inheritance?
+
+There are some things that can be done with (multiple) inheritance that can also be expressed via `__require`s. For example, both can represent the "diamond" pattern:
+
+```
+class A { ... }
+class B : A { ... }
+class C : A { ... }
+class D : B, C { ... }
+```
+
+```
+__component_type A { ... }
+__component_type B { __require A; ... }
+__component_type C { __require A; ... }
+__component_type D { __require B; __require C; ... }
+```
+
+The Spark shading language research project used multiple mixin class inheritance to compose units of shader code akin to what are being proposed here as coponent types (hmm... I guess that should go into related work...).
+
+In general, using inheritance to model something that isn't an "is-a" relationship is poor modeling.
+Inheritance as a modelling tool cannot capture some patterns that are possible with `__aggregate` (notably, with mixin inheritance you can't get multiple "copies" of a component).
+Most importantly, when inheritance is abused for modeling like this, the resulting code can be confusing. Consider:
+
+```
+abstract class MyFeature : ISystemA, ISystemB
+{ ... }
+```
+
+From this declaration, it is not possible to tell whether `MyFeature` implements just `ISystemA`, just `ISystemB`, both, or neither.
+The distinction between an inheritance clause ("I implement this thing") vs. a `__require` ("I need *somebody else* to implement this thing") is important documentation.
+
+Alternatives Considered
+-----------------------
+
+I'm not aware of any big design alternatives that don't amount to more or less the same thing with different syntax.
+One alternatives is to try to do something like ML-style "signature" for our modules, and allow something like `import ILighting` to allow module-level dependencies on abstracted interfaces.
+
+Another alternative is to do what this document proposes, but make it work with the existing `struct` keyword (or `class`) instead of adding a new one.