Skip to content

Commit 81ce78d

Browse files
author
Tim Foley
authored
Add support for the HLSL "cast from zero" idiom (shader-slang#1008)
If the user writes code like this: MyStruct s = (MyStruct) 0; then we will interpret it as if they had written: MyStruct s = {}; That is, the "cast from zero" idiom will be taken as a legacy syntax for default construction (using an empty initializer list). This will be semantically equivalent to zero-initialization for all existing HLSL code (where `struct` fields can't have default initialization expressions defined), and is the easiest option for us to support in Slang (since we already support default-initialization using empty initializer lists). The implementation of this feature is narrowly scoped: * It only targets explicit cast expressions like `(MyStruct) 0` and not "constructor" syntax like `MyStruct(0)` * It only applies when there is a single argument that is exactly an integer literal with a zero value (not a reference to a `static const int` that happens to be zero). This change adds a test case to make sure that the feature works as expected. Because it relies on our existing initializer-list handling, the "cast from zero" idiom should work for any user-defined type where an initializer list would work.
1 parent 8975854 commit 81ce78d

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

source/slang/slang-check.cpp

+80
Original file line numberDiff line numberDiff line change
@@ -8464,6 +8464,21 @@ namespace Slang
84648464
context.baseExpr = funcOverloadExpr2->base;
84658465
}
84668466

8467+
// TODO: We should have a special case here where an `InvokeExpr`
8468+
// with a single argument where the base/func expression names
8469+
// a type should always be treated as an explicit type coercion
8470+
// (and hence bottleneck through `coerce()`) instead of just
8471+
// as a constructor call.
8472+
//
8473+
// Such a special-case would help us handle cases of identity
8474+
// casts (casting an expression to the type it already has),
8475+
// without needing dummy initializer/constructor declarations.
8476+
//
8477+
// Handling that special casing here (rather than in, say,
8478+
// `visitTypeCastExpr`) would allow us to continue to ensure
8479+
// that `(T) expr` and `T(expr)` continue to be semantically
8480+
// equivalent in (almost) all cases.
8481+
84678482
if (!context.bestCandidate)
84688483
{
84698484
AddOverloadCandidates(funcExpr, context);
@@ -8885,6 +8900,71 @@ namespace Slang
88858900
arg = CheckExpr(arg);
88868901
}
88878902

8903+
// LEGACY FEATURE: As a backwards-compatibility feature
8904+
// for HLSL, we will allow for a cast to a `struct` type
8905+
// from a literal zero, with the semantics of default
8906+
// initialization.
8907+
//
8908+
if( auto declRefType = typeExp.type.as<DeclRefType>() )
8909+
{
8910+
if(auto structDeclRef = declRefType->declRef.as<StructDecl>())
8911+
{
8912+
if( expr->Arguments.getCount() == 1 )
8913+
{
8914+
auto arg = expr->Arguments[0];
8915+
if( auto intLitArg = arg.as<IntegerLiteralExpr>() )
8916+
{
8917+
if(getIntegerLiteralValue(intLitArg->token) == 0)
8918+
{
8919+
// At this point we have confirmed that the cast
8920+
// has the right form, so we want to apply our special case.
8921+
//
8922+
// TODO: If/when we allow for user-defined initializer/constructor
8923+
// definitions we would have to be careful here because it is
8924+
// possible that the target type has defined an initializer/constructor
8925+
// that takes a single `int` parmaeter and means to call that instead.
8926+
//
8927+
// For now that should be a non-issue, and in a pinch such a user
8928+
// could use `T(0)` instead of `(T) 0` to get around this special
8929+
// HLSL legacy feature.
8930+
8931+
// We will type-check code like:
8932+
//
8933+
// MyStruct s = (MyStruct) 0;
8934+
//
8935+
// the same as:
8936+
//
8937+
// MyStruct s = {};
8938+
//
8939+
// That is, we construct an empty initializer list, and then coerce
8940+
// that initializer list expression to the desired type (letting
8941+
// the code for handling initializer lists work out all of the
8942+
// details of what is/isn't valid). This choice means we get
8943+
// to benefit from the existing codegen support for initializer
8944+
// lists, rather than needing the `(MyStruct) 0` idiom to be
8945+
// special-cased in later stages of the compiler.
8946+
//
8947+
// Note: we use an empty initializer list `{}` instead of an
8948+
// initializer list with a single zero `{0}`, which is semantically
8949+
// significant if the first field of `MyStruct` had its own
8950+
// default initializer defined as part of the `struct` definition.
8951+
// Basically we have chosen to interpret the "cast from zero" syntax
8952+
// as sugar for default initialization, and *not* specifically
8953+
// for zero-initialization. That choice could be revisited if
8954+
// users express displeasure. For now there isn't enough usage
8955+
// of explicit default initializers for `struct` fields to
8956+
// make this a major concern (since they aren't supported in HLSL).
8957+
//
8958+
RefPtr<InitializerListExpr> initListExpr = new InitializerListExpr();
8959+
auto checkedInitListExpr = visitInitializerListExpr(initListExpr);
8960+
return coerce(typeExp.type, initListExpr);
8961+
}
8962+
}
8963+
}
8964+
}
8965+
}
8966+
8967+
88888968
// Now process this like any other explicit call (so casts
88898969
// and constructor calls are semantically equivalent).
88908970
return CheckInvokeExprWithCheckedOperands(expr);
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// cast-zero-to-struct.slang
2+
3+
// Test that HLSL legacy syntax for casting from literal zero
4+
// to a `struct` type works.
5+
6+
//TEST(compute):COMPARE_COMPUTE:
7+
8+
struct S
9+
{
10+
int2 a;
11+
int2 b;
12+
int2 c;
13+
}
14+
15+
int test(int val)
16+
{
17+
S s = (S) 0;
18+
19+
s.a.x = val;
20+
s.b.y = val;
21+
s.c.x = val;
22+
23+
int2 t = s.a + s.b*256 + s.c*65536;
24+
return t.x + t.y*16;
25+
}
26+
27+
//TEST_INPUT: ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
28+
RWStructuredBuffer<int> gOutputBuffer;
29+
30+
[numthreads(4, 1, 1)]
31+
void computeMain(uint3 tid : SV_DispatchThreadID)
32+
{
33+
int inVal = tid.x;
34+
int outVal = test(inVal);
35+
gOutputBuffer[tid.x] = outVal;
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
0
2+
11001
3+
22002
4+
33003

0 commit comments

Comments
 (0)