Files
clang-p2996/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
Greg Roth c2063de159 Switch DirectX Target to use the Itanium ABI (#111632)
To consolidate behavior of function mangling and limit the number of
places that ABI changes will need to be made, this switches the DirectX
target used for HLSL to use the Itanium ABI from the Microsoft ABI. The
Itanium ABI has greater flexibility in decisions regarding mangling of
new types of which we have more than a few yet to add.

One effect of this will be that linking library shaders compiled with
DXC will not be possible with shaders compiled with clang. That isn't
considered a terribly interesting use case and one that would likely
have been onerous to maintain anyway.

This involved adding a function to call all global destructors as the
Microsoft ABI had done.

This requires a few changes to tests. Most notably the mangling style
has changed which accounts for most of the changes. In making those
changes, I took the opportunity to harmonize some very similar tests for
greater consistency. I also shaved off some unneeded run flags that had
probably been copied over from one test to another.

Other changes effected by using the new ABI include using different
types when manipulating smaller bitfields, eliminating an unnecessary
alloca in one instance in this-assignment.hlsl, changing the way static
local initialization is guarded, and changing the order of inout
parameters getting copied in and out. That last is a subtle change in
functionality, but one where there was sufficient inconsistency in the
past that standardizing is important, but the particular direction of
the standardization is less important for the sake of existing shaders.

fixes #110736
2024-10-10 12:58:28 -06:00

331 lines
11 KiB
HLSL

// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -disable-llvm-passes -emit-llvm -finclude-default-header -o - %s | FileCheck %s --check-prefixes=CHECK,ALL
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -O3 -emit-llvm -finclude-default-header -o - %s | FileCheck %s --check-prefixes=OPT,ALL
// Case 1: Simple floating integral conversion.
// In this test case a float value is passed to an inout parameter taking an
// integer. It is converted to an integer on call and converted back after the
// function.
// CHECK: define void {{.*}}trunc_Param{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
void trunc_Param(inout int X) {}
// ALL-LABEL: define noundef float {{.*}}case1
// CHECK: [[F:%.*]] = alloca float
// CHECK: [[ArgTmp:%.*]] = alloca i32
// CHECK: [[FVal:%.*]] = load float, ptr {{.*}}
// CHECK: [[IVal:%.*]] = fptosi float [[FVal]] to i32
// CHECK: store i32 [[IVal]], ptr [[ArgTmp]]
// CHECK: call void {{.*}}trunc_Param{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
// CHECK: [[IRet:%.*]] = load i32, ptr [[ArgTmp]]
// CHECK: [[FRet:%.*]] = sitofp i32 [[IRet]] to float
// CHECK: store float [[FRet]], ptr [[F]]
// OPT: [[IVal:%.*]] = fptosi float {{.*}} to i32
// OPT: [[FVal:%.*]] = sitofp i32 [[IVal]] to float
// OPT: ret float [[FVal]]
export float case1(float F) {
trunc_Param(F);
return F;
}
// Case 2: Uninitialized `out` parameters.
// `out` parameters are not pre-initialized by the caller, so they are
// uninitialized in the function. If they are not initialized before the
// function returns the value is undefined.
// CHECK: define void {{.*}}undef{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
void undef(out int Z) { }
// ALL-LABEL: define noundef i32 {{.*}}case2
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[ArgTmp:%.*]] = alloca i32
// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
// CHECK: call void {{.*}}unde{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
// CHECK: [[Res:%.*]] = load i32, ptr [[ArgTmp]]
// CHECK: store i32 [[Res]], ptr [[V]], align 4
// OPT: ret i32 undef
export int case2() {
int V;
undef(V);
return V;
}
// Case 3: Simple initialized `out` parameter.
// This test should verify that an out parameter value is written to as
// expected.
// CHECK: define void {{.*}}zero{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
void zero(out int Z) { Z = 0; }
// ALL-LABEL: define noundef i32 {{.*}}case3
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[ArgTmp:%.*]] = alloca i32
// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
// CHECK: call void {{.*}}zero{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
// CHECK: [[Res:%.*]] = load i32, ptr [[ArgTmp]]
// CHECK: store i32 [[Res]], ptr [[V]], align 4
// OPT: ret i32 0
export int case3() {
int V;
zero(V);
return V;
}
// Case 4: Vector swizzle arguments.
// Vector swizzles in HLSL produce lvalues, so they can be used as arguments to
// inout parameters and the swizzle is reversed on writeback.
// CHECK: define void {{.*}}funky{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) {{%.*}})
void funky(inout int3 X) {
X.x += 1;
X.y += 2;
X.z += 3;
}
// ALL-LABEL: define noundef {{.*}}<3 x i32> {{.*}}case4
// This block initializes V = 0.xxx.
// CHECK: [[V:%.*]] = alloca <3 x i32>
// CHECK: [[ArgTmp:%.*]] = alloca <3 x i32>
// CHECK: store <1 x i32> zeroinitializer, ptr [[ZeroPtr:%.*]]
// CHECK: [[ZeroV1:%.*]] = load <1 x i32>, ptr [[ZeroPtr]]
// CHECK: [[ZeroV3:%.*]] = shufflevector <1 x i32> [[ZeroV1]], <1 x i32> poison, <3 x i32> zeroinitializer
// CHECK: store <3 x i32> [[ZeroV3]], ptr [[V]]
// Shuffle the vector to the temporary.
// CHECK: [[VVal:%.*]] = load <3 x i32>, ptr [[V]]
// CHECK: [[Vyzx:%.*]] = shufflevector <3 x i32> [[VVal]], <3 x i32> poison, <3 x i32> <i32 1, i32 2, i32 0>
// CHECK: store <3 x i32> [[Vyzx]], ptr [[ArgTmp]]
// Call the function with the temporary.
// CHECK: call void {{.*}}funky{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) [[ArgTmp]])
// Shuffle it back.
// CHECK: [[RetVal:%.*]] = load <3 x i32>, ptr [[ArgTmp]]
// CHECK: [[Vxyz:%.*]] = shufflevector <3 x i32> [[RetVal]], <3 x i32> poison, <3 x i32> <i32 2, i32 0, i32 1>
// CHECK: store <3 x i32> [[Vxyz]], ptr [[V]]
// OPT: ret <3 x i32> <i32 3, i32 1, i32 2>
export int3 case4() {
int3 V = 0.xxx;
funky(V.yzx);
return V;
}
// Case 5: Straightforward inout of a scalar value.
// CHECK: define void {{.*}}increment{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
void increment(inout int I) {
I += 1;
}
// ALL-LABEL: define noundef i32 {{.*}}case5
// CHECK: [[I:%.*]] = alloca i32
// CHECK: [[ArgTmp:%.*]] = alloca i32
// CHECK: store i32 4, ptr [[I]]
// CHECK: [[IInit:%.*]] = load i32, ptr [[I]]
// CHECK: store i32 [[IInit:%.*]], ptr [[ArgTmp]], align 4
// CHECK: call void {{.*}}increment{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
// CHECK: [[RetVal:%.*]] = load i32, ptr [[ArgTmp]]
// CHECK: store i32 [[RetVal]], ptr [[I]], align 4
// OPT: ret i32 5
export int case5() {
int I = 4;
increment(I);
return I;
}
// Case 6: Aggregate out parameters.
struct S {
int X;
float Y;
};
// CHECK: define void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) {{%.*}})
void init(out S s) {
s.X = 3;
s.Y = 4;
}
// ALL-LABEL: define noundef i32 {{.*}}case6
// CHECK: [[S:%.*]] = alloca %struct.S
// CHECK: [[Tmp:%.*]] = alloca %struct.S
// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[S]], ptr align 4 [[Tmp]], i32 8, i1 false)
// OPT: ret i32 7
export int case6() {
S s;
init(s);
return s.X + s.Y;
}
// Case 7: Aggregate inout parameters.
struct R {
int X;
float Y;
};
// CHECK: define void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) {{%.*}})
void init(inout R s) {
s.X = 3;
s.Y = 4;
}
// ALL-LABEL: define noundef i32 {{.*}}case7
// CHECK: [[S:%.*]] = alloca %struct.R
// CHECK: [[Tmp:%.*]] = alloca %struct.R
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp]], ptr align 4 [[S]], i32 8, i1 false)
// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[S]], ptr align 4 [[Tmp]], i32 8, i1 false)
// OPT: ret i32 7
export int case7() {
R s;
init(s);
return s.X + s.Y;
}
// Case 8: Non-scalars with a cast expression.
// CHECK: define void {{.*}}trunc_vec{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) {{%.*}})
void trunc_vec(inout int3 V) {}
// ALL-LABEL: define noundef <3 x float> {{.*}}case8
// CHECK: [[V:%.*]] = alloca <3 x float>
// CHECK: [[Tmp:%.*]] = alloca <3 x i32>
// CHECK: [[FVal:%.*]] = load <3 x float>, ptr [[V]]
// CHECK: [[IVal:%.*]] = fptosi <3 x float> [[FVal]] to <3 x i32>
// CHECK: store <3 x i32> [[IVal]], ptr [[Tmp]]
// CHECK: call void {{.*}}trunc_vec{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) [[Tmp]])
// CHECK: [[IRet:%.*]] = load <3 x i32>, ptr [[Tmp]]
// CHECK: [[FRet:%.*]] = sitofp <3 x i32> [[IRet]] to <3 x float>
// CHECK: store <3 x float> [[FRet]], ptr [[V]]
// OPT: [[IVal:%.*]] = fptosi <3 x float> {{.*}} to <3 x i32>
// OPT: [[FVal:%.*]] = sitofp <3 x i32> [[IVal]] to <3 x float>
// OPT: ret <3 x float> [[FVal]]
export float3 case8(float3 V) {
trunc_vec(V);
return V;
}
// Case 9: Side-effecting lvalue argument expression!
void do_nothing(inout int V) {}
// ALL-LABEL: define noundef i32 {{.*}}case9
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[Tmp:%.*]] = alloca i32
// CHECK: store i32 0, ptr [[V]]
// CHECK: [[VVal:%.*]] = load i32, ptr [[V]]
// CHECK: [[VInc:%.*]] = add nsw i32 [[VVal]], 1
// CHECK: store i32 [[VInc]], ptr [[V]]
// CHECK: [[VArg:%.*]] = load i32, ptr [[V]]
// CHECK-NOT: add
// CHECK: store i32 [[VArg]], ptr [[Tmp]]
// CHECK: call void {{.*}}do_nothing{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp]])
// CHECK: [[RetVal:%.*]] = load i32, ptr [[Tmp]]
// CHECK: store i32 [[RetVal]], ptr [[V]]
// OPT: ret i32 1
export int case9() {
int V = 0;
do_nothing(++V);
return V;
}
// Case 10: Verify argument writeback ordering for aliasing arguments.
void order_matters(inout int X, inout int Y) {
Y = 2;
X = 1;
}
// ALL-LABEL: define noundef i32 {{.*}}case10
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[Tmp0:%.*]] = alloca i32
// CHECK: [[Tmp1:%.*]] = alloca i32
// CHECK: store i32 0, ptr [[V]]
// CHECK: [[VVal:%.*]] = load i32, ptr [[V]]
// CHECK: store i32 [[VVal]], ptr [[Tmp0]]
// CHECK: [[VVal:%.*]] = load i32, ptr [[V]]
// CHECK: store i32 [[VVal]], ptr [[Tmp1]]
// CHECK: call void {{.*}}order_matters{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp0]], ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp1]])
// CHECK: [[Arg1Val:%.*]] = load i32, ptr [[Tmp0]]
// CHECK: store i32 [[Arg1Val]], ptr [[V]]
// CHECK: [[Arg2Val:%.*]] = load i32, ptr [[Tmp1]]
// CHECK: store i32 [[Arg2Val]], ptr [[V]]
// OPT: ret i32 2
export int case10() {
int V = 0;
order_matters(V, V);
return V;
}
// Case 11: Verify inout on bitfield lvalues
struct B {
int X : 8;
int Y : 8;
};
void setFour(inout int I) {
I = 4;
}
// ALL-LABEL: define {{.*}} i32 {{.*}}case11
// CHECK: [[B:%.*]] = alloca %struct.B
// CHECK: [[Tmp:%.*]] = alloca i32
// CHECK: [[BFLoad:%.*]] = load i16, ptr [[B]]
// CHECK: [[BFshl:%.*]] = shl i16 [[BFLoad]], 8
// CHECK: [[BFashr:%.*]] = ashr i16 [[BFshl]], 8
// CHECK: [[BFcast:%.*]] = sext i16 [[BFashr]] to i32
// CHECK: store i32 [[BFcast]], ptr [[Tmp]]
// CHECK: call void {{.*}}setFour{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp]])
// CHECK: [[RetVal:%.*]] = load i32, ptr [[Tmp]]
// CHECK: [[TruncVal:%.*]] = trunc i32 [[RetVal]] to i16
// CHECK: [[BFLoad:%.*]] = load i16, ptr [[B]]
// CHECK: [[BFValue:%.*]] = and i16 [[TruncVal]], 255
// CHECK: [[ZerodField:%.*]] = and i16 [[BFLoad]], -256
// CHECK: [[BFSet:%.*]] = or i16 [[ZerodField]], [[BFValue]]
// CHECK: store i16 [[BFSet]], ptr [[B]]
// OPT: ret i32 8
export int case11() {
B b = {1 , 2};
setFour(b.X);
return b.X * b.Y;
}
// Case 12: Uninitialized out parameters are undefined
void oops(out int X) {}
// ALL-LABEL: define {{.*}} i32 {{.*}}case12
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[Tmp:%.*]] = alloca i32
// CHECK-NOT: store {{.*}}, ptr [[Tmp]]
// CHECK: call void {{.*}}oops{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp]])
// CHECK: [[ArgVal:%.*]] = load i32, ptr [[Tmp]]
// CHECK: store i32 [[ArgVal]], ptr [[V]]
// OPT: ret i32 undef
export int case12() {
int V = 0;
oops(V);
return V;
}