Skip to content

Commit 3a5214b

Browse files
author
Tim Foley
authored
Add support for static methods in interfaces (shader-slang#680)
This change allows an interface to include `static` methods as requirements, so that types that conform to the interface will need to satisfy the requirement with a `static` method. The essence of the check is simple: when checking that a method satisfies a requirement, we enforce that both are `static` or both are non-`static`. Making that simple change and adding a test change broke a few other places in the compiler that this change tries to fix. The main fix is to handle cases where we might look up an "effectively static" member of a type through an instance, and to make sure that we replace the instance-based lookup with type-based lookup. There was already logic along these lines in `lower-to-ir.cpp`, so this change centralizes it in `check.cpp` where it seems to logically belong.
1 parent f9710d5 commit 3a5214b

File tree

4 files changed

+159
-34
lines changed

4 files changed

+159
-34
lines changed

source/slang/check.cpp

+92
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,75 @@
99

1010
namespace Slang
1111
{
12+
13+
/// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration?
14+
bool isEffectivelyStatic(
15+
Decl* decl,
16+
ContainerDecl* parentDecl)
17+
{
18+
// Things at the global scope are always "members" of their module.
19+
//
20+
if(parentDecl->As<ModuleDecl>())
21+
return false;
22+
23+
// Anything explicitly marked `static` and not at module scope
24+
// counts as a static rather than instance declaration.
25+
//
26+
if(decl->HasModifier<HLSLStaticModifier>())
27+
return true;
28+
29+
// Next we need to deal with cases where a declaration is
30+
// effectively `static` even if the language doesn't make
31+
// the user say so. Most languages make the default assumption
32+
// that nested types are `static` even if they don't say
33+
// so (Java is an exception here, perhaps due to some
34+
// influence from the Scandanavian OOP tradition of Beta/gbeta).
35+
//
36+
if(dynamic_cast<AggTypeDecl*>(decl))
37+
return true;
38+
if(dynamic_cast<SimpleTypeDecl*>(decl))
39+
return true;
40+
41+
// Things nested inside functions may have dependencies
42+
// on values from the enclosing scope, but this needs to
43+
// be dealt with via "capture" so they are also effectively
44+
// `static`
45+
//
46+
if(dynamic_cast<FunctionDeclBase*>(parentDecl))
47+
return true;
48+
49+
// Type constraint declarations are used in member-reference
50+
// context as a form of casting operation, so we treat them
51+
// as if they are instance members. This is a bit of a hack,
52+
// but it achieves the result we want until we have an
53+
// explicit representation of up-cast operations in the
54+
// AST.
55+
//
56+
if(decl->As<TypeConstraintDecl>())
57+
return false;
58+
59+
return false;
60+
}
61+
62+
/// Should the given `decl` be treated as a static rather than instance declaration?
63+
bool isEffectivelyStatic(
64+
Decl* decl)
65+
{
66+
// For the purposes of an ordinary declaration, when determining if
67+
// it is static or per-instance, the "parent" declaration we really
68+
// care about is the next outer non-generic declaration.
69+
//
70+
// TODO: This idiom of getting the "next outer non-generic declaration"
71+
// comes up just enough that we should probably have a convenience
72+
// function for it.
73+
74+
auto parentDecl = decl->ParentDecl;
75+
if(auto genericDecl = parentDecl->As<GenericDecl>())
76+
parentDecl = genericDecl->ParentDecl;
77+
78+
return isEffectivelyStatic(decl, parentDecl);
79+
}
80+
1281
// A flat representation of basic types (scalars, vectors and matrices)
1382
// that can be used as lookup key in caches
1483
struct BasicTypeKey
@@ -416,6 +485,22 @@ namespace Slang
416485
expr->declRef = declRef;
417486
return expr;
418487
}
488+
else if(isEffectivelyStatic(declRef.getDecl()))
489+
{
490+
// Extract the type of the baseExpr
491+
auto baseExprType = baseExpr->type.type;
492+
RefPtr<SharedTypeExpr> baseTypeExpr = new SharedTypeExpr();
493+
baseTypeExpr->base.type = baseExprType;
494+
baseTypeExpr->type = new TypeType(baseExprType);
495+
496+
auto expr = new StaticMemberExpr();
497+
expr->loc = loc;
498+
expr->type = type;
499+
expr->BaseExpression = baseTypeExpr;
500+
expr->name = declRef.GetName();
501+
expr->declRef = declRef;
502+
return expr;
503+
}
419504
else
420505
{
421506
// If the base expression wasn't a type, then this
@@ -2246,6 +2331,13 @@ namespace Slang
22462331
return false;
22472332
}
22482333

2334+
if(satisfyingMemberDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()
2335+
!= requiredMemberDeclRef.getDecl()->HasModifier<HLSLStaticModifier>())
2336+
{
2337+
// A `static` method can't satisfy a non-`static` requirement and vice versa.
2338+
return false;
2339+
}
2340+
22492341
// TODO: actually implement matching here. For now we'll
22502342
// just pretend that things are satisfied in order to make progress..
22512343
witnessTable->requirementDictionary.Add(

source/slang/lower-to-ir.cpp

+7-34
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,12 @@ void setValue(IRGenContext* context, Decl* decl, LoweredValInfo value)
384384
context->env->mapDeclToValue[decl] = value;
385385
}
386386

387+
388+
/// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration?
389+
bool isEffectivelyStatic(
390+
Decl* decl,
391+
ContainerDecl* parentDecl);
392+
387393
// Ensure that a version of the given declaration has been emitted to the IR
388394
LoweredValInfo ensureDecl(
389395
IRGenContext* context,
@@ -4446,46 +4452,13 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
44464452
//
44474453
// We also need to be able to detect whether a declaration is
44484454
// either explicitly or implicitly treated as `static`:
4449-
bool isMemberDeclarationEffectivelyStatic(
4450-
Decl* decl,
4451-
ContainerDecl* parentDecl)
4452-
{
4453-
// Anything explicitly marked `static` counts.
4454-
//
4455-
// There is a subtle detail here with a global-scope `static`
4456-
// variable not really meaning `static` in the same way, but
4457-
// it doesn't matter because the module shouldn't introduce
4458-
// any parameters we care about.
4459-
if(decl->HasModifier<HLSLStaticModifier>())
4460-
return true;
4461-
4462-
// Next we need to deal with cases where a declaration is
4463-
// effectively `static` even if the language doesn't make
4464-
// the user say so. Most languages make the default assumption
4465-
// that nested types are `static` even if they don't say
4466-
// so (Java is an exception here, perhaps due to some
4467-
// includence from the Scandanavian OOP tradition).
4468-
if(dynamic_cast<AggTypeDecl*>(decl))
4469-
return true;
4470-
4471-
// Things nested inside functions may have dependencies
4472-
// on values from the enclosing scope, but this needs to
4473-
// be dealt with via "capture" so they are also effectively
4474-
// `static`
4475-
if(dynamic_cast<FunctionDeclBase*>(parentDecl))
4476-
return true;
4477-
4478-
return false;
4479-
}
4480-
// We also need to be able to detect whether a declaration is
4481-
// either explicitly or implicitly treated as `static`:
44824455
ParameterListCollectMode getModeForCollectingParentParameters(
44834456
Decl* decl,
44844457
ContainerDecl* parentDecl)
44854458
{
44864459
// If we have a `static` parameter, then it is obvious
44874460
// that we should use the `static` mode
4488-
if(isMemberDeclarationEffectivelyStatic(decl, parentDecl))
4461+
if(isEffectivelyStatic(decl, parentDecl))
44894462
return kParameterListCollectMode_Static;
44904463

44914464
// Otherwise, let's default to collecting everything
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// interface-static-method.slang
2+
3+
//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
4+
//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12
5+
//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
6+
7+
interface IHideout
8+
{
9+
int getAnimalCount();
10+
}
11+
12+
interface ISuperhero
13+
{
14+
associatedtype Hideout : IHideout;
15+
16+
static Hideout getHideout();
17+
}
18+
19+
struct Batcave : IHideout
20+
{
21+
int batCount;
22+
23+
int getAnimalCount() { return batCount; }
24+
}
25+
26+
struct Batman : ISuperhero
27+
{
28+
typedef Batcave Hideout;
29+
30+
static Batcave getHideout()
31+
{
32+
Batcave batcave = { 100 };
33+
return batcave;
34+
}
35+
}
36+
37+
int doIt<T:ISuperhero>()
38+
{
39+
return T.getHideout().getAnimalCount();
40+
}
41+
42+
int test(int val)
43+
{
44+
return doIt<Batman>();
45+
}
46+
47+
48+
//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
49+
RWStructuredBuffer<int> outputBuffer;
50+
51+
[numthreads(4, 1, 1)]
52+
void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
53+
{
54+
int tid = dispatchThreadID.x;
55+
outputBuffer[tid] = test(tid);
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
64
2+
64
3+
64
4+
64

0 commit comments

Comments
 (0)