https://github.com/lhmouse updated https://github.com/llvm/llvm-project/pull/237057
>From 1a64c1b926c67f9dbc9a82d2fb9e3ff19c75d97a Mon Sep 17 00:00:00 2001 From: LIU Hao <[email protected]> Date: Thu, 29 Oct 2026 13:18:23 +0800 Subject: [PATCH] [ARM64EC] Implement `-mlong-double-{64,80,128}` options This commit implements the three options for arm64ec-w64-mingw32. `-mlong-double-80` is made default to fix the interoperability bug with x86_64-w64-mingw32. This also introduces an ABI break with old ARM64EC binaries. `-mlong-double-64` is the old behavior that is compatible with aarch64-w64-mingw32 and MSVC. `-mlong-double-128` enables float128 in software, same as x86-64. Operations of 80-bit `long double` will be implemented in the ARM64EC backend. Its ABI, including its calling convention, is the same as a 128-bit structure: For ARM64 code it's passed and returned in an integer register pair; for x86-64 it's passed and returned in memory. The difference is handled in entry/exit thunks. Assisted-by: DeepSeek V4.1 Flash --- clang/lib/Basic/Targets/AArch64.cpp | 17 ++ clang/lib/CodeGen/Targets/AArch64.cpp | 14 ++ clang/lib/Driver/ToolChains/Clang.cpp | 3 + clang/test/CodeGen/arm64ec-long-double-abi.c | 126 +++++++++++ .../test/CodeGen/arm64ec-long-double-modes.c | 37 ++++ .../arm64ec-soft-x87-long-double-atomic.c | 27 +++ .../arm64ec-soft-x87-long-double-builtin.c | 64 ++++++ ...arm64ec-soft-x87-long-double-fixed-point.c | 38 ++++ .../arm64ec-soft-x87-long-double-matrix.c | 68 ++++++ ...64ec-soft-x87-long-double-vector-builtin.c | 24 +++ ...ec-soft-x87-long-double-vector-logical.cpp | 22 ++ .../arm64ec-soft-x87-long-double-vector.c | 63 ++++++ .../CodeGen/arm64ec-soft-x87-long-double.c | 195 ++++++++++++++++++ clang/test/Driver/arm64ec-long-double.c | 21 ++ 14 files changed, 719 insertions(+) create mode 100644 clang/test/CodeGen/arm64ec-long-double-abi.c create mode 100644 clang/test/CodeGen/arm64ec-long-double-modes.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-atomic.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-builtin.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-fixed-point.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-matrix.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-builtin.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-logical.cpp create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double-vector.c create mode 100644 clang/test/CodeGen/arm64ec-soft-x87-long-double.c create mode 100644 clang/test/Driver/arm64ec-long-double.c diff --git a/clang/lib/Basic/Targets/AArch64.cpp b/clang/lib/Basic/Targets/AArch64.cpp index 7ddf9d87ad163e..01da8b879df053 100644 --- a/clang/lib/Basic/Targets/AArch64.cpp +++ b/clang/lib/Basic/Targets/AArch64.cpp @@ -206,6 +206,10 @@ AArch64TargetInfo::AArch64TargetInfo(const llvm::Triple &Triple, this->MCountName = "\01_mcount"; else if (Triple.getOS() == llvm::Triple::UnknownOS) this->MCountName = Triple.isGNUEnvironment() ? "\01_mcount" : "mcount"; + + // This should be compatible with x86-64. + if (Triple.isWindowsArm64EC() && !Triple.isWindowsMSVCEnvironment()) + HasFloat128 = true; } StringRef AArch64TargetInfo::getABI() const { return ABI; } @@ -450,6 +454,11 @@ void AArch64TargetInfo::getTargetDefines(const LangOptions &Opts, if (T.isLFI()) Builder.defineMacro("__LFI__"); + if (HasFloat128) { + Builder.defineMacro("__FLOAT128__"); + Builder.defineMacro("__SIZEOF_FLOAT128__", "16"); + } + // Inline assembly supports AArch64 flag outputs. Builder.defineMacro("__GCC_ASM_FLAG_OUTPUTS__"); @@ -1831,6 +1840,14 @@ WindowsARM64TargetInfo::WindowsARM64TargetInfo(const llvm::Triple &Triple, SizeType = UnsignedLongLong; PtrDiffType = SignedLongLong; IntPtrType = SignedLongLong; + + if (Triple.isWindowsArm64EC() && !Triple.isWindowsMSVCEnvironment()) { + // arm64ec-w64-mingw32 should be interoperable with x86_64-w64-mingw32, + // which by default has `long double` as the x87-specific extended + // precision format. + LongDoubleWidth = LongDoubleAlign = SuitableAlign = 128; + LongDoubleFormat = &llvm::APFloat::x87DoubleExtended(); + } } TargetInfo::BuiltinVaListKind diff --git a/clang/lib/CodeGen/Targets/AArch64.cpp b/clang/lib/CodeGen/Targets/AArch64.cpp index e493f717af581d..1003a0fd7c845c 100644 --- a/clang/lib/CodeGen/Targets/AArch64.cpp +++ b/clang/lib/CodeGen/Targets/AArch64.cpp @@ -383,6 +383,13 @@ ABIArgInfo AArch64ABIInfo::classifyArgumentType(QualType Ty, bool IsVariadicFn, Ty, IsNamedArg); } + // ARM64EC supports the x87 `long double` type, which is passed as a 128-bit + // integer image. + if (getTarget().getTriple().isWindowsArm64EC() && + Ty->isSpecificBuiltinType(BuiltinType::LongDouble) && + &getTarget().getLongDoubleFormat() == &llvm::APFloat::x87DoubleExtended()) + return ABIArgInfo::getDirect(llvm::IntegerType::get(getVMContext(), 128)); + // Handle illegal vector types here. if (isIllegalVectorType(Ty)) return coerceIllegalVector(Ty, NSRN, NPRN); @@ -557,6 +564,13 @@ ABIArgInfo AArch64ABIInfo::classifyReturnType(QualType RetTy, if (RetTy->isVoidType()) return ABIArgInfo::getIgnore(); + // ARM64EC supports the x87 `long double` type, which is returned as a + // 128-bit integer image. + if (getTarget().getTriple().isWindowsArm64EC() && + RetTy->isSpecificBuiltinType(BuiltinType::LongDouble) && + &getTarget().getLongDoubleFormat() == &llvm::APFloat::x87DoubleExtended()) + return ABIArgInfo::getDirect(llvm::IntegerType::get(getVMContext(), 128)); + if (const auto *VT = RetTy->getAs<VectorType>()) { if (VT->getVectorKind() == VectorKind::SveFixedLengthData || VT->getVectorKind() == VectorKind::SveFixedLengthPredicate) { diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 1bce61d2cfbeaa..b97e1faf3635b1 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6347,6 +6347,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, else if (TC.getTriple().isPPC() && (A->getOption().getID() != options::OPT_mlong_double_80)) A->render(Args, CmdArgs); + else if (TC.getTriple().isWindowsArm64EC() && + !TC.getTriple().isWindowsMSVCEnvironment()) + A->render(Args, CmdArgs); else D.Diag(diag::err_drv_unsupported_opt_for_target) << A->getAsString(Args) << TripleStr; diff --git a/clang/test/CodeGen/arm64ec-long-double-abi.c b/clang/test/CodeGen/arm64ec-long-double-abi.c new file mode 100644 index 00000000000000..18468b159fba27 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-long-double-abi.c @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -mlong-double-64 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM64EC_LD64 +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -mlong-double-80 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM64EC_LD80 +// RUN: %clang_cc1 -triple x86_64-w64-windows-gnu -mlong-double-64 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=X64_LD64 +// RUN: %clang_cc1 -triple x86_64-w64-windows-gnu -mlong-double-80 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=X64_LD80 + +// The calling convention of a `long double` argument depends on the format +// that `-mlong-double-*` selects, and on the machine. On ARM64EC, named +// parameters and return values follow the ARM64 convention: a `double` +// occupies a single floating-point register, and the 80-bit image occupies a +// pair of integer registers. On x86_64 they follow the x64 convention: a +// `double` occupies an XMM register, while the 80-bit image does not fit in +// a register and goes through memory as a reference to a copy and a hidden +// return pointer. A call across the ARM64EC boundary is translated by an +// entry or an exit thunk, and the two halves below are what such a thunk +// has to translate between. +// +// Variadic arguments follow the x86_64 convention on both machines, where +// anything that is not 1, 2, 4 or 8 bytes is passed by reference: a `double` +// is read back from an 8-byte slot, and the 16-byte image from a reference +// that is 16-byte aligned. +// +// Neither format ever sees the other format's representation. +// ARM64EC_LD64-NOT: alloca i128 +// ARM64EC_LD80-NOT: alloca double +// X64_LD64-NOT: alloca x86_fp80 +// X64_LD80-NOT: alloca double + +typedef __builtin_va_list va_list; + +// A named parameter is received in a floating-point register as a `double`, +// or as the 16-byte image in a pair of integer registers; an x86_64 callee +// receives the `double` in an XMM register, and the image through a +// reference. +// ARM64EC_LD64-LABEL: define {{.*}}double @named_receive(double noundef %x) +// ARM64EC_LD80-LABEL: define {{.*}}i128 @named_receive(i128 {{.*}}%x.coerce) +// X64_LD64-LABEL: define {{.*}}double @named_receive(double noundef %x) +// X64_LD80-LABEL: define {{.*}}void @named_receive(ptr dead_on_unwind noalias writable sret(x86_fp80) align 16 %agg.result, ptr {{.*}}align 16{{.*}}%0) +long double named_receive(long double x) { return x; } + +// A caller passes a named parameter and receives the return value the same +// way: through a floating-point register, an integer register pair, an XMM +// register, or a reference to a copy of the argument together with a hidden +// return pointer. +// ARM64EC_LD64-LABEL: define {{.*}}double @named_call(double noundef %x) +// ARM64EC_LD64: call double @named_receive(double noundef +// ARM64EC_LD64: ret double +// ARM64EC_LD80-LABEL: define {{.*}}i128 @named_call(i128 {{.*}}%x.coerce) +// ARM64EC_LD80: call i128 @named_receive(i128 +// ARM64EC_LD80: ret i128 +// X64_LD64-LABEL: define {{.*}}double @named_call(double noundef %x) +// X64_LD64: call double @named_receive(double noundef +// X64_LD64: ret double +// X64_LD80-LABEL: define {{.*}}void @named_call(ptr dead_on_unwind noalias writable sret(x86_fp80) align 16 %agg.result, ptr +// X64_LD80: alloca x86_fp80, align 16 +// X64_LD80: store x86_fp80 {{.*}}, ptr {{.*}}, align 16 +// X64_LD80: call void @named_receive(ptr dead_on_unwind writable sret(x86_fp80) align 16 {{%.*}}, ptr {{.*}}align 16{{.*}}) +long double named_call(long double x) { return named_receive(x); } + +int varfmt(const char *fmt, ...); + +// A variadic `double` is passed directly, and `va_arg` reads it back from the +// spill slot with 8-byte alignment ... +// ARM64EC_LD64-LABEL: define {{.*}}i32 @vararg_pass(double noundef %x) +// ARM64EC_LD64-NOT: i128 +// ARM64EC_LD64: call i32 (ptr, ...) @varfmt(ptr noundef @.str, double noundef +// ARM64EC_LD64-LABEL: define {{.*}}double @vararg_take(ptr noundef %ap) +// ARM64EC_LD64-NOT: i128 +// ARM64EC_LD64: load double, ptr %argp.cur, align 8 +// X64_LD64-LABEL: define {{.*}}i32 @vararg_pass(double noundef %x) +// X64_LD64-NOT: x86_fp80 +// X64_LD64: call i32 (ptr, ...) @varfmt(ptr noundef @.str, double noundef +// X64_LD64-LABEL: define {{.*}}double @vararg_take(ptr noundef %ap) +// X64_LD64-NOT: x86_fp80 +// X64_LD64: load double, ptr %argp.cur, align 8 +// ... while the 16-byte image is passed by reference to a 16-byte aligned +// temporary, through which `va_arg` loads it. +// ARM64EC_LD80-LABEL: define {{.*}}i32 @vararg_pass(i128 {{.*}}%x.coerce) +// ARM64EC_LD80-NOT: double +// ARM64EC_LD80: alloca x86_fp80, align 16 +// ARM64EC_LD80: store x86_fp80 {{.*}}, ptr {{.*}}, align 16 +// ARM64EC_LD80: call i32 (ptr, ...) @varfmt(ptr noundef @.str, ptr {{.*}}align 16{{.*}}) +// ARM64EC_LD80-LABEL: define {{.*}}i128 @vararg_take(ptr noundef %ap) +// ARM64EC_LD80-NOT: double +// ARM64EC_LD80: load ptr, ptr %argp.cur, align 8 +// ARM64EC_LD80: load x86_fp80, ptr {{.*}}, align 16 +// ARM64EC_LD80: load i128, ptr %retval, align 16 +// X64_LD80-LABEL: define {{.*}}i32 @vararg_pass(ptr {{.*}}align 16{{.*}}) +// X64_LD80-NOT: alloca double +// X64_LD80: alloca x86_fp80, align 16 +// X64_LD80: store x86_fp80 {{.*}}, ptr {{.*}}, align 16 +// X64_LD80: call i32 (ptr, ...) @varfmt(ptr noundef @.str, ptr {{.*}}align 16{{.*}}) +// X64_LD80-LABEL: define {{.*}}void @vararg_take(ptr dead_on_unwind noalias writable sret(x86_fp80) align 16 %agg.result, ptr noundef %ap) +// X64_LD80-NOT: alloca double +// X64_LD80: load ptr, ptr %argp.cur, align 8 +// X64_LD80: load x86_fp80, ptr {{.*}}, align 16 +int vararg_pass(long double x) { return varfmt("%Lg", x); } +long double vararg_take(va_list ap) { + return __builtin_va_arg(ap, long double); +} + +// A return value leaves and enters in a floating-point register, or in a +// pair of integer registers; an x86_64 callee returns the 80-bit image +// through a hidden pointer. +// ARM64EC_LD64-LABEL: define {{.*}}double @ret_out() +// ARM64EC_LD64: ret double 1.000000e+00 +// ARM64EC_LD64-LABEL: define {{.*}}double @ret_in() +// ARM64EC_LD64: call double @ret_out() +// ARM64EC_LD80-LABEL: define {{.*}}i128 @ret_out() +// ARM64EC_LD80: store x86_fp80 1.000000e+00, ptr %retval, align 16 +// ARM64EC_LD80: load i128, ptr %retval, align 16 +// ARM64EC_LD80-LABEL: define {{.*}}i128 @ret_in() +// ARM64EC_LD80: call i128 @ret_out() +// X64_LD64-LABEL: define {{.*}}double @ret_out() +// X64_LD64: ret double 1.000000e+00 +// X64_LD64-LABEL: define {{.*}}double @ret_in() +// X64_LD64: call double @ret_out() +// X64_LD80-LABEL: define {{.*}}void @ret_out(ptr dead_on_unwind noalias writable sret(x86_fp80) align 16 %agg.result) +// X64_LD80: store x86_fp80 1.000000e+00, ptr %agg.result, align 16 +// X64_LD80-LABEL: define {{.*}}void @ret_in(ptr dead_on_unwind noalias writable sret(x86_fp80) align 16 %agg.result) +// X64_LD80: call void @ret_out(ptr dead_on_unwind writable sret(x86_fp80) align 16 +long double ret_out(void) { return 1.0L; } +long double ret_in(void) { return ret_out(); } diff --git a/clang/test/CodeGen/arm64ec-long-double-modes.c b/clang/test/CodeGen/arm64ec-long-double-modes.c new file mode 100644 index 00000000000000..fae0446a69b35d --- /dev/null +++ b/clang/test/CodeGen/arm64ec-long-double-modes.c @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -mlong-double-64 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=LD64 +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -mlong-double-80 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=LD80 +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -mlong-double-128 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=LD128 + +// The three widths select the format of `long double`; only the 80-bit one is +// the software x87 format that goes through the ARM64EC runtime helpers. + +long double g = 1.0L; +long double add(long double a, long double b) { return a + b; } +int cmp(long double a, long double b) { return a < b; } + +// LD64: @g = {{.*}}global double 1.000000e+00 +// LD64-LABEL: define {{.*}}double @add(double noundef %a, double noundef %b) +// LD64: fadd double +// LD64-LABEL: define {{.*}}i32 @cmp(double noundef %a, double noundef %b) +// LD64: fcmp olt double +// LD64-NOT: __arm64ec_ + +// The 80-bit format stays a real `x86_fp80` in the IR so that the middle-end +// can optimize it; the ABI passes and returns it as a 16-byte image, and the +// backend softens every operation to a compiler-rt helper. The padding after +// the 80 bits of the slot is not represented by the IR type. +// LD80: @g = {{.*}}global x86_fp80 1.000000e+00, align 16 +// LD80-LABEL: define {{.*}}i128 @add(i128 {{.*}}%a.coerce, i128 {{.*}}%b.coerce) +// LD80: fadd x86_fp80 +// LD80-NOT: __arm64ec_ +// LD80-LABEL: define {{.*}}i32 @cmp(i128 {{.*}}%a.coerce, i128 {{.*}}%b.coerce) +// LD80: fcmp olt x86_fp80 +// LD80-NOT: __arm64ec_ + +// LD128: @g = {{.*}}global fp128 +// LD128-LABEL: define {{.*}}fp128 @add(fp128 noundef %a, fp128 noundef %b) +// LD128-LABEL: define {{.*}}i32 @cmp(fp128 noundef %a, fp128 noundef %b) +// LD128-NOT: __arm64ec_ diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-atomic.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double-atomic.c new file mode 100644 index 00000000000000..1a699adbbdf451 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-atomic.c @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s | FileCheck %s + +// The ABI moves a `long double` as a 16-byte integer image, but the value +// itself is a real `x86_fp80`; the read-modify-write therefore loads and +// stores the image and performs the increment as a floating-point operation. +_Atomic long double g; + +long double pre_inc(void) { return ++g; } +// CHECK-LABEL: define {{.*}} @pre_inc( +// CHECK-NOT: atomicrmw +// CHECK: load atomic i128, ptr @g seq_cst +// CHECK: [[TMP:%.*]] = load x86_fp80, ptr %atomic-temp +// CHECK: [[OLD:%.*]] = phi x86_fp80 [ [[TMP]], %entry ] +// CHECK: fadd x86_fp80 [[OLD]], 1.000000e+00 +// CHECK: cmpxchg ptr @g, i128 {{.*}}, i128 {{.*}} seq_cst seq_cst, align 16 + +long double post_dec(void) { return g--; } +// CHECK-LABEL: define {{.*}} @post_dec( +// CHECK-NOT: atomicrmw +// CHECK: load atomic i128, ptr @g seq_cst +// CHECK: fadd x86_fp80 {{.*}}, -1.000000e+00 +// CHECK: cmpxchg ptr @g, i128 {{.*}}, i128 {{.*}} seq_cst seq_cst, align 16 + +void add_assign(void) { g += 1.0L; } +// CHECK-LABEL: define {{.*}} @add_assign( +// CHECK-NOT: atomicrmw +// CHECK: cmpxchg ptr @g, i128 {{.*}}, i128 {{.*}} seq_cst seq_cst, align 16 diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-builtin.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double-builtin.c new file mode 100644 index 00000000000000..102f9afb0b23be --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-builtin.c @@ -0,0 +1,64 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s | FileCheck %s + +// The value itself is a real `x86_fp80`, so every builtin that takes or +// returns a floating-point value is emitted as the ordinary floating-point +// intrinsic and softened by the backend; the frontend does not lower the +// operation on the integer image. + +// `__builtin_elementwise_abs` is `fabsl`. +// CHECK-LABEL: define dso_local i128 @elementwise_abs( +// CHECK: call x86_fp80 @llvm.fabs.f80 +long double elementwise_abs(long double x) { return __builtin_elementwise_abs(x); } + +// `__builtin_elementwise_copysign` is `copysignl`. +// CHECK-LABEL: define dso_local i128 @elementwise_copysign( +// CHECK: call x86_fp80 @llvm.copysign.f80 +long double elementwise_copysign(long double x, long double y) { + return __builtin_elementwise_copysign(x, y); +} + +// `__builtin_canonicalizel` is a real floating-point canonicalization. +// CHECK-LABEL: define dso_local i128 @canonical( +// CHECK: call x86_fp80 @llvm.canonicalize.f80 +long double canonical(long double x) { return __builtin_canonicalizel(x); } + +// The constants stay `x86_fp80` constants and are widened to the 16-byte +// image by the backend, where the sign and the payload are preserved. +// CHECK-LABEL: define dso_local i128 @canonical_signalling( +// CHECK: call x86_fp80 @llvm.canonicalize.f80(x86_fp80 +snan( +long double canonical_signalling(void) { + return __builtin_canonicalizel(__builtin_nansl("")); +} + +// CHECK-LABEL: define dso_local i128 @canonical_negative_signalling( +// CHECK: call x86_fp80 @llvm.canonicalize.f80(x86_fp80 -snan( +long double canonical_negative_signalling(void) { + return __builtin_canonicalizel(-__builtin_nansl("")); +} + +// CHECK-LABEL: define dso_local i128 @canonical_quiet( +// CHECK: call x86_fp80 @llvm.canonicalize.f80(x86_fp80 +qnan) +long double canonical_quiet(void) { + return __builtin_canonicalizel(__builtin_nanl("")); +} + +// CHECK-LABEL: define dso_local i128 @canonical_infinity( +// CHECK: call x86_fp80 @llvm.canonicalize.f80(x86_fp80 +inf) +long double canonical_infinity(void) { + return __builtin_canonicalizel(__builtin_infl()); +} + +// `__builtin_iszero`, `__builtin_issubnormal` and `__builtin_issignaling` +// classify the floating-point value with the generic FP-class intrinsic; the +// masks are the ones defined by the LangRef. +// CHECK-LABEL: define dso_local i32 @is_zero( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 96) +int is_zero(long double x) { return __builtin_iszero(x); } + +// CHECK-LABEL: define dso_local i32 @is_subnormal( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 144) +int is_subnormal(long double x) { return __builtin_issubnormal(x); } + +// CHECK-LABEL: define dso_local i32 @is_signaling( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 1) +int is_signaling(long double x) { return __builtin_issignaling(x); } diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-fixed-point.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double-fixed-point.c new file mode 100644 index 00000000000000..75d0a1e40e67bd --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-fixed-point.c @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -ffixed-point -emit-llvm -o - %s | FileCheck %s + +// A conversion to `bool` tests the `x86_fp80` value directly against zero, +// so a magnitude below the smallest `double` is still nonzero. +// CHECK-LABEL: define dso_local i32 @to_bool( +// CHECK-NOT: x87ld.to.double +// CHECK: fcmp une x86_fp80 {{%.*}}, 0.000000e+00 +int to_bool(long double x) { return x ? 1 : 0; } + +// CHECK-LABEL: define dso_local i32 @not_ld( +// CHECK-NOT: x87ld.to.double +// CHECK: fcmp une x86_fp80 {{%.*}}, 0.000000e+00 +int not_ld(long double x) { return !x; } + +// A conversion between `long double` and a fixed-point type is an ordinary +// floating-point conversion, which the backend softens. +// CHECK-LABEL: define dso_local i128 @from_accum( +// CHECK: sitofp i16 {{%.*}} to x86_fp80 +long double from_accum(short _Accum a) { return a; } + +// CHECK-LABEL: define dso_local i16 @to_accum( +// CHECK: fptosi x86_fp80 {{%.*}} to i16 +short _Accum to_accum(long double x) { return x; } + +// CHECK-LABEL: define dso_local i128 @from_fract( +// CHECK: sitofp i8 {{%.*}} to x86_fp80 +long double from_fract(short _Fract a) { return a; } + +// CHECK-LABEL: define dso_local i8 @to_fract( +// CHECK: fptosi x86_fp80 {{%.*}} to i8 +short _Fract to_fract(long double x) { return x; } + +// A fixed-point to `bool` conversion does not arrive here at all, and it must +// not be routed through `double` either. +// CHECK-LABEL: define dso_local i32 @accum_to_bool( +// CHECK-NOT: x87ld +// CHECK: icmp ne i16 {{%.*}}, 0 +int accum_to_bool(short _Accum a) { return a ? 1 : 0; } diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-matrix.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double-matrix.c new file mode 100644 index 00000000000000..94de49438e15b2 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-matrix.c @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -fenable-matrix -emit-llvm -O0 -o - %s | FileCheck %s + +// A matrix is lowered to a flat vector of the element type, so a matrix of +// `long double` is a vector of real `x86_fp80` values; the backend splits the +// vector and softens every lane. +typedef long double m2x2 __attribute__((matrix_type(2, 2))); +typedef long double m2x3 __attribute__((matrix_type(2, 3))); +typedef long double m3x2 __attribute__((matrix_type(3, 2))); +typedef float f2x3 __attribute__((matrix_type(2, 3))); + +// Whole vectors move around as they are. +m2x3 copy(m2x3 a) { return a; } +// CHECK-LABEL: define {{.*}} @copy( +// CHECK-NOT: call +// CHECK: ret <6 x x86_fp80> + +m2x3 add(m2x3 a, m2x3 b) { return a + b; } +// CHECK-LABEL: define {{.*}} @add( +// CHECK: fadd <6 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +m2x3 sub(m2x3 a, m2x3 b) { return a - b; } +// CHECK-LABEL: define {{.*}} @sub( +// CHECK: fsub <6 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +// A scalar operand of a matrix is splatted into every lane. +m2x3 mul_scalar(m2x3 a, long double s) { return a * s; } +// CHECK-LABEL: define {{.*}} @mul_scalar( +// CHECK: fmul <6 x x86_fp80> {{.*}}, %scalar.splat.splat +// CHECK-NOT: __arm64ec_ + +m2x3 scalar_mul(long double s, m2x3 a) { return s * a; } +// CHECK-LABEL: define {{.*}} @scalar_mul( +// CHECK: fmul <6 x x86_fp80> %scalar.splat.splat, {{.*}} +// CHECK-NOT: __arm64ec_ + +m2x3 div_scalar(m2x3 a, long double s) { return a / s; } +// CHECK-LABEL: define {{.*}} @div_scalar( +// CHECK: fdiv <6 x x86_fp80> {{.*}}, %scalar.splat.splat +// CHECK-NOT: __arm64ec_ + +// Matrix multiplication stays a vector-of-f80 operation. +m2x2 mul_matrix(m2x3 a, m3x2 b) { return a * b; } +// CHECK-LABEL: define {{.*}} @mul_matrix( +// CHECK: %[[P:.*]] = call <4 x x86_fp80> @llvm.matrix.multiply.v4f80.v6f80.v6f80(<6 x x86_fp80> {{.*}}, <6 x x86_fp80> {{.*}}, i32 2, i32 3, i32 2) +// CHECK: ret <4 x x86_fp80> %[[P]] + +void mul_assign(m2x2 *a, m2x2 b) { *a *= b; } +// CHECK-LABEL: define {{.*}} @mul_assign( +// CHECK: call <4 x x86_fp80> @llvm.matrix.multiply.v4f80.v4f80.v4f80(<4 x x86_fp80> {{.*}}, <4 x x86_fp80> {{.*}}, i32 2, i32 2, i32 2) +// CHECK: store <4 x x86_fp80> + +// Conversions stay vector conversions. +f2x3 truncate(m2x3 a) { return (f2x3)a; } +// CHECK-LABEL: define {{.*}} @truncate( +// CHECK: fptrunc <6 x x86_fp80> {{.*}} to <6 x float> + +m2x3 extend(f2x3 a) { return (m2x3)a; } +// CHECK-LABEL: define {{.*}} @extend( +// CHECK: fpext <6 x float> {{.*}} to <6 x x86_fp80> + +// Transposing only moves whole values around. +m3x2 transpose(m2x3 a) { return __builtin_matrix_transpose(a); } +// CHECK-LABEL: define {{.*}} @transpose( +// CHECK-NOT: call double +// CHECK: %[[T:.*]] = call <6 x x86_fp80> @llvm.matrix.transpose.v6f80(<6 x x86_fp80> +// CHECK: ret <6 x x86_fp80> %[[T]] diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-builtin.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-builtin.c new file mode 100644 index 00000000000000..52a2870186e7b9 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-builtin.c @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s -DOPTNONE | FileCheck %s --check-prefix=OPTNONE + +// Element-wise floating-point builtins work on the vector values themselves, +// which stay real `<2 x x86_fp80>` in the IR; the backend splits and softens +// the lanes. +typedef long double V __attribute__((vector_size(32))); + +#ifdef OPTNONE +// `optnone` does not change the representation. +__attribute__((optnone)) V reject(V a) { + return __builtin_elementwise_abs(a); +} +// OPTNONE-LABEL: define {{.*}} @reject( +// OPTNONE: call <2 x x86_fp80> @llvm.fabs.v2f80 +#else +V reject(V a, V b, V c) { + V abs_a = __builtin_elementwise_abs(a); + return __builtin_elementwise_fma(abs_a, b, c); +} +// CHECK-LABEL: define {{.*}} @reject( +// CHECK: call <2 x x86_fp80> @llvm.fabs.v2f80 +// CHECK: call <2 x x86_fp80> @llvm.fma.v2f80 +#endif diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-logical.cpp b/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-logical.cpp new file mode 100644 index 00000000000000..fae184e95ee023 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector-logical.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s | FileCheck %s + +// C++ is the only language that accepts the logical operators on vector +// operands. A lane is true when its value is not zero, which is an ordinary +// floating-point comparison; `-0.0` compares equal to zero and a NaN is true. +typedef long double V __attribute__((vector_size(32))); + +extern "C" int not_(V a) { return (!a)[0]; } +// CHECK-LABEL: define {{.*}} @not_( +// CHECK: fcmp oeq <2 x x86_fp80> {{.*}}, zeroinitializer + +extern "C" int land_(V a, V b) { return (a && b)[0]; } +// CHECK-LABEL: define {{.*}} @land_( +// CHECK: fcmp une <2 x x86_fp80> {{.*}}, zeroinitializer +// CHECK: fcmp une <2 x x86_fp80> {{.*}}, zeroinitializer +// CHECK: and <2 x i1> + +extern "C" int lor_(V a, V b) { return (a || b)[0]; } +// CHECK-LABEL: define {{.*}} @lor_( +// CHECK: fcmp une <2 x x86_fp80> {{.*}}, zeroinitializer +// CHECK: fcmp une <2 x x86_fp80> {{.*}}, zeroinitializer +// CHECK: or <2 x i1> diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector.c new file mode 100644 index 00000000000000..3aab8433dd2d3e --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double-vector.c @@ -0,0 +1,63 @@ +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s | FileCheck %s + +// The elements of a `long double` vector are real `x86_fp80` values. The IR +// keeps the vector operations intact; the AArch64 backend splits the vector +// and softens every lane to a compiler-rt helper. +typedef long double V __attribute__((vector_size(32))); +typedef double D2 __attribute__((vector_size(16))); + +// Whole vectors move around as they are. +V copy(V a) { return a; } +// CHECK-LABEL: define {{.*}} @copy( +// CHECK-NOT: call +// CHECK: ret void + +V add(V a, V b) { return a + b; } +// CHECK-LABEL: define {{.*}} @add( +// CHECK: fadd <2 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +V div(V a, V b) { return a / b; } +// CHECK-LABEL: define {{.*}} @div( +// CHECK: fdiv <2 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +// Negation stays a floating-point operation on the vector. +V neg(V a) { return -a; } +// CHECK-LABEL: define {{.*}} @neg( +// CHECK: fneg <2 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +// A compound assignment takes the same path. +V compound(V a, V b) { + a += b; + return a; +} +// CHECK-LABEL: define {{.*}} @compound( +// CHECK: fadd <2 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +// A scalar operand is splatted at the IR level. +V splat(V a) { return a + 1.0L; } +// CHECK-LABEL: define {{.*}} @splat( +// CHECK: fadd <2 x x86_fp80> {{.*}}, splat (x86_fp80 1.000000e+00) +// CHECK-NOT: __arm64ec_ + +// Comparison stays a vector compare. +int lt(V a, V b) { return (a < b)[0]; } +// CHECK-LABEL: define {{.*}} @lt( +// CHECK: fcmp olt <2 x x86_fp80> +// CHECK-NOT: __arm64ec_ + +// Conversions stay vector conversions. +D2 convert(V a) { return __builtin_convertvector(a, D2); } +// CHECK-LABEL: define {{.*}} @convert( +// CHECK: fptrunc <2 x x86_fp80> {{.*}} to <2 x double> +// CHECK-NOT: __arm64ec_ + +// The reverse conversion goes the other way. +V back(D2 a) { return __builtin_convertvector(a, V); } +// CHECK-LABEL: define {{.*}} @back( +// CHECK: fpext <2 x double> {{.*}} to <2 x x86_fp80> +// CHECK-NOT: __arm64ec_ + diff --git a/clang/test/CodeGen/arm64ec-soft-x87-long-double.c b/clang/test/CodeGen/arm64ec-soft-x87-long-double.c new file mode 100644 index 00000000000000..6e4dc8f05976e8 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-soft-x87-long-double.c @@ -0,0 +1,195 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals --include-generated-funcs --global-value-regex "g" +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64ec-w64-windows-gnu -O2 -emit-llvm -o - %s | FileCheck %s --check-prefix=OPT + +// ARM64EC keeps the x86_64 80-bit x87 format, in a 16-byte slot, but has no +// x87 hardware: the value is an integer image that only ever goes through +// `double` for computation. +_Static_assert(sizeof(long double) == 16, "the slot is 16 bytes"); +_Static_assert(_Alignof(long double) == 16, "the slot is 16-byte aligned"); +_Static_assert(__LDBL_MANT_DIG__ == 64, "80-bit x87 significand"); +_Static_assert(__LDBL_MAX_EXP__ == 16384, "80-bit x87 exponent range"); + +long double g = 1.0L; +// The tag of a NaN fills the 63 significand bits below the explicit integer +// bit, so the same expression has to give the same image whether it is folded +// into a global initializer or emitted in a function body. +long double g_nan = __builtin_nanl("0x5B3C9A7E1D46F2CD"); + +long double add(long double a, long double b) { return a + b; } +long double neg(long double a) { return -a; } + +int cmp(long double a, long double b) { return a < b; } +int cmp_eq(long double a, long double b) { return a == b; } + +int isinf_(long double a) { return __builtin_isinf(a); } +int isnan_(long double a) { return __builtin_isnan(a); } +int isfinite_(long double a) { return __builtin_isfinite(a); } +int signbit_(long double a) { return __builtin_signbitl(a); } +int fpclass(long double a) { + return __builtin_fpclassify(1, 2, 4, 8, 16, a); +} +long double ilogb_(long double a) { return (long double)__builtin_ilogbl(a); } +long double powi_(long double a, int n) { return __builtin_powil(a, n); } +int pred(long double a, long double b) { + return __builtin_isgreater(a, b) || __builtin_islessequal(a, b) || + __builtin_isunordered(a, b); +} +int fpclassify_mask(long double a) { return __builtin_isfpclass(a, 0x200); } + +long double nanl(const char *); +long double nan_q(void) { return __builtin_nanl("0x5B3C9A7E1D46F2CD"); } +long double nan_s(void) { return __builtin_nansl("0x5B3C9A7E1D46F2CD"); } +long double nan_rt(const char *tag) { return nanl(tag); } + +long double _Complex conj_(long double _Complex z) { return ~z; } +long double _Complex conj_b(long double _Complex z) { + return __builtin_conjl(z); +} +long double creal_b(long double _Complex z) { return __builtin_creall(z); } +long double cimag_b(long double _Complex z) { return __builtin_cimagl(z); } +long double cabs_b(long double _Complex z) { return __builtin_cabsl(z); } +long double _Complex cadd(long double _Complex a, long double _Complex b) { + return a + b; +} +long double _Complex cinc(long double _Complex z) { return ++z; } + +int varargs(const char *fmt, ...); +int callv(void) { + // A 16-byte `long double` is passed by reference, exactly as on x86_64. + return varargs("%Lg", 1.0L); +} + +// The 80-bit value remains a real `x86_fp80` in the IR, in a 16-byte slot; +// the ABI around it is the 16-byte integer image. +// CHECK: @g = {{.*}}global x86_fp80 1.000000e+00, align 16 +// CHECK: @g_nan = {{.*}}global x86_fp80 +nan(0x1B3C9A7E1D46F2CD), align 16 + +// Arithmetic stays a real floating-point operation, which the AArch64 backend +// softens to a compiler-rt helper. Nothing is lowered in the frontend. +// CHECK-LABEL: define {{.*}}i128 @add(i128 {{.*}}%a.coerce, i128 {{.*}}%b.coerce) +// CHECK: fadd x86_fp80 +// CHECK-NOT: __arm64ec_ + +// Negation is a single floating-point operation too. +// CHECK-LABEL: define {{.*}}i128 @neg(i128 {{.*}}%a.coerce) +// CHECK: fneg x86_fp80 +// CHECK-NOT: __arm64ec_ + +// Comparisons stay `fcmp` on the value. +// CHECK-LABEL: define {{.*}}i32 @cmp(i128 {{.*}}%a.coerce, i128 {{.*}}%b.coerce) +// CHECK: fcmp olt x86_fp80 +// CHECK-LABEL: define {{.*}}i32 @cmp_eq( +// CHECK: fcmp oeq x86_fp80 + +// Classification builtins are the generic floating-point intrinsic; the +// backend lowers them without going through the frontend. +// CHECK-LABEL: define {{.*}}i32 @isinf_( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 516) +// CHECK-LABEL: define {{.*}}i32 @isnan_( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 3) +// CHECK-LABEL: define {{.*}}i32 @isfinite_( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 504) + +// The sign is taken from the 80-bit value directly, not through a CRT call. +// CHECK-LABEL: define {{.*}}i32 @signbit_( +// CHECK: [[BITS:%.*]] = bitcast x86_fp80 {{.*}} to i80 +// CHECK: icmp slt i80 [[BITS]], 0 +// CHECK-NOT: call i32 @signbit + +// CHECK-LABEL: define {{.*}}i32 @fpclass( +// CHECK: fcmp oeq x86_fp80 +// CHECK: fcmp uno x86_fp80 +// CHECK: call x86_fp80 @llvm.fabs.f80 +// CHECK: fcmp oeq x86_fp80 +// CHECK: fcmp uge x86_fp80 +// CHECK-NOT: __arm64ec_ + +// `ilogbl` takes and returns the 16-byte image at its ABI boundary. +// CHECK-LABEL: define {{.*}}i128 @ilogb_( +// CHECK: call i32 @ilogbl(i128 +// CHECK-NOT: call i32 @ilogb( + +// `__builtin_powil` is a real `llvm.powi.f80`, softened by the backend. +// CHECK-LABEL: define {{.*}}i128 @powi_( +// CHECK: call x86_fp80 @llvm.powi.f80.i32 +// CHECK-NOT: __arm64ec_ + +// The comparison predicates are `fcmp` on the value. +// CHECK-LABEL: define {{.*}}i32 @pred( +// CHECK: fcmp ogt x86_fp80 +// CHECK: fcmp ole x86_fp80 +// CHECK: fcmp uno x86_fp80 +// CHECK-NOT: call i32 @isgreater +// CHECK-NOT: call i32 @islessequal +// CHECK-NOT: call i32 @isunordered +// CHECK: ret i32 + +// CHECK-LABEL: define {{.*}}i32 @fpclassify_mask( +// CHECK: call i1 @llvm.is.fpclass.f80({{.*}}i32 512) +// CHECK-NOT: call i32 @isfpclass + +// A string literal tag is folded into an `x86_fp80` constant, which the +// backend widens to the 16-byte image. +// CHECK-LABEL: define {{.*}}i128 @nan_q( +// CHECK: store x86_fp80 +nan(0x1B3C9A7E1D46F2CD) +// CHECK: load i128 + +// The signalling NaN keeps the payload but has the quiet bit clear. +// CHECK-LABEL: define {{.*}}i128 @nan_s( +// CHECK: store x86_fp80 +snan(0x1B3C9A7E1D46F2CD) +// CHECK: load i128 + +// A tag that is not a string literal is parsed by the library at run time, +// rather than replaced with a default payload. +// CHECK-LABEL: define {{.*}}i128 @nan_rt( +// CHECK: call i128 @nanl( + +// Conjugation only negates the imaginary component. +// CHECK-LABEL: define {{.*}}@conj_( +// CHECK: fneg x86_fp80 {{.*}}%z.imag +// CHECK-NOT: __arm64ec_ + +// The `conjl` builtin is the same operation, not a call to a CRT entry point. +// CHECK-LABEL: define {{.*}}@conj_b( +// CHECK: fneg x86_fp80 {{.*}}%z.imag +// CHECK-NOT: call {{.*}}@conjl + +// `creall` and `cimagl` only pick a component of the pair. +// CHECK-LABEL: define {{.*}}i128 @creal_b( +// CHECK-NOT: call {{.*}}@creall +// CHECK: load i128 + +// CHECK-LABEL: define {{.*}}i128 @cimag_b( +// CHECK-NOT: call {{.*}}@cimagl +// CHECK: load i128 + +// A complex magnitude is real mathematics, so it stays a library call. +// CHECK-LABEL: define {{.*}}i128 @cabs_b( +// CHECK: call i128 @cabsl( + +// CHECK-LABEL: define {{.*}}@cadd( +// CHECK: fadd x86_fp80 +// CHECK: fadd x86_fp80 +// CHECK-NOT: __arm64ec_ + +// `++z` increments the value, not the integer image. +// CHECK-LABEL: define {{.*}}@cinc( +// CHECK: fadd x86_fp80 {{.*}}, 1.000000e+00 +// CHECK-NOT: __arm64ec_ + +// A variadic `long double` is passed by reference to a 16-byte-aligned +// temporary, exactly as on x86-64. +// CHECK-LABEL: define {{.*}}i32 @callv( +// CHECK: [[TMP:%.*]] = alloca x86_fp80, align 16 +// CHECK: store x86_fp80 1.000000e+00, ptr [[TMP]], align 16 +// CHECK: call i32 (ptr, ...) @varargs(ptr {{.*}}, ptr {{.*}}align 16{{.*}}[[TMP]]) + +// The optimized pipeline keeps `fneg` as a floating-point operation as well. +// OPT-LABEL: define {{.*}}i128 @neg(i128 {{.*}}%a.coerce) +// OPT: [[BITS:%.*]] = trunc i128 %a.coerce to i80 +// OPT: [[VAL:%.*]] = bitcast i80 [[BITS]] to x86_fp80 +// OPT: [[NEG:%.*]] = fneg x86_fp80 [[VAL]] +// OPT: [[NEGBITS:%.*]] = bitcast x86_fp80 [[NEG]] to i80 +// OPT: [[EXT:%.*]] = zext i80 [[NEGBITS]] to i128 +// OPT: ret i128 [[EXT]] diff --git a/clang/test/Driver/arm64ec-long-double.c b/clang/test/Driver/arm64ec-long-double.c new file mode 100644 index 00000000000000..18df2a1858e1d1 --- /dev/null +++ b/clang/test/Driver/arm64ec-long-double.c @@ -0,0 +1,21 @@ +// RUN: %clang -### --target=arm64ec-w64-mingw32 -mlong-double-64 -c %s 2>&1 \ +// RUN: | FileCheck %s --check-prefix=LD64 +// RUN: %clang -### --target=arm64ec-w64-mingw32 -mlong-double-80 -c %s 2>&1 \ +// RUN: | FileCheck %s --check-prefix=LD80 +// RUN: %clang -### --target=arm64ec-w64-mingw32 -mlong-double-128 -c %s 2>&1 \ +// RUN: | FileCheck %s --check-prefix=LD128 +// RUN: not %clang --target=arm64ec-pc-windows-msvc -mlong-double-64 \ +// RUN: -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=MSVC +// RUN: not %clang --target=aarch64-w64-mingw32 -mlong-double-80 \ +// RUN: -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=AARCH64 + +// ARM64EC with a non-MSVC ABI defaults to the x87 80-bit format of x86_64, so +// all three widths are accepted and forwarded to the frontend. +// LD64: "-mlong-double-64" +// LD80: "-mlong-double-80" +// LD128: "-mlong-double-128" + +// The MSVC ABI keeps its binary64 `long double` for compatibility, and plain +// AArch64 has no reason to support the x87 format at all. +// MSVC: error: unsupported option '-mlong-double-64' for target 'arm64ec-pc-windows-msvc{{.*}}' +// AARCH64: error: unsupported option '-mlong-double-80' for target 'aarch64-w64-windows-gnu' _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
