HLSLChanges.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. ============
  2. HLSL Changes
  3. ============
  4. Introduction
  5. ============
  6. This document is meant to describe the changes that have been made to the LLVM
  7. and Clang codebases to support HLSL, the High-Level Shading Language. The
  8. focus is on design changes and general approach rather than specific changes
  9. made to support the language and runtime.
  10. Driving Forces
  11. ==============
  12. This section outlines the considerations that have prompted multiple changes.
  13. * Provide a successor to the fxc.exe and d3dcompiler_47.dll components. This
  14. means providing a DLL that can be used in a variety of situations, as well
  15. as a command-line program to access its functionality.
  16. * The requirement to act as a proper DLL means that it shouldn't rely on
  17. process-wide state, as this prevents callers from using it from more than
  18. one thread concurrently. This includes changing the current directory, using
  19. stdin/stdout/stderr, or deciding to terminate the process (except for fatal
  20. conditions).
  21. * There are scenarios in which applications may choose to redistribute the
  22. built DLL, for example an IDE for writing shaders, tools to instrument and
  23. debug them, or a game that chooses to emit shaders at runtime. This means
  24. that compilation time and DLL/program size are important considerations.
  25. * Because the new command-line program should be a replacement of specific
  26. components, it's desirable to keep the interface similar to the prior
  27. versions (API in case of the DLL, command-line format in the case of the
  28. program).
  29. Forking LLVM and Clang
  30. ======================
  31. This section describes why and how the HLSL on LLVM project has forked LLVM
  32. and Clang.
  33. LLVM and Clang provide an excellent starting point for building a compiler,
  34. especially one that will exist in an ecosystem of organizations contributing
  35. to the pipeline, whether it be in the form of tooling and abstractions by
  36. middleware and tool authors, or backend compilers by hardware vendors.
  37. The decision was made to fork LLVM and Clang rather than work directly and
  38. upstream all changes for the following reasons:
  39. * While HLSL started out as a C derivative, over time is has drifted away, not
  40. to the extent that C++ and Objective-C have but certainly a fair
  41. bit. Furthermore, some of the behavior was never compatible to begin with,
  42. and so there are significant differences, especially in the type system,
  43. that make upstreaming difficult.
  44. * HLSL is expected to evolve over the next few years to shed some of the
  45. incompatibilities that exist for historical reasons (as opposed to those
  46. that provide actual developer benefit). It's entirely possible that a future
  47. version will be much more closely aligned to C/C++ semantics and could be
  48. more easily adapted. This version of the codebase isn't it.
  49. * The changes to LLVM are meaningless without an execution model, which is
  50. currently being worked on. Changes to LLVM are more likely to get
  51. upstreamed, however we have chosen to not consider this until we have
  52. multiple implementations of DXIL backends.
  53. We have already done a 3.4 to 3.7 upgrade in the sources, and it's entirely
  54. possible that this will happen again. Therefore, all changes introduces are
  55. done in entirely new files, or marked with an 'HLSL Change', or marked with a
  56. pair of 'HLSL Change Starts' / 'HLSL Change Ends'. This makes integrations
  57. easier (or rather less difficult).
  58. Dependency Injection
  59. ====================
  60. One of the goals of dxcompiler is to be a reusable component for a variety of
  61. applications. While Clang and LLVM have support for being hosted as a
  62. dynamically loaded library, there is still a number of assumptions made that
  63. make usage problematic, specifically:
  64. - Usage of process-wide handles (stdin/stdout/stderr).
  65. - Reliance on file system access (clang has some level of virtual file system
  66. support in the later versions), including temporary files.
  67. - Direct usage of memory allocation mechanisms.
  68. - Reliance on other process-wide constructs like environment variables.
  69. Redesigning a number of these mechanisms would require changes throughout the
  70. codebase, and it's hard to whether any regressions are introduced when a large
  71. number of changes are integrated.
  72. The solution we have implemented relies on having a thread-local component
  73. that can service I/O requests as well as other OS-implemented or process-wide
  74. constructs. The library is then meant to be used through specific API points
  75. that will set and tear down this component as appropriate. The MSFileSystem
  76. class in include/llvm/Support/MSFileSystem.h provides the access point
  77. (although the API should likely be renamed, as it's broader than a pur file
  78. system abstraction, and 'MS' monikers are being changed to 'HLSL').
  79. To guard against regressions, we can simply verify that no libraries include
  80. APIs that are virtualized, such as calls to CreateFile. If a case is found, a
  81. drop-in replacement function call can be made in its place.
  82. Some of the tasks that are simplified include:
  83. - Host in-process in an IDE to provide tooling services.
  84. - Guarantee that no process execution takes place, or that the host
  85. environment influences the tools without an explicit usage.
  86. - Provide virtualized I/O for all kinds of file access, including redirecting
  87. to in-memory buffers.
  88. - Override memory allocation to constrain memory consumption.
  89. At the moment, memory allocation is still not redirected, but I/O has been
  90. cleaned up.
  91. Error Handling
  92. ==============
  93. Clang and LLVM already provide a number of error handling mechanisms:
  94. returning a bool flag to indicate success or failure, returning a structured
  95. object that flags the result along with other information, using STL system
  96. errors for certain errors, or using errno for some C standard library calls.
  97. There are two other kinds of error handling mechanisms introduced by HLSL
  98. on LLVM.
  99. * C++ Exceptions. The primary use case for these is to handle out-of-memory
  100. exceptions. A second case is to handle cases where LLVM and Clang today
  101. attempt to terminate the process even though they are in a recoverable state
  102. (for example, when a '--help' switch is found in command-line options).
  103. * HRESULT. The APIs are designed to be familiar to Windows developers and to
  104. provide a simple transition for d3dcompiler_47.dll users. As such, error
  105. codes are typically returned as HRESULT values that can be tested with the
  106. SUCCEEDED/FAILED macros.
  107. Some of the code that is written to interface with the rest of the system can
  108. also make use of these error handling strategies. A number of macros to handle
  109. them, or to convert one into the other, are available in
  110. include/dxc/Support/Global.h.
  111. Removing Unused Functionality
  112. =============================
  113. Removing unused functionality can help reduce the binary size, improve
  114. performance, and speed up compilation time for the project. However, this has
  115. to be traded off against changing the behavior of LLVM and Clang (and the
  116. cost of understanding this change for developers who are familiar with those
  117. projects), as well as the future maintenance for integrations.
  118. The recommendations is to avoid removing small bits of functionality, and only
  119. do so for significant subsystems that can be "sliced off" cleanly (for
  120. example, the interpreter component or target support).
  121. Component Design
  122. ================
  123. The dxcompiler DLL is designed to export a number of components that can be
  124. reused in different contexts. The API is exposed as a lightweight form of the
  125. Microsoft Component Object Model (COM); a similar approach can be seen in the
  126. design of the xmllite library.
  127. The functionality of the library is encapsulated in discrete compmonents, each
  128. of which is embodied in an object that implements one or more
  129. interfaces. Interfaces are derived from IUnknown as in COM and are responsible
  130. for interface discovery and lifetime management. Object construction is done
  131. via the single exported API, DxcCreateObject, which acts much like
  132. DllCreateObject would in a COM library.
  133. Interfaces are mostly COM-compatible and have been designed to be easy to use
  134. from other languages that can consume COM libraries, such as the .NET runtime
  135. or C++ applications. Importantly, memory allocated in the library that should
  136. be freed by the consumer is allocated using the COM allocation, through
  137. CoTaskMemAlloc or the use of IMalloc via CoGetMalloc().
  138. Note that this lightweight COM support implies that some features are missing:
  139. - There is no support for marshalling across COM apartments.
  140. - There is (at the moment at least) no management of library references based
  141. on outstanding objects (a typical bug that would arise from this would be,
  142. for example, unloading dxcompiler while outstanding objects exist, at which
  143. point even releasing them would lead to an access violation).
  144. Text and Buffer Management
  145. ==========================
  146. Tradionally, the D3D compilers have used an ID3DBlob interface to encapsulate
  147. a buffer. The HLSL compiler avoids pulling in DirectX headers and defines an
  148. IDxcBlob interface that has the same layout and interface identifier (IID).
  149. Buffers are often used to hold text, for example shader sources or compilation
  150. logs. IDxcBlobEncoding inherits from IDxcBlob and has functionality to declare
  151. the encoding for the buffer.
  152. The design principle for using a character pointer or an IDxcBlobEncoding is
  153. as follows: for internal dxcompiler text, UTF-8 and char* are used; for API
  154. parameters that are "short" such as file names or command line parameters,
  155. UTF-16 and wchar_t* are used; for longer text such as source files or error
  156. logs, IDxcBlobEncoding is used.
  157. The DLL provides a "library" component that provides utility functions to
  158. create and transform blobs and strings.
  159. Specification Database
  160. ======================
  161. In the utils\hct directory, an hctdb.py file can be found that initialized a
  162. number of Python object instances describing different aspects of the DXIL
  163. specification. These act as a queryable, programmable repository of
  164. information which feed into other tasks, such as generating documentation,
  165. generating code or performing compatibility checks across versions.
  166. We require that the database be kept up-to-date for the concepts embedded
  167. there to drive a number of code-generation tasks, which can be found in other
  168. .py files in that same directory.
  169. HLSL Modules
  170. ============
  171. llvm::Module is the type that represents a shader program. It includes
  172. metadata nodes to provide details around the ABI, flags, etc. However,
  173. manipulation of all this information in terms of metadata is not very
  174. efficient or convenient.
  175. As part of the work with HLSL, we introduce two modules that are attached
  176. in-memory to an llvm::Module: a high-level HLModule, and a low-level
  177. DxilModule. The high-level module is used in the early passes to deal with
  178. HLSL-as-a-language concepts, such as intrinsics, matrices and vectors; the
  179. low-level module is used to deal with concepts as they exist in the DXIL
  180. specification. Only one of these additional modules ever exists at one point;
  181. the DxilGenerationPass that does the translation destroys the high-level
  182. representation and creates a low-level one as part of its work.
  183. To preserve many of the benefits of LLVM's modular pipeline, it is useful to
  184. serialize and deserialize shaders at different stages of processing, and so
  185. both HLModule and DxilModule provide support for these. The expectation for a
  186. wholesale compilation from source, however, is that this information lives
  187. only in memory until it's ready to be serialized out in final DIXL form. As
  188. such, various passes along the way may need to do update to these modules to
  189. maintain consistency (for example, if global DCE removes a variable, the
  190. corresponding resource mapping that reflects the shader ABI should be cleaned
  191. up as well).