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

Reply via email to