Skip to content

Commit ed9f9b7

Browse files
authored
Merge pull request #14 from csyonghe/any-some
Type System for Compile-Time and Run-Time Existential Values
2 parents 5ee249e + 675f6fd commit ed9f9b7

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed

proposals/024-any-dyn-types.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Type System for Compile-Time and Run-Time Existential Values
2+
3+
This proposal adds `some` and `dyn` keyword for explicitly specifying whether or not an interface type can be used in dynamic dispatch.
4+
5+
## Status
6+
7+
Status: Design Review
8+
9+
Implementation:
10+
11+
Author: Yong He
12+
13+
Reviewer:
14+
15+
## Background
16+
17+
Currently, Slang allows interface types to be used directly to declare a variable, a function parameter, or a struct field. If the compiler is able to deduce the actual type of the var decl, it will specialize the code as if the variable is declared with the deduced type. If this type info is not known at compile time, our compiler will fallback to emit dynamic dispatch logic for the var decl. This can lead to surprises since the user can trigger dynamic dispatch logic unintentionally and pay for the runtime cost. This design violates Slang's philosophy that all non-trivial runtime overhead should only be introduced explicitly by the programmer. Another big down side is that our dynamic dispatch logic can only handle ordinary data types. If a type implementing an interface contains opaque types such as `Texture2D`, the dynamic dispatch pass will fail and give user confusing error messages. The decision of whether or not a variable or a type is allowed to be involved in dynamic dispatch has non-trivial effect in both runtime performance and coding restrictions. Therefore this should be modeled as a part of the type system.
18+
19+
## Proposed Solution
20+
21+
### Language Version
22+
23+
The new syntax and type checking rules are introduced to Slang language version 2026, which can be enabled with compiler flag -lang 2026. By default, the compiler assumes language version 2025 so existing code will continue to compile.
24+
25+
### Dynamic interface types
26+
27+
Interface types that can be used for dynamic dispatch must be qualified with the `dyn` keyword. Interface types decorated with the existing `[anyValueSize(n)]` attribute are assumed to be qualified with `dyn`. For example:
28+
29+
```
30+
// Define an interface that can be used in dynamic dispatch.
31+
dyn interface IFoo
32+
{
33+
float compute(int x);
34+
}
35+
```
36+
37+
By default, a `dyn` interface is subject to the following restrictions, unless language version is 2025 or `-enable-experimental-dynamic-dispatch` flag is present:
38+
39+
1. The interface itself cannnot be generic.
40+
1. It must not define any associated types.
41+
1. It must not define any generic methods.
42+
1. It must not define any mutating methods.
43+
1. It cannot contain any methods that has a `some IFoo` return type, or has any `some IFoo` parameters.
44+
1. A `dyn` interface cannot inherit from any interfaces that are not `dyn`.
45+
1. A `dyn` interface cannot contain any function requirements that are marked as `[Differentiable]`.
46+
47+
Any type that conforms to one or more `dyn` interface is subject to these restrictions:
48+
49+
1. The type itself must be an ordinary data type, meaning that it cannot contain any fields that are opaque or non-copyable or unsized.
50+
51+
In addition, such types are subject to these restrictions unless language version is 2025 or `-enable-experimental-dynamic-dispatch` flag is present:
52+
53+
1. The type itself cannot be generic.
54+
1. Extensions that make types conform to `dyn` interfaces are not allowed.
55+
56+
### Use of interface types
57+
58+
If a variable, a parameter or a struct field is declared to have an interface type or array of interface type, the var decl must be qualified with either the `some` keyword or the `dyn` keyword. If the var decl is not explicitly qualified with `some` or `dyn`, the compiler implicitly qualifieds the type as `some` when language version is 2026 or later, or as `dyn` in language version 2025.
59+
60+
The concrete type of a `some` qualified var decl must be compile-time deducible to a proper type and will never involve in dynamic dispatch. The concrete type of a `dyn` qualified var decl might not be compile-time deducible and might involve in dynamic dispatch.
61+
62+
The following rules apply to `some` and `dyn` qualifiers on var decls:
63+
64+
1. A `some IFoo` type is valid only if `IFoo` is an interface type.
65+
1. A `some IFoo` type can only be used on a function parameter, a function return value or a local variable. Struct fields or global variables cannot have a `some` type.
66+
1. A `some IFoo` type cannot appear in any complex type expression. For example, `some IFoo*`, `some IFoo[2]`, `Tuple<some IFoo, some IFoo>` etc. are not allowed.
67+
1. A local variable of `some IFoo` type cannot be assigned or initialized more than once throughout its lifetime.
68+
1. If a function returns `some IFoo`, then all return statements must return values of exactly the same type.
69+
1. If a function contains a `out some IFoo` parameter, then all values assignmened to the parameter must have exactly the same type.
70+
1. Two different var decls of `some IFoo` type are considered to have **different** types.
71+
1. A `dyn IFoo` type is valid only if `IFoo` is an interface type with `dyn` qualifier.
72+
1. A value of `dyn` interface type cannot be assigned to a location of `some` interface type, but the opposite direction is OK.
73+
1. Attempting to use a `dyn` or `some` typed value before initialization is an error.
74+
75+
76+
Here is an example demonstrating the rules:
77+
78+
```
79+
dyn interface IDyn
80+
{
81+
// error: dyn interface cannot contain generic method.
82+
void computeGeneric<T>();
83+
84+
void compute(float v);
85+
}
86+
87+
interface IFoo
88+
{
89+
[mutating]
90+
void computeFoo(float v);
91+
}
92+
93+
struct Opaque : IDyn
94+
{
95+
Texture2D b; // error: type conforming to dyn interface cannot be opaque.
96+
...
97+
}
98+
99+
// OK.
100+
struct Opaque1 : IFoo
101+
{
102+
...
103+
}
104+
105+
struct Ordinary : IDyn
106+
{}
107+
108+
void callee(some IFoo b) {}
109+
110+
void output(out some IFoo b) {}
111+
112+
void modify(inout some IFoo b) {}
113+
114+
void test(inout some IFoo out, some IFoo f)
115+
{
116+
out = f; // error: type mismatch.
117+
out.computeFoo(1.0); // OK, calling mutable methods on a some type.
118+
dyn IFoo d = f; // OK, assigning `some IFoo` to `dyn IFoo`.
119+
some IFoo s = d; // error: type mismatch.
120+
callee(d); // error: type mismatch `dyn IFoo` and `some IFoo`.
121+
callee(f); // OK.
122+
123+
some IFoo foo;
124+
output(foo); // OK, initializing `foo` the first time.
125+
modify(foo); // OK.
126+
output(foo); // error, initializing `foo` more than once.
127+
foo = ...; // error, assigning to `foo` more than once.
128+
129+
some IFoo foo1;
130+
for (int i = 0; i < 1; i++)
131+
{
132+
// error, assigning to `foo1` inside a loop, potentially
133+
// initializing it more than once.
134+
output(foo1);
135+
}
136+
137+
dyn Opaque v; // error, invalid type.
138+
dyn IFoo v1; // error, `IFoo` is not `dyn` interface.
139+
}
140+
141+
some IFoo global; // error: cannot use some type in global variable.
142+
struct Invalid
143+
{
144+
some IFoo field; // error, cannot use some type in struct field.
145+
}
146+
```
147+
148+
### Implementing `dyn` type
149+
150+
A `dyn IFoo` type should be treated exactly like `IFoo` type as today, they will be represented as a `DeclRefType` referencing `IFoo`.
151+
The existing type system should just work for the `dyn` case.
152+
153+
### Implementing `some` type
154+
155+
A `some` type declaration should be considered as its own `Decl` in Slang's type system. For example, consider the following code:
156+
157+
```
158+
interface IFoo {}
159+
void compute(some IFoo f1, some IFoo f2)
160+
{
161+
...
162+
}
163+
```
164+
165+
This should be translated into:
166+
167+
```
168+
// Compiler should insert a `some_type` as a member of the function,
169+
// and translate `f` to a `DeclRefType` referencing `__type_of_f_IFoo`.
170+
//
171+
void compute(__type_of_f1_IFoo f1, __type_of_f2_IFoo f2)
172+
{
173+
some_type __type_of_f1_IFoo : IFoo;
174+
some_type __type_of_f2_IFoo : IFoo;
175+
}
176+
```
177+
178+
An interesting case is when the user declares a local variable of `some IFoo` type:
179+
180+
```
181+
struct Concrete : IFoo {}
182+
183+
void output(out some IFoo f, some IFoo f1) { f = f1; }
184+
void test()
185+
{
186+
some IFoo o;
187+
Concrete c;
188+
output(o, c);
189+
}
190+
```
191+
192+
When checking this code, we will first convert the some types into actual decls:
193+
194+
```
195+
void output(out __type_of_output_f_IFoo f, __type_of_output_f1_IFoo f1)
196+
{
197+
unbound_some_type __type_of_output_f_IFoo : IFoo;
198+
some_type __type_of_output_f1_IFoo : IFoo;
199+
f = f1;
200+
}
201+
void test()
202+
{
203+
unbound_some_type __type_of_test_o_IFoo : IFoo;
204+
__type_of_test_o_IFoo o;
205+
Concrete c;
206+
output(o);
207+
}
208+
```
209+
210+
Note that all local variables or `out` parameters of `some IFoo` type is converted into `unbound_some_type` decls.
211+
`in` or `inout` parameters of `some IFoo` type will be converted into `some_type` decls.
212+
When checking the call to `output(o)` in `test()`, we will encounter a type mismatch, because
213+
`o` is of type `__type_of_test_o_IFoo`, and `output`'s parameter type is `__type_of_output_f_IFoo`.
214+
To allow this code to work, we will add a type coercion rule that enables a `DeclRefType` of a `some_type(IFoo)` or `unbound_some_type(IFoo)` to coerce into `unbound_some_type(IFoo)`.
215+
216+
Note that we will still disallow any coercions of `some_type` or `unbound_some_type` into a `some_type`, so we can still diagnose the type mismatches in cases like:
217+
218+
```
219+
void test(inout some IFoo f1, some IFoo f2)
220+
{
221+
f1 = f2; // error, type mismatch
222+
}
223+
```
224+
225+
In order to diagnose cases such as:
226+
227+
```
228+
void test(some IFoo f1, some IFoo f2)
229+
{
230+
some IFoo o;
231+
o = f1;
232+
o = f2; // should error here
233+
}
234+
```
235+
236+
We need to implement a separate check in an IR pass to ensure variables of `some` type is never written to more than once via an assignment or an `out` parameter in its lifetime. It is OK for a `some` type variable to be modified multiple times through an `inout` parameter.
237+
238+
### Lowering to IR
239+
240+
Both `dyn` type and `some` type should lower to IR in the same way. That is, `some IFoo` and `dyn IFoo` should both lower to `%IFoo = IRInterfaceType`. Once we reach IR, there is no longer any distinction between `some` or `dyn` type.
241+
242+
The one exception is that variables or parameters of `some` type will be emitted with a IR decoration, so our IR validation pass can identify these variables or parameters and check if they are being written to more than once.
243+
244+
## Alternatives Considered
245+
246+
In this proposal, we made dynamic dispatch an explicit opt-in feature on `interface` types by requiring `dyn` on interfaces that may be used in dynamic dispatch. This allows us to quickly check the interface type itself for additional restrictions without looking at how the interface type itself is used. An alternative is to make this implicit: we validate an interface type with dyanmic-dispatch related restrictions only when we see the interface is used to define a `dyn IFoo` type. We may want to move to this design going forward, but start with an explicit opt-in keyword is a conservative approach.
247+
248+
We can also consider lifting the restriction on interface types to allow any interface type to be used in a `dyn IFoo` type, as long as the none of the dynamic dispatch calls actually uses the unsupported members of the interface. This offers a much greater degree of flexibility, but it also means the check is less structural.
249+
250+
We also decided to perform many `some` type related checks in the type system. Many of the checks can be moved to an IR pass right after lowering, so we can make use of sophiscated dataflow analysis to verify the type of a `some` typed variable remains constant throughout the function, and allow more than one assignments to a `some` typed variable. We would like to start with a simple and more restrictive solution, and relax the restrictions in the future.

0 commit comments

Comments
 (0)