Skip to content

Commit f1f4d31

Browse files
authored
[0021] Proposal for handling member function address spaces. (#187)
As we expose new address spaces in HLSL, we will have a problem with the address space for the `this` pointer in member functions.
1 parent 7a5d513 commit f1f4d31

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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

Comments
 (0)