|
| 1 | +<!-- {% raw %} --> |
| 2 | + |
| 3 | +# Allowing multiple address spaces for the `this` pointer |
| 4 | + |
| 5 | +* Proposal: [0021](http://0021-this-address-space.md) |
| 6 | +* Author(s): [Nathan Gauer](https://github.com/keenuts), |
| 7 | + [Steven Perron](https://github.com/s-perron) |
| 8 | +* Status: **Design In Progress** |
| 9 | + |
| 10 | +*During the review process, add the following fields as needed:* |
| 11 | + |
| 12 | +* PRs: [\#111](https://github.com/llvm/wg-hlsl/pull/111), |
| 13 | + [llvm-project:\#127675](https://github.com/llvm/llvm-project/pull/127675) |
| 14 | +* Issues: |
| 15 | +* Posts: |
| 16 | + |
| 17 | +## Introduction |
| 18 | + |
| 19 | +HLSL uses copy-in/copy-out semantics for parameter passing. This requires that |
| 20 | +the pointer passed to the callee points to the default address space and |
| 21 | +directly to a variable. This simplifies code generation for the callee, as there |
| 22 | +is only one address space for the parameter. It also ensures that the SPIR-V |
| 23 | +restriction that pointer operands must be memory object declarations is true. |
| 24 | + |
| 25 | +However, this is not the case for the 'this' pointer on member functions. The |
| 26 | +HLSL specification states that the 'this' pointer is a reference to the object, |
| 27 | +which means that it could be a pointer to any address space and could point to |
| 28 | +an object that is a member of another object. |
| 29 | + |
| 30 | +## Motivation |
| 31 | + |
| 32 | +HLSL allows member functions. In HLSL, the this pointer is a reference to the |
| 33 | +object on which it was called. From the user perspective, the same function is |
| 34 | +called regardless of the address space of the object on which it is called. The |
| 35 | +user does not have to write a version of the function for each address space. |
| 36 | + |
| 37 | +This is implemented in DXC, and the same behavior must be implemented in Clang. |
| 38 | +So far, this has not been a problem because no address space other than the |
| 39 | +default address space has been used until recently. This problem will be exposed |
| 40 | +when the hlsl\_device and hlsl\_constant address spaces are used. |
| 41 | + |
| 42 | +Consider this example: |
| 43 | + |
| 44 | +```c |
| 45 | +struct S { |
| 46 | + int a; |
| 47 | + int add(int v) { return a+v; } |
| 48 | +}; |
| 49 | + |
| 50 | +cbuffer B : register(b1) { |
| 51 | + S s; |
| 52 | +}; |
| 53 | + |
| 54 | +RWBuffer<int> o : register(u0); |
| 55 | + |
| 56 | +[numthreads(1,1,1)] |
| 57 | +void main() |
| 58 | +{ |
| 59 | + o[0] = s.add(3); |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +Once |
| 64 | +[https://github.com/llvm/llvm-project/pull/124886](https://github.com/llvm/llvm-project/pull/124886), |
| 65 | +lands, this example fails with the error: |
| 66 | + |
| 67 | +``` |
| 68 | +t.hlsl:15:10: error: cannot initialize object parameter of type 'S' with an expression of type 'hlsl_constant S' |
| 69 | + 15 | o[0] = s.add(3); |
| 70 | +``` |
| 71 | + |
| 72 | +## Proposed solution |
| 73 | + |
| 74 | +All HLSL address spaces will be made a subspace of a new hlsl\_generic address |
| 75 | +space. The \`this\` pointer will point to data within this address space. This |
| 76 | +allows an address space cast to be inserted on the actual parameter by Clang and |
| 77 | +the member function to be called. |
| 78 | + |
| 79 | +These address space casts cause issues for SPIR-V because SPIR-V’s Generic |
| 80 | +storage class is not allowed by the Vulkan environment for SPIR-V, and a pointer |
| 81 | +cannot be cast from one storage class to another. The address space casts will |
| 82 | +be removed through optimizations and a fix-up pass. |
| 83 | + |
| 84 | +All calls to member functions will be inlined, even if the function is marked as |
| 85 | +noinline. Once inlined, a pass that will propagate the address space before the |
| 86 | +cast to all uses of the address space cast can be run. |
| 87 | + |
| 88 | +This solution has been used before. It is essentially the same solution used in |
| 89 | +DXC to generate SPIR-V code. The fix-up pass would be similar to the |
| 90 | +FixStorageClass pass. This solution should work for SPIR-V. C++ for OpenCL uses |
| 91 | +a generic address space in the same way. The OpenCL environment for SPIR-V |
| 92 | +allows address space casts to generic, so no fix-up pass is required. |
| 93 | + |
| 94 | +The disadvantage of this solution is that some error checking is not possible |
| 95 | +until after optimizations have been run. For example, the compiler will not be |
| 96 | +able to check that the pointer for the InterlockedAdd is valid until after |
| 97 | +inlining. See |
| 98 | + |
| 99 | +``` |
| 100 | +struct S |
| 101 | +{ |
| 102 | + int a; |
| 103 | + int atomicAdd() { |
| 104 | + int o; |
| 105 | + InterlockedAdd(a, 1, o); |
| 106 | + return o; |
| 107 | + } |
| 108 | +}; |
| 109 | +
|
| 110 | +RWBuffer<float> b; |
| 111 | +
|
| 112 | +[numthreads(1, 1, 1)] |
| 113 | +void computeMain() |
| 114 | +{ |
| 115 | + S str; |
| 116 | + int a = str.atomicAdd(); |
| 117 | + b[0] = a; |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +It is important to note that these problems will become increasingly complex if |
| 122 | +future versions of HLSL add references with explicit address spaces and |
| 123 | +functions that can be overloaded based on the address space. Should that occur, |
| 124 | +reevaluation of this solution may be necessary. |
| 125 | + |
| 126 | +## Alternatives considered |
| 127 | + |
| 128 | +### Make the `this` parameter implicitly `inout` |
| 129 | + |
| 130 | +An alternative solution is to create a copy of the object on which the member |
| 131 | +function is being called, similar to the handling of other parameters. However, |
| 132 | +this approach necessitates special handling of the copy assignment operator to |
| 133 | +prevent an infinite recursion of temporary objects. The copying in and out at a |
| 134 | +call site are managed as if the assignment operator was employed; however, this |
| 135 | +is a function call with a "this" pointer, which requires the same treatment. |
| 136 | + |
| 137 | +Furthermore, this solution introduces a discrepancy between the behavior and the |
| 138 | +specification, as well as potential inconsistencies with DXC. The aforementioned |
| 139 | +example with the atomic operation illustrates this issue. If a copy-in and |
| 140 | +copy-out operation is implemented for "str," the accesses to "str" are no longer |
| 141 | +atomic. |
| 142 | + |
| 143 | +### Automatically replicate member functions for each address space |
| 144 | + |
| 145 | +An additional potential solution is to duplicate the member function for each |
| 146 | +address space. This could be implemented in two ways. The first is to have |
| 147 | +multiple versions of the struct, one for each address space. This would be |
| 148 | +similar to adding an implicit template for the address space to the struct. The |
| 149 | +second is to have one struct type, but have multiple member functions. This is |
| 150 | +similar to having an implicit template on each member function for the address |
| 151 | +space. |
| 152 | + |
| 153 | +Both of these options would have to be implemented during sema. The issue is |
| 154 | +that both of these solutions appear to be significantly different than anything |
| 155 | +currently done in sema. Although I believe it is possible, I am uncertain if we |
| 156 | +should invent a novel process. |
| 157 | + |
| 158 | +<!-- {% endraw %} --> |
0 commit comments