When individual elements of a vector are updated via vector swizzle, it needs to be handled as separate store operations to the individual vector elements. Clang treats vectors as one unit, so if a part of a vector needs to be updated, the whole vector is loaded, some elements modified, and then the whole vector is stored. In HLSL vector elements are handled separately. We need to avoid this load/modify/store sequence to prevent overwriting other vector elements that might be getting updated in parallel. Fixes #152815
331 lines
11 KiB
HLSL
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 hidden void {{.*}}trunc_Param{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
|
|
void trunc_Param(inout int X) {}
|
|
|
|
// ALL-LABEL: define noundef nofpclass(nan inf) 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 hidden 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 hidden 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 hidden 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]])
|
|
|
|
// Write it back.
|
|
// CHECK: [[RetVal:%.*]] = load <3 x i32>, ptr [[ArgTmp]]
|
|
// CHECK: [[Src0:%.*]] = extractelement <3 x i32> [[RetVal]], i32 0
|
|
// CHECK: [[PtrY:%.*]] = getelementptr <3 x i32>, ptr %V, i32 0, i32 1
|
|
// CHECK: store i32 [[Src0]], ptr [[PtrY]], align 4
|
|
// CHECK: [[Src1:%.*]] = extractelement <3 x i32> [[RetVal]], i32 1
|
|
// CHECK: [[PtrZ:%.*]] = getelementptr <3 x i32>, ptr %V, i32 0, i32 2
|
|
// CHECK: store i32 [[Src1]], ptr [[PtrZ]], align 4
|
|
// CHECK: [[Src2:%.*]] = extractelement <3 x i32> [[RetVal]], i32 2
|
|
// CHECK: store i32 [[Src2]], ptr %V, align 4
|
|
|
|
// 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 hidden 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 hidden void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 1 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 1 dereferenceable(8) [[Tmp]])
|
|
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[S]], ptr align 1 [[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 hidden void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 1 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 1 [[Tmp]], ptr align 1 [[S]], i32 8, i1 false)
|
|
// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 1 dereferenceable(8) [[Tmp]])
|
|
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[S]], ptr align 1 [[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 hidden void {{.*}}trunc_vec{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) {{%.*}})
|
|
void trunc_vec(inout int3 V) {}
|
|
|
|
// ALL-LABEL: define noundef nofpclass(nan inf) <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 i8, ptr [[B]]
|
|
// CHECK: [[BFcast:%.*]] = sext i8 [[BFLoad]] 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 i8
|
|
// CHECK: store i8 [[TruncVal]], 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;
|
|
}
|