
Implements https://github.com/llvm/wg-hlsl/blob/main/proposals/0026-symbol-visibility.md. The change is to stop using the `hlsl.export` attribute. Instead, symbols with "program linkage" in HLSL will have export linkage with default visibility, and symbols with "external linkage" in HLSL will have export linkage with hidden visibility.
325 lines
10 KiB
HLSL
325 lines
10 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]])
|
|
|
|
// 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 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;
|
|
}
|