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

Conversation

spall
Copy link
Contributor

@spall spall commented Oct 3, 2024

Get inout/out parameters working for HLSL Arrays.
Utilizes the fix from #109323, and corrects the assignment behavior slightly to allow for Non-LValues on the RHS.
Closes #106917

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen HLSL HLSL Language Support labels Oct 3, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Oct 3, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Sarah Spall (spall)

Changes

Get inout/out parameters working for HLSL Arrays.
Utilizes the fix from #109323, and corrects the assignment behavior slightly to allow for Non-LValues on the RHS.
Closes #106917


Patch is 21.41 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/111047.diff

13 Files Affected:

  • (modified) clang/include/clang/AST/ASTContext.h (+4)
  • (modified) clang/lib/AST/ASTContext.cpp (+10)
  • (modified) clang/lib/CodeGen/CGCall.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+5-2)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+10-3)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+24-19)
  • (modified) clang/lib/Sema/SemaType.cpp (+3-1)
  • (added) clang/test/AST/HLSL/ArrayOutArgExpr.hlsl (+63)
  • (modified) clang/test/CodeGenHLSL/ArrayAssignable.hlsl (+9-11)
  • (modified) clang/test/CodeGenHLSL/ArrayTemporary.hlsl (+4-6)
  • (added) clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl (+48)
  • (modified) clang/test/SemaHLSL/ArrayTemporary.hlsl (-3)
  • (added) clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl (+51)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index a4d36f2eacd5d1..bdd4d86c30d389 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -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;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 735def67f7840f..b49783176f79d8 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -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;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 4ae981e4013e9c..fe578cacc64de1 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4690,7 +4690,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
     return emitWritebackArg(*this, args, CRE);
   }
 
-  assert(type->isReferenceType() == E->isGLValue() &&
+  assert(type->isArrayParameterType() ||
+	 (type->isReferenceType() == E->isGLValue()) &&
          "reference binding to unmaterialized r-value!");
 
   // Add writeback for HLSLOutParamExpr.
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index aaaa4c7cbf2aec..9f7ce71b30b498 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -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;
 }
 
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index b30414a8a8277a..89df42a837d024 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -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)
+    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:
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 2cde8131108fbe..6dd6bdbd336860 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -2212,26 +2212,9 @@ 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() &&
+  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
@@ -2239,6 +2222,9 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
     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;
     }
@@ -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;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c44fc9c4194ca4..8c80ece635fbff 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -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);
 }
 
@@ -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);
   }
 }
diff --git a/clang/test/AST/HLSL/ArrayOutArgExpr.hlsl b/clang/test/AST/HLSL/ArrayOutArgExpr.hlsl
new file mode 100644
index 00000000000000..10825bf0f93bc7
--- /dev/null
+++ b/clang/test/AST/HLSL/ArrayOutArgExpr.hlsl
@@ -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;
+}
diff --git a/clang/test/CodeGenHLSL/ArrayAssignable.hlsl b/clang/test/CodeGenHLSL/ArrayAssignable.hlsl
index a0dfe26e5d147b..e2ff2de68ed990 100644
--- a/clang/test/CodeGenHLSL/ArrayAssignable.hlsl
+++ b/clang/test/CodeGenHLSL/ArrayAssignable.hlsl
@@ -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}};
diff --git a/clang/test/CodeGenHLSL/ArrayTemporary.hlsl b/clang/test/CodeGenHLSL/ArrayTemporary.hlsl
index 63a30b61440eb5..c254fe76a8eaf4 100644
--- a/clang/test/CodeGenHLSL/ArrayTemporary.hlsl
+++ b/clang/test/CodeGenHLSL/ArrayTemporary.hlsl
@@ -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]) {
@@ -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>
@@ -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]+]]
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl
new file mode 100644
index 00000000000000..d4a5cdb9445a7f
--- /dev/null
+++ b/clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl
@@ -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];
+}
diff --git a/clang/test/SemaHLSL/ArrayTemporary.hlsl b/clang/test/SemaHLSL/ArrayTemporary.hlsl
index dff9aff7d9b299..f1e446d8d3f8f1 100644
--- a/clang/test/SemaHLSL/ArrayTemporary.hlsl
+++ b/clang/test/SemaHLSL/ArrayTemporary.hlsl
@@ -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]) {
diff --git a/clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl b/clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl
new file mode 100644
index 00000000000000..46bed0d5a7cbdc
--- /dev/null
+++ b/clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl
@@ -0,0 +1,51 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -verify
+
+void increment(inout int Arr[2]) {
+  for (int I = 0; I < 2; I++)
+    Arr[0] += 2;
+}
+
+export int wrongSize() {
+  int A[3] = { 0, 1, 2 };
+  increment(A);
+  // expected-error@-1 {{no matching function for call to 'increment'}}
+  // expected-note@*:* {{candidate function not viable: no ...
[truncated]

@llvmbot
Copy link
Collaborator

llvmbot commented Oct 3, 2024

@llvm/pr-subscribers-hlsl

Author: Sarah Spall (spall)

Changes

Get inout/out parameters working for HLSL Arrays.
Utilizes the fix from #109323, and corrects the assignment behavior slightly to allow for Non-LValues on the RHS.
Closes #106917


Patch is 21.41 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/111047.diff

13 Files Affected:

  • (modified) clang/include/clang/AST/ASTContext.h (+4)
  • (modified) clang/lib/AST/ASTContext.cpp (+10)
  • (modified) clang/lib/CodeGen/CGCall.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+5-2)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+10-3)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+24-19)
  • (modified) clang/lib/Sema/SemaType.cpp (+3-1)
  • (added) clang/test/AST/HLSL/ArrayOutArgExpr.hlsl (+63)
  • (modified) clang/test/CodeGenHLSL/ArrayAssignable.hlsl (+9-11)
  • (modified) clang/test/CodeGenHLSL/ArrayTemporary.hlsl (+4-6)
  • (added) clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl (+48)
  • (modified) clang/test/SemaHLSL/ArrayTemporary.hlsl (-3)
  • (added) clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl (+51)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index a4d36f2eacd5d1..bdd4d86c30d389 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -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;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 735def67f7840f..b49783176f79d8 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -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;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 4ae981e4013e9c..fe578cacc64de1 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4690,7 +4690,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
     return emitWritebackArg(*this, args, CRE);
   }
 
-  assert(type->isReferenceType() == E->isGLValue() &&
+  assert(type->isArrayParameterType() ||
+	 (type->isReferenceType() == E->isGLValue()) &&
          "reference binding to unmaterialized r-value!");
 
   // Add writeback for HLSLOutParamExpr.
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index aaaa4c7cbf2aec..9f7ce71b30b498 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -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;
 }
 
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index b30414a8a8277a..89df42a837d024 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -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)
+    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:
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 2cde8131108fbe..6dd6bdbd336860 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -2212,26 +2212,9 @@ 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() &&
+  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
@@ -2239,6 +2222,9 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
     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;
     }
@@ -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;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c44fc9c4194ca4..8c80ece635fbff 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -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);
 }
 
@@ -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);
   }
 }
diff --git a/clang/test/AST/HLSL/ArrayOutArgExpr.hlsl b/clang/test/AST/HLSL/ArrayOutArgExpr.hlsl
new file mode 100644
index 00000000000000..10825bf0f93bc7
--- /dev/null
+++ b/clang/test/AST/HLSL/ArrayOutArgExpr.hlsl
@@ -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;
+}
diff --git a/clang/test/CodeGenHLSL/ArrayAssignable.hlsl b/clang/test/CodeGenHLSL/ArrayAssignable.hlsl
index a0dfe26e5d147b..e2ff2de68ed990 100644
--- a/clang/test/CodeGenHLSL/ArrayAssignable.hlsl
+++ b/clang/test/CodeGenHLSL/ArrayAssignable.hlsl
@@ -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}};
diff --git a/clang/test/CodeGenHLSL/ArrayTemporary.hlsl b/clang/test/CodeGenHLSL/ArrayTemporary.hlsl
index 63a30b61440eb5..c254fe76a8eaf4 100644
--- a/clang/test/CodeGenHLSL/ArrayTemporary.hlsl
+++ b/clang/test/CodeGenHLSL/ArrayTemporary.hlsl
@@ -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]) {
@@ -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>
@@ -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]+]]
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl
new file mode 100644
index 00000000000000..d4a5cdb9445a7f
--- /dev/null
+++ b/clang/test/CodeGenHLSL/BasicFeatures/ArrayOutputArguments.hlsl
@@ -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];
+}
diff --git a/clang/test/SemaHLSL/ArrayTemporary.hlsl b/clang/test/SemaHLSL/ArrayTemporary.hlsl
index dff9aff7d9b299..f1e446d8d3f8f1 100644
--- a/clang/test/SemaHLSL/ArrayTemporary.hlsl
+++ b/clang/test/SemaHLSL/ArrayTemporary.hlsl
@@ -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]) {
diff --git a/clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl b/clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl
new file mode 100644
index 00000000000000..46bed0d5a7cbdc
--- /dev/null
+++ b/clang/test/SemaHLSL/Language/ArrayOutputArgs-errors.hlsl
@@ -0,0 +1,51 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -verify
+
+void increment(inout int Arr[2]) {
+  for (int I = 0; I < 2; I++)
+    Arr[0] += 2;
+}
+
+export int wrongSize() {
+  int A[3] = { 0, 1, 2 };
+  increment(A);
+  // expected-error@-1 {{no matching function for call to 'increment'}}
+  // expected-note@*:* {{candidate function not viable: no ...
[truncated]

Copy link

github-actions bot commented Oct 3, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

[HLSL] inout/out ABI for array parameters
2 participants