diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -2450,6 +2450,12 @@ return isX86VectorCallAggregateSmallEnough(NumMembers); } + ABIArgInfo classifyArgForArm64ECVarArg(QualType Ty) { + unsigned FreeSSERegs = 0; + return classify(Ty, FreeSSERegs, /*IsReturnType=*/false, + /*IsVectorCall=*/false, /*IsRegCall=*/false); + } + private: ABIArgInfo classify(QualType Ty, unsigned &FreeSSERegs, bool IsReturnType, bool IsVectorCall, bool IsRegCall) const; @@ -5735,6 +5741,13 @@ unsigned CallingConvention) const { Ty = useFirstFieldIfTransparentUnion(Ty); + if (IsVariadic && getTarget().getTriple().isWindowsArm64EC()) { + // Arm64EC varargs functions use the x86_64 classification rules, + // not the AArch64 ABI rules. + WinX86_64ABIInfo Win64ABIInfo(CGT, X86AVXABILevel::None); + return Win64ABIInfo.classifyArgForArm64ECVarArg(Ty); + } + // Handle illegal vector types here. if (isIllegalVectorType(Ty)) return coerceIllegalVector(Ty); @@ -6258,9 +6271,16 @@ QualType Ty) const { bool IsIndirect = false; - // Composites larger than 16 bytes are passed by reference. - if (isAggregateTypeForABI(Ty) && getContext().getTypeSize(Ty) > 128) - IsIndirect = true; + if (getTarget().getTriple().isWindowsArm64EC()) { + // MS x64 ABI requirement: "Any argument that doesn't fit in 8 bytes, or is + // not 1, 2, 4, or 8 bytes, must be passed by reference." + uint64_t Width = getContext().getTypeSize(Ty); + IsIndirect = Width > 64 || !llvm::isPowerOf2_64(Width); + } else { + // Composites larger than 16 bytes are passed by reference. + if (isAggregateTypeForABI(Ty) && getContext().getTypeSize(Ty) > 128) + IsIndirect = true; + } return emitVoidPtrVAArg(CGF, VAListAddr, Ty, IsIndirect, CGF.getContext().getTypeInfoInChars(Ty), diff --git a/clang/test/CodeGen/arm64ec.c b/clang/test/CodeGen/arm64ec.c --- a/clang/test/CodeGen/arm64ec.c +++ b/clang/test/CodeGen/arm64ec.c @@ -1,7 +1,62 @@ -// RUN: %clang_cc1 -no-opaque-pointers -triple arm64ec-windows-msvc -emit-llvm -o - %s | FileCheck %s +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals --include-generated-funcs --global-value-regex "f" +// RUN: %clang_cc1 -opaque-pointers -triple arm64ec-windows-msvc -emit-llvm -o - %s | FileCheck %s -// CHECK: @g = alias void ([2 x float], [4 x float]), void ([2 x float], [4 x float])* @"#g" -// CHECK: define dso_local void @"#g" typedef struct { float x[2]; } A; typedef struct { float x[4]; } B; -void g(A a, B b) { } +void f(A a, ...) { + __builtin_va_list b; + __builtin_va_start(b, a); + float x = __builtin_va_arg(b, A).x[0]; + float y = __builtin_va_arg(b, B).x[0]; +} +void g(A a, B b) { f(a, b); } + +//. +// CHECK: @g = alias void ([2 x float], [4 x float]), ptr @"#g" +//. +// CHECK-LABEL: @"#f"( +// CHECK-NEXT: entry: +// CHECK-NEXT: [[A:%.*]] = alloca [[STRUCT_A:%.*]], align 4 +// CHECK-NEXT: [[B:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: [[X:%.*]] = alloca float, align 4 +// CHECK-NEXT: [[TMP:%.*]] = alloca [[STRUCT_A]], align 4 +// CHECK-NEXT: [[Y:%.*]] = alloca float, align 4 +// CHECK-NEXT: [[TMP2:%.*]] = alloca [[STRUCT_B:%.*]], align 4 +// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 0 +// CHECK-NEXT: store i64 [[A_COERCE:%.*]], ptr [[COERCE_DIVE]], align 4 +// CHECK-NEXT: call void @llvm.va_start(ptr [[B]]) +// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[B]], align 8 +// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr [[ARGP_CUR]], i64 8 +// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[B]], align 8 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP]], ptr align 8 [[ARGP_CUR]], i64 8, i1 false) +// CHECK-NEXT: [[X1:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[TMP]], i32 0, i32 0 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x float], ptr [[X1]], i64 0, i64 0 +// CHECK-NEXT: [[TMP0:%.*]] = load float, ptr [[ARRAYIDX]], align 4 +// CHECK-NEXT: store float [[TMP0]], ptr [[X]], align 4 +// CHECK-NEXT: [[ARGP_CUR3:%.*]] = load ptr, ptr [[B]], align 8 +// CHECK-NEXT: [[ARGP_NEXT4:%.*]] = getelementptr inbounds i8, ptr [[ARGP_CUR3]], i64 8 +// CHECK-NEXT: store ptr [[ARGP_NEXT4]], ptr [[B]], align 8 +// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[ARGP_CUR3]], align 8 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP2]], ptr align 4 [[TMP1]], i64 16, i1 false) +// CHECK-NEXT: [[X5:%.*]] = getelementptr inbounds [[STRUCT_B]], ptr [[TMP2]], i32 0, i32 0 +// CHECK-NEXT: [[ARRAYIDX6:%.*]] = getelementptr inbounds [4 x float], ptr [[X5]], i64 0, i64 0 +// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[ARRAYIDX6]], align 4 +// CHECK-NEXT: store float [[TMP2]], ptr [[Y]], align 4 +// CHECK-NEXT: ret void +// +// +// CHECK-LABEL: @"#g"( +// CHECK-NEXT: entry: +// CHECK-NEXT: [[A:%.*]] = alloca [[STRUCT_A:%.*]], align 4 +// CHECK-NEXT: [[B:%.*]] = alloca [[STRUCT_B:%.*]], align 4 +// CHECK-NEXT: [[BYVAL_TEMP:%.*]] = alloca [[STRUCT_B]], align 4 +// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 0 +// CHECK-NEXT: store [2 x float] [[A_COERCE:%.*]], ptr [[COERCE_DIVE]], align 4 +// CHECK-NEXT: [[COERCE_DIVE1:%.*]] = getelementptr inbounds [[STRUCT_B]], ptr [[B]], i32 0, i32 0 +// CHECK-NEXT: store [4 x float] [[B_COERCE:%.*]], ptr [[COERCE_DIVE1]], align 4 +// CHECK-NEXT: [[COERCE_DIVE2:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 0 +// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[COERCE_DIVE2]], align 4 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[BYVAL_TEMP]], ptr align 4 [[B]], i64 16, i1 false) +// CHECK-NEXT: call void (i64, ...) @f(i64 [[TMP0]], ptr noundef [[BYVAL_TEMP]]) +// CHECK-NEXT: ret void +// diff --git a/clang/test/CodeGenCXX/arm64ec.cpp b/clang/test/CodeGenCXX/arm64ec.cpp --- a/clang/test/CodeGenCXX/arm64ec.cpp +++ b/clang/test/CodeGenCXX/arm64ec.cpp @@ -2,6 +2,8 @@ // CHECK: @"?g@@YAXUA@@UB@@@Z" = alias void ([2 x float], [4 x float]), void ([2 x float], [4 x float])* @"?g@@$$hYAXUA@@UB@@@Z" // CHECK: define dso_local void @"?g@@$$hYAXUA@@UB@@@Z" +// CHECK: call void (i64, ...) @"?f@@YAXUA@@ZZ"(i64 %{{.*}}, %struct.B* noundef %{{.*}}) typedef struct { float x[2]; } A; typedef struct { float x[4]; } B; -void g(A a, B b) { } +void f(A a, ...); +void g(A a, B b) { f(a, b); } diff --git a/llvm/utils/UpdateTestChecks/common.py b/llvm/utils/UpdateTestChecks/common.py --- a/llvm/utils/UpdateTestChecks/common.py +++ b/llvm/utils/UpdateTestChecks/common.py @@ -327,7 +327,7 @@ UNUSED_NOTE = 'NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:' OPT_FUNCTION_RE = re.compile( - r'^(\s*;\s*Function\sAttrs:\s(?P[\w\s]+?))?\s*define\s+(?:internal\s+)?[^@]*@(?P[\w.$-]+?)\s*' + r'^(\s*;\s*Function\sAttrs:\s(?P[\w\s]+?))?\s*define\s+(?:internal\s+)?[^@]*@(?P[\w.$-]+?|".+?")\s*' r'(?P\((\)|(.*?[\w.-]+?)\))[^{]*\{)\n(?P.*?)^\}$', flags=(re.M | re.S))