Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HLSL] get inout/out ABI for array parameters working #111047

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// type to the decayed type.
QualType getDecayedType(QualType Orig, QualType Decayed) const;

/// Return the uniqued reference to a constant array type from the
/// original array parameter type.
QualType getConstantArrayFromArrayParameterType(QualType Ty) const;

/// Return the uniqued reference to a specified array parameter type from the
/// original array type.
QualType getArrayParameterType(QualType Ty) const;
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3830,6 +3830,16 @@ QualType ASTContext::getDecayedType(QualType T) const {
return getDecayedType(T, Decayed);
}

QualType ASTContext::getConstantArrayFromArrayParameterType(QualType Ty) const {
if (Ty->isConstantArrayType() && !Ty->isArrayParameterType())
return Ty;
assert(Ty->isArrayParameterType() && "Ty must be an array parameter type.");
const auto *ATy = cast<ArrayParameterType>(Ty);
return getConstantArrayType(ATy->getElementType(), ATy->getSize(),
ATy->getSizeExpr(), ATy->getSizeModifier(),
ATy->getIndexTypeQualifiers().getAsOpaqueValue());
}

QualType ASTContext::getArrayParameterType(QualType Ty) const {
if (Ty->isArrayParameterType())
return Ty;
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4690,8 +4690,9 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
return emitWritebackArg(*this, args, CRE);
}

assert(type->isReferenceType() == E->isGLValue() &&
"reference binding to unmaterialized r-value!");
assert(type->isArrayParameterType() ||
(type->isReferenceType() == E->isGLValue()) &&
"reference binding to unmaterialized r-value!");

// Add writeback for HLSLOutParamExpr.
if (const HLSLOutArgExpr *OE = dyn_cast<HLSLOutArgExpr>(E)) {
Expand Down
7 changes: 5 additions & 2 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5811,9 +5811,12 @@ LValue CodeGenFunction::EmitBinaryOperatorLValue(const BinaryOperator *E) {
// This function implements trivial copy assignment for HLSL's
// assignable constant arrays.
LValue CodeGenFunction::EmitHLSLArrayAssignLValue(const BinaryOperator *E) {
LValue TrivialAssignmentRHS = EmitLValue(E->getRHS());
// Don't emit an LValue for the RHS because it might not be an LValue
LValue LHS = EmitLValue(E->getLHS());
EmitAggregateAssign(LHS, TrivialAssignmentRHS, E->getLHS()->getType());
// In C assignment operator RHS is often an RValue.
// EmitAggregateAssign expects an LValue for the RHS so call the below
// function instead.
EmitInitializationToLValue(E->getRHS(), LHS);
return LHS;
}

Expand Down
15 changes: 11 additions & 4 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4424,10 +4424,17 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
break;

case ICK_HLSL_Array_RValue:
FromType = Context.getArrayParameterType(FromType);
From = ImpCastExprToType(From, FromType, CK_HLSLArrayRValue, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
if (ToType->isArrayParameterType()) {
FromType = Context.getArrayParameterType(FromType);
From = ImpCastExprToType(From, FromType, CK_HLSLArrayRValue, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
} else { // FromType must be ArrayParameterType
FromType = Context.getConstantArrayFromArrayParameterType(FromType);
From = ImpCastExprToType(From, FromType, CK_HLSLArrayRValue, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
}
break;

case ICK_Function_To_Pointer:
Expand Down
45 changes: 25 additions & 20 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2212,33 +2212,19 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
return false;
}
}
// Lvalue-to-rvalue conversion (C++11 4.1):
// A glvalue (3.10) of a non-function, non-array type T can
// be converted to a prvalue.
bool argIsLValue = From->isGLValue();
if (argIsLValue && !FromType->canDecayToPointerType() &&
S.Context.getCanonicalType(FromType) != S.Context.OverloadTy) {
SCS.First = ICK_Lvalue_To_Rvalue;

// C11 6.3.2.1p2:
// ... if the lvalue has atomic type, the value has the non-atomic version
// of the type of the lvalue ...
if (const AtomicType *Atomic = FromType->getAs<AtomicType>())
FromType = Atomic->getValueType();

// If T is a non-class type, the type of the rvalue is the
// cv-unqualified version of T. Otherwise, the type of the rvalue
// is T (C++ 4.1p1). C++ can't get here with class types; in C, we
// just strip the qualifiers because they don't matter.
FromType = FromType.getUnqualifiedType();
} else if (S.getLangOpts().HLSL && FromType->isConstantArrayType() &&
ToType->isConstantArrayType()) {
bool argIsLValue = From->isGLValue();
if (S.getLangOpts().HLSL && FromType->isConstantArrayType() &&
ToType->isConstantArrayType()) {
// HLSL constant array parameters do not decay, so if the argument is a
// constant array and the parameter is an ArrayParameterType we have special
// handling here.
if (ToType->isArrayParameterType()) {
FromType = S.Context.getArrayParameterType(FromType);
SCS.First = ICK_HLSL_Array_RValue;
} else if (FromType->isArrayParameterType()) {
FromType = S.Context.getConstantArrayFromArrayParameterType(FromType);
SCS.First = ICK_HLSL_Array_RValue;
} else {
SCS.First = ICK_Identity;
}
Expand All @@ -2249,6 +2235,25 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,

SCS.setAllToTypes(ToType);
return true;
} else if (argIsLValue && !FromType->canDecayToPointerType() &&
S.Context.getCanonicalType(FromType) != S.Context.OverloadTy) {
// Lvalue-to-rvalue conversion (C++11 4.1):
// A glvalue (3.10) of a non-function, non-array type T can
// be converted to a prvalue.

SCS.First = ICK_Lvalue_To_Rvalue;

// C11 6.3.2.1p2:
// ... if the lvalue has atomic type, the value has the non-atomic version
// of the type of the lvalue ...
if (const AtomicType *Atomic = FromType->getAs<AtomicType>())
FromType = Atomic->getValueType();

// If T is a non-class type, the type of the rvalue is the
// cv-unqualified version of T. Otherwise, the type of the rvalue
// is T (C++ 4.1p1). C++ can't get here with class types; in C, we
// just strip the qualifiers because they don't matter.
FromType = FromType.getUnqualifiedType();
} else if (FromType->isArrayType()) {
// Array-to-pointer conversion (C++ 4.2)
SCS.First = ICK_Array_To_Pointer;
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5675,6 +5675,9 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
assert(!T.isNull() && "T must not be null at the end of this function");
if (!AreDeclaratorChunksValid)
return Context.getTrivialTypeSourceInfo(T);

if (state.didParseHLSLParamMod() && !T->isConstantArrayType())
T = S.HLSL().getInoutParameterType(T);
return GetTypeSourceInfoForDeclarator(state, T, TInfo);
}

Expand Down Expand Up @@ -8562,7 +8565,6 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
return;
if (Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_inout ||
Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out) {
CurType = S.HLSL().getInoutParameterType(CurType);
State.setParsedHLSLParamMod(true);
}
}
Expand Down
63 changes: 63 additions & 0 deletions clang/test/AST/HLSL/ArrayOutArgExpr.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -ast-dump %s | FileCheck %s

// CHECK-LABEL: increment
void increment(inout int Arr[2]) {
for (int I = 0; I < 2; I++)
Arr[0] += 2;
}

// CHECK-LABEL: call
// CHECK: CallExpr 0x{{.*}} {{.*}} 'void'
// CHECK: ImplicitCastExpr 0x{{.*}} {{.*}} 'void (*)(inout int[2])' <FunctionToPointerDecay>
// CHECK: DeclRefExpr 0x{{.*}} {{.*}} 'void (inout int[2])' lvalue Function 0x{{.*}} 'increment' 'void (inout int[2])'
// CHECK: HLSLOutArgExpr 0x{{.*}} {{.*}} 'int[2]' lvalue inout
// CHECK: OpaqueValueExpr [[A:0x.*]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B:0x.*]] {{.*}} 'int[2]' lvalue Var [[E:0x.*]] 'A' 'int[2]'
// CHECK: OpaqueValueExpr [[C:0x.*]] {{.*}} 'int[2]' lvalue
// CHECK: ImplicitCastExpr [[D:0x.*]] {{.*}} 'int[2]' <HLSLArrayRValue>
// CHECK: OpaqueValueExpr [[A]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B]] {{.*}} 'int[2]' lvalue Var [[E]] 'A' 'int[2]'
// CHECK: BinaryOperator 0x{{.*}} {{.*}} 'int[2]' lvalue '='
// CHECK: OpaqueValueExpr [[A]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr 0x{{.*}} {{.*}} 'int[2]' lvalue Var [[E]] 'A' 'int[2]'
// CHECK: ImplicitCastExpr 0x{{.*}} {{.*}} 'int[2]' <HLSLArrayRValue>
// CHECK: OpaqueValueExpr [[C]] {{.*}} 'int[2]' lvalue
// CHECK: ImplicitCastExpr [[D]] {{.*}} 'int[2]' <HLSLArrayRValue>
// CHECK: OpaqueValueExpr [[A]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B]] {{.*}} 'int[2]' lvalue Var [[E]] 'A' 'int[2]'
export int call() {
int A[2] = { 0, 1 };
increment(A);
return A[0];
}

// CHECK-LABEL: fn2
void fn2(out int Arr[2]) {
Arr[0] += 5;
Arr[1] += 6;
}

// CHECK-LABEL: call2
// CHECK: CallExpr 0x{{.*}} {{.*}} 'void'
// CHECK: ImplicitCastExpr 0x{{.*}} {{.*}} 'void (*)(out int[2])' <FunctionToPointerDecay>
// CHECK: DeclRefExpr 0x{{.*}} {{.*}} 'void (out int[2])' lvalue Function 0x{{.*}} 'fn2' 'void (out int[2])'
// CHECK: HLSLOutArgExpr 0x{{.*}} {{.*}} 'int[2]' lvalue out
// CHECK: OpaqueValueExpr [[A:0x.*]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B:0x.*]] {{.*}} 'int[2]' lvalue Var [[E:0x.*]] 'A' 'int[2]'
// CHECK: OpaqueValueExpr [[C:0x.*]] {{.*}} 'int[2]' lvalue
// CHECK: ImplicitCastExpr [[D:0x.*]] {{.*}} 'int[2]' <HLSLArrayRValue>
// CHECK: OpaqueValueExpr [[A]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B]] {{.*}} 'int[2]' lvalue Var [[E]] 'A' 'int[2]'
// CHECK: BinaryOperator 0x{{.*}} {{.*}} 'int[2]' lvalue '='
// CHECK: OpaqueValueExpr [[A]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B]] {{.*}} 'int[2]' lvalue Var [[E]] 'A' 'int[2]'
// CHECK: ImplicitCastExpr 0x{{.*}} {{.*}} 'int[2]' <HLSLArrayRValue>
// CHECK: OpaqueValueExpr [[C]] {{.*}} 'int[2]' lvalue
// CHECK: ImplicitCastExpr [[D]] {{.*}} 'int[2]' <HLSLArrayRValue>
// CHECK: OpaqueValueExpr [[A]] {{.*}} 'int[2]' lvalue
// CHECK: DeclRefExpr [[B]] {{.*}} 'int[2]' lvalue Var [[E]] 'A' 'int[2]'
export int call2() {
int A[2] = { 0, 1 };
fn2(A);
return 1;
}
20 changes: 9 additions & 11 deletions clang/test/CodeGenHLSL/ArrayAssignable.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,16 @@ void arr_assign6() {
}

// CHECK-LABEL: define void {{.*}}arr_assign7
// CHECK: [[Arr3:%.*]] = alloca [2 x [2 x i32]], align 4
// CHECK-NEXT: [[Arr4:%.*]] = alloca [2 x [2 x i32]], align 4
// CHECK-NEXT: [[Tmp:%.*]] = alloca [2 x i32], align 4
// CHECK: [[Arr:%.*]] = alloca [2 x [2 x i32]], align 4
// CHECK-NEXT: [[Arr2:%.*]] = alloca [2 x [2 x i32]], align 4
// CHECK-NOT: alloca
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Arr3]], ptr align 4 {{@.*}}, i32 16, i1 false)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Arr4]], ptr align 4 {{@.*}}, i32 16, i1 false)
// CHECK-NEXT: store i32 6, ptr [[Tmp]], align 4
// CHECK-NEXT: [[AIE:%.*]] = getelementptr inbounds i32, ptr [[Tmp]], i32 1
// CHECK-NEXT: store i32 6, ptr [[AIE]], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Arr3]], ptr align 4 [[Arr4]], i32 16, i1 false)
// CHECK-NEXT: [[Idx:%.*]] = getelementptr inbounds [2 x [2 x i32]], ptr [[Arr3]], i32 0, i32 0
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Idx]], ptr align 4 [[Tmp]], i32 8, i1 false)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Arr]], ptr align 4 {{@.*}}, i32 16, i1 false)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Arr2]], ptr align 4 {{@.*}}, i32 16, i1 false)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Arr]], ptr align 4 [[Arr2]], i32 16, i1 false)
// CHECK-NEXT: [[Idx:%.*]] = getelementptr inbounds [2 x [2 x i32]], ptr [[Arr]], i32 0, i32 0
// CHECK-NEXT: store i32 6, ptr [[Idx]], align 4
// CHECK-NEXT: [[Idx2:%.*]] = getelementptr inbounds i32, ptr %arrayidx, i32 1
// CHECK-NEXT: store i32 6, ptr [[Idx2]], align 4
// CHECK-NEXT: ret void
void arr_assign7() {
int Arr[2][2] = {{0, 1}, {2, 3}};
Expand Down
10 changes: 4 additions & 6 deletions clang/test/CodeGenHLSL/ArrayTemporary.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ void call3() {
}

// CHECK-LABEL: define void {{.*}}call4{{.*}}(ptr
// CHECK-SAME: noundef byval([2 x [2 x float]]) align 4 [[Arr:%.*]])
// CHECK: [[Tmp:%.*]] = alloca [2 x [2 x float]]
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp]], ptr align 4 [[Arr]], i32 16, i1 false)
// CHECK: store ptr {{.*}}, ptr [[Tmp]], align 4
// CHECK: call void {{.*}}fn3{{.*}}(ptr noundef byval([2 x [2 x float]]) align 4 [[Tmp]])

void call4(float Arr[2][2]) {
Expand All @@ -67,11 +66,11 @@ void call4(float Arr[2][2]) {
// CHECK: [[Tmp1:%.*]] = alloca [2 x float]
// CHECK: [[Tmp2:%.*]] = alloca [4 x float]
// CHECK: [[Tmp3:%.*]] = alloca [3 x i32]
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp1]], ptr align 4 [[FA2]], i32 8, i1 false)
// CHECK: store ptr {{.*}}, ptr [[Tmp1]], align 4
// CHECK: call void @"??$template_fn@$$BY01M@@YAXY01M@Z"(ptr noundef byval([2 x float]) align 4 [[Tmp1]])
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp2]], ptr align 4 [[FA4]], i32 16, i1 false)
// CHECK: store ptr {{.*}}, ptr [[Tmp2]], align 4
// CHECK: call void @"??$template_fn@$$BY03M@@YAXY03M@Z"(ptr noundef byval([4 x float]) align 4 [[Tmp2]])
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp3]], ptr align 4 [[IA3]], i32 12, i1 false)
// CHECK: store ptr {{.*}}, ptr [[Tmp3]], align 4
// CHECK: call void @"??$template_fn@$$BY02H@@YAXY02H@Z"(ptr noundef byval([3 x i32]) align 4 [[Tmp3]])

template<typename T>
Expand All @@ -83,7 +82,6 @@ void template_call(float FA2[2], float FA4[4], int IA3[3]) {
template_fn(IA3);
}


// Verify that Array parameter element access correctly codegens.
// CHECK-LABEL: define void {{.*}}element_access{{.*}}(ptr
// CHECK-SAME: noundef byval([2 x float]) align 4 [[FA2:%[0-9A-Z]+]]
Expand Down
48 changes: 48 additions & 0 deletions clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -disable-llvm-passes -emit-llvm -finclude-default-header -o - %s | FileCheck %s

// CHECK-LABEL: increment
void increment(inout int Arr[2]) {
for (int I = 0; I < 2; I++)
Arr[0] += 2;
}

// CHECK-LABEL: call
// CHECK: [[A:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: [[Tmp:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: [[Tmp2:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[A]], ptr align 4 @{{.*}}, i32 8, i1 false)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp]], ptr align 4 [[A]], i32 8, i1 false)
// CHECK-NEXT: store ptr [[Tmp]], ptr [[Tmp2]], align 4
// CHECK-NEXT: call void @"?increment{{.*}}(ptr noalias noundef byval([2 x i32]) align 4 [[Tmp2]]) #3
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[A]], ptr align 4 [[Tmp]], i32 8, i1 false)
// CHECK-NEXT: [[Idx:%.*]] = getelementptr inbounds [2 x i32], ptr [[A]], i32 0, i32 0
// CHECK-NEXT: [[B:%.*]] = load i32, ptr [[Idx]], align 4
// CHECK-NEXT: ret i32 [[B]]
export int call() {
int A[2] = { 0, 1 };
increment(A);
return A[0];
}

// CHECK-LABEL: fn2
void fn2(out int Arr[2]) {
Arr[0] += 5;
Arr[1] += 6;
}

// CHECK-LABEL: call2
// CHECK: [[A:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: [[Tmp:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: [[Tmp2:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[A]], ptr align 4 @{{.*}}, i32 8, i1 false)
// CHECK-NEXT: store ptr [[Tmp]], ptr [[Tmp2]], align 4
// CHECK-NEXT: call void @"?fn2{{.*}}(ptr noalias noundef byval([2 x i32]) align 4 [[Tmp2]]) #3
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[A]], ptr align 4 [[Tmp]], i32 8, i1 false)
// CHECK-NEXT: [[Idx:%.*]] = getelementptr inbounds [2 x i32], ptr [[A]], i32 0, i32 0
// CHECK-NEXT: [[B:%.*]] = load i32, ptr [[Idx]], align 4
// CHECK-NEXT: ret i32 [[B]]
export int call2() {
int A[2] = { 0, 1 };
fn2(A);
return A[0];
}
3 changes: 0 additions & 3 deletions clang/test/SemaHLSL/ArrayTemporary.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,14 @@ void template_fn(T Val) {}
// CHECK: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float[2])' <FunctionToPointerDecay>
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float[2])' lvalue Function {{.*}} 'template_fn' 'void (float[2])' (FunctionTemplate {{.*}} 'template_fn')
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float[2]' <LValueToRValue>
// CHECK-NEXT: DeclRefExpr {{.*}} 'float[2]' lvalue ParmVar {{.*}} 'FA2' 'float[2]'
// CHECK-NEXT: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float[4])' <FunctionToPointerDecay>
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float[4])' lvalue Function {{.*}} 'template_fn' 'void (float[4])' (FunctionTemplate {{.*}} 'template_fn')
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float[4]' <LValueToRValue>
// CHECK-NEXT: DeclRefExpr {{.*}} 'float[4]' lvalue ParmVar {{.*}} 'FA4' 'float[4]'
// CHECK-NEXT: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(int[3])' <FunctionToPointerDecay>
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (int[3])' lvalue Function {{.*}} 'template_fn' 'void (int[3])' (FunctionTemplate {{.*}} 'template_fn')
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int[3]' <LValueToRValue>
// CHECK-NEXT: DeclRefExpr {{.*}} 'int[3]' lvalue ParmVar {{.*}} 'IA3' 'int[3]'

void call(float FA2[2], float FA4[4], int IA3[3]) {
Expand Down
Loading
Loading