llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-backend-aarch64 Author: LIU Hao (lhmouse) <details> <summary>Changes</summary> This commits implement 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 compativle with aarch64-w64-mingw32 and MSVC. `-mlong-double-128` enables float128 in software, same as x86-64. The new, default option `-mlong-double-80` changes `long double` to a pseudo floating-point type: The type exists in the frontend only; for the middle-end and backend, it's an 128-bit integer that provides storage for a value. The way how the frontend emit IR for an operation varies: 1. The ABI of such `long double` is the same with an 128-bit integer. For x86-64 it's passed and returned in memory. 2. Load/store operations are performed with integer instructions, and are exact. 3. Bitwise operations, such as `fabsl` and `isnan`, are performed directly in the frontend with integer instructions. 4. Arithmetic operations of builtin operators are performed by first converting `long double` to `double`, doing the arithmetic, then converting the result back. This matches how x86-64 emulation works by default on Windows on ARM64 with only 53-bit precision, and should be more efficient than a strict softmath implementation, which the AArch64 backend doesn't support yet. 5. Other builtin functions result in external calls to mingw-w64 CRT directly. These changes have been verified on my ARM64 tablet with a patched mingw-w64 CRT. No regressions are introduced. Assisted-by: DeepSeek V4.1 Flash --- Patch is 75.67 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/237057.diff 14 Files Affected: - (modified) clang/include/clang/Basic/TargetInfo.h (+6) - (modified) clang/lib/Basic/Targets/AArch64.cpp (+17) - (modified) clang/lib/Basic/Targets/AArch64.h (+5) - (modified) clang/lib/CodeGen/CGBuiltin.cpp (+428) - (modified) clang/lib/CodeGen/CGExpr.cpp (+13-2) - (modified) clang/lib/CodeGen/CGExprComplex.cpp (+101-6) - (modified) clang/lib/CodeGen/CGExprConstant.cpp (+19-8) - (modified) clang/lib/CodeGen/CGExprScalar.cpp (+369-14) - (modified) clang/lib/CodeGen/CodeGenFunction.h (+17) - (modified) clang/lib/CodeGen/CodeGenTypes.cpp (+7) - (modified) clang/lib/Driver/ToolChains/Clang.cpp (+3) - (added) clang/test/CodeGen/arm64ec-long-double-modes.c (+31) - (added) clang/test/CodeGen/arm64ec-soft-x87-long-double.c (+160) - (added) clang/test/Driver/arm64ec-long-double.c (+21) ``````````diff diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h index a6523e8eaaa437..32aeb5f645b3db 100644 --- a/clang/include/clang/Basic/TargetInfo.h +++ b/clang/include/clang/Basic/TargetInfo.h @@ -709,6 +709,12 @@ class TargetInfo : public TransferrableTargetInfo, /// Whether half args and returns are supported. virtual bool allowHalfArgsAndReturns() const { return HalfArgsAndReturns; } + /// Determine whether the target has the x87-specific `long double` type + /// without a physical x87. The target arm64ec-w64-mingw32 has this property. + /// On such a target, `long double` is an integer image and is not a true + /// floating-point type. + virtual bool hasSoftX87LongDouble() const { return false; } + /// Determine whether the __float128 type is supported on this target. virtual bool hasFloat128Type() const { return HasFloat128; } 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/Basic/Targets/AArch64.h b/clang/lib/Basic/Targets/AArch64.h index 00e84cc4864399..c17a092265ddb1 100644 --- a/clang/lib/Basic/Targets/AArch64.h +++ b/clang/lib/Basic/Targets/AArch64.h @@ -295,6 +295,11 @@ class LLVM_LIBRARY_VISIBILITY WindowsARM64TargetInfo BuiltinVaListKind getBuiltinVaListKind() const override; CallingConvCheckResult checkCallingConvention(CallingConv CC) const override; + + bool hasSoftX87LongDouble() const override { + return Triple.isWindowsArm64EC() && + LongDoubleFormat == &llvm::APFloat::x87DoubleExtended(); + } }; // Windows ARM, MS (C++) ABI diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index e154b9e752fd5c..9b3b28c85a03e3 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2714,12 +2714,440 @@ static void ClearPadding(CodeGenFunction &CGF, Address Src, } } +/// Lower builtins that operate on ARM64EC's software x87 long double. The +/// ones that are required to be exact (fabs, copysign, signbit, isnan, isinf, +/// isfinite, isnormal, fpclassify) are implemented with integer operations on +/// the image itself; everything else is evaluated in `double` exactly like the +/// C library does for its own long double entry points. Return false when the +/// builtin does not involve such a value. +bool CodeGenFunction::EmitSoftX87LongDoubleBuiltin(unsigned BuiltinID, + const CallExpr *E, + RValue &Result) { + // The 'nan' and 'inf' families produce a `long double`, so they do not + // consume one but do need the integer image constructed below: they are the + // only builtins that yield a constant. Match the bare name, since some of + // them are also reachable with a `__builtin_` prefix. + std::string Name = getContext().BuiltinInfo.getName(BuiltinID); + StringRef BaseName = Name; + BaseName.consume_front("__builtin_"); + bool ProducesLongDouble = false; + for (const char *Str : {"nanl", "nansl", "infl", "huge_vall"}) + ProducesLongDouble |= BaseName == Str; + + // Everything below rewrites a call into its binary64 equivalent, which only + // makes sense when a `long double` is *consumed*. + bool ConsumesLongDouble = false; + for (const Expr *Arg : E->arguments()) { + QualType Ty = Arg->getType(); + if (const auto *CTy = Ty->getAs<ComplexType>()) + Ty = CTy->getElementType(); + ConsumesLongDouble |= Ty->isSpecificBuiltinType(BuiltinType::LongDouble); + } + + if (!ProducesLongDouble && !ConsumesLongDouble) + return false; + + // `fabsl`: Set the sign bit to zero. + if (BaseName == "fabsl") { + if (!E->getType()->isSpecificBuiltinType(BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Constant *MagnitudeMask = llvm::ConstantInt::get( + V->getType(), ~llvm::APInt::getOneBitSet(128, 79)); + Result = RValue::get(Builder.CreateAnd(V, MagnitudeMask)); + return true; + } + + // `copysignl`: Take the magnitude from the first operand and the sign bit + // from the second one. + if (BaseName == "copysignl") { + if (!E->getType()->isSpecificBuiltinType(BuiltinType::LongDouble)) + return false; + llvm::Value *X = EmitScalarExpr(E->getArg(0)); + llvm::Value *Y = EmitScalarExpr(E->getArg(1)); + llvm::Constant *MagnitudeMask = llvm::ConstantInt::get( + X->getType(), ~llvm::APInt::getOneBitSet(128, 79)); + llvm::Value *Magnitude = Builder.CreateAnd(X, MagnitudeMask); + llvm::Value *Sign = Builder.CreateAnd(Y, Builder.CreateNot(MagnitudeMask)); + Result = RValue::get(Builder.CreateOr(Magnitude, Sign)); + return true; + } + + // `signbit`: Take the sign bit and normalize it to boolean. + if (Name == "__builtin_signbit" || BaseName == "signbitl") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Value *Sign = + Builder.CreateTrunc(Builder.CreateLShr(V, 79), Builder.getInt1Ty()); + Result = RValue::get(Builder.CreateZExt(Sign, ConvertType(E->getType()))); + return true; + } + + // `__builtin_isnan`: Check if the value is a NaN. An unnormal or pseudo + // denormal image is undefined behavior, since it will never be produced in a + // constant expression. + if (Name == "__builtin_isnan") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Constant *MagnitudeMask = llvm::ConstantInt::get( + V->getType(), ~llvm::APInt::getOneBitSet(128, 79)); + llvm::Value *Magnitude = Builder.CreateAnd(V, MagnitudeMask); + llvm::APInt InfinityImage(128, 0x8000000000000000ull); + InfinityImage |= llvm::APInt(128, 0x7fffull) << 64; + llvm::Constant *Infinity = + llvm::ConstantInt::get(V->getType(), InfinityImage); + llvm::Value *IsNan = Builder.CreateICmpUGT(Magnitude, Infinity); + Result = RValue::get(Builder.CreateZExt(IsNan, ConvertType(E->getType()))); + return true; + } + + // `__builtin_isinf`: Check if the value is a (positive or negative) infinity. + if (Name == "__builtin_isinf") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Constant *MagnitudeMask = llvm::ConstantInt::get( + V->getType(), ~llvm::APInt::getOneBitSet(128, 79)); + llvm::Value *Magnitude = Builder.CreateAnd(V, MagnitudeMask); + llvm::APInt InfinityImage(128, 0x8000000000000000ull); + InfinityImage |= llvm::APInt(128, 0x7fffull) << 64; + llvm::Constant *Infinity = + llvm::ConstantInt::get(V->getType(), InfinityImage); + llvm::Value *IsInf = Builder.CreateICmpEQ(Magnitude, Infinity); + Result = RValue::get(Builder.CreateZExt(IsInf, ConvertType(E->getType()))); + return true; + } + + // `__builtin_isinf_sign`: Return 1 or -1 depending on the sign of an + // infinity, and zero for everything else. + if (Name == "__builtin_isinf_sign") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Constant *MagnitudeMask = llvm::ConstantInt::get( + V->getType(), ~llvm::APInt::getOneBitSet(128, 79)); + llvm::Value *Magnitude = Builder.CreateAnd(V, MagnitudeMask); + llvm::APInt InfinityImage(128, 0x8000000000000000ull); + InfinityImage |= llvm::APInt(128, 0x7fffull) << 64; + llvm::Constant *Infinity = + llvm::ConstantInt::get(V->getType(), InfinityImage); + llvm::Type *IntTy = ConvertType(E->getType()); + llvm::Value *Sign = + Builder.CreateTrunc(Builder.CreateLShr(V, 79), Builder.getInt1Ty()); + llvm::Value *Signed = Builder.CreateSelect( + Sign, llvm::ConstantInt::get(IntTy, -1, /*isSigned=*/true), + llvm::ConstantInt::get(IntTy, 1)); + llvm::Value *IsInf = Builder.CreateICmpEQ(Magnitude, Infinity); + Result = RValue::get( + Builder.CreateSelect(IsInf, Signed, llvm::ConstantInt::get(IntTy, 0))); + return true; + } + + // `__builtin_isfinite`: Check if the value is neither an infinity nor a NaN. + if (Name == "__builtin_isfinite") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Constant *MagnitudeMask = llvm::ConstantInt::get( + V->getType(), ~llvm::APInt::getOneBitSet(128, 79)); + llvm::Value *Magnitude = Builder.CreateAnd(V, MagnitudeMask); + llvm::APInt InfinityImage(128, 0x8000000000000000ull); + InfinityImage |= llvm::APInt(128, 0x7fffull) << 64; + llvm::Constant *Infinity = + llvm::ConstantInt::get(V->getType(), InfinityImage); + llvm::Value *IsFinite = Builder.CreateICmpULT(Magnitude, Infinity); + Result = + RValue::get(Builder.CreateZExt(IsFinite, ConvertType(E->getType()))); + return true; + } + + // `__builtin_isnormal`: Check if the value is finite and nonzero. A + // canonical image has the explicit integer bit set when the exponent is + // nonzero, so the exponent alone decides; an unnormal is undefined behaviour. + if (Name == "__builtin_isnormal") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Value *Exp = Builder.CreateAnd( + Builder.CreateTrunc(Builder.CreateLShr(V, 64), Builder.getInt16Ty()), + Builder.getInt16(0x7FFF)); + llvm::Value *IsNormal = + Builder.CreateAnd(Builder.CreateICmpNE(Exp, Builder.getInt16(0)), + Builder.CreateICmpNE(Exp, Builder.getInt16(0x7FFF))); + Result = + RValue::get(Builder.CreateZExt(IsNormal, ConvertType(E->getType()))); + return true; + } + + // `__builtin_fpclassify`: Pick the first five arguments that correspond to a + // NaN, an infinity, a normal, a subnormal and a zero respectively. + if (Name == "__builtin_fpclassify") { + if (!E->getArg(5)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *V = EmitScalarExpr(E->getArg(5)); + llvm::Value *Exp = Builder.CreateAnd( + Builder.CreateTrunc(Builder.CreateLShr(V, 64), Builder.getInt16Ty()), + Builder.getInt16(0x7FFF)); + llvm::Value *Man = Builder.CreateTrunc(V, Builder.getInt64Ty()); + llvm::Value *ExpZero = Builder.CreateICmpEQ(Exp, Builder.getInt16(0)); + llvm::Value *ExpAllOnes = + Builder.CreateICmpEQ(Exp, Builder.getInt16(0x7FFF)); + llvm::Value *ManZero = Builder.CreateICmpEQ(Man, Builder.getInt64(0)); + // Infinity is the all-ones exponent with nothing but the integer bit set; + // any other value with that exponent is a NaN. An unnormal, whose integer + // bit is clear, is undefined behaviour. + llvm::Value *IsInf = Builder.CreateAnd( + ExpAllOnes, + Builder.CreateICmpEQ(Man, Builder.getInt64(0x8000000000000000ull))); + llvm::Value *IsNaN = + Builder.CreateAnd(ExpAllOnes, Builder.CreateNot(IsInf)); + llvm::Value *IsNormal = Builder.CreateAnd(Builder.CreateNot(ExpZero), + Builder.CreateNot(ExpAllOnes)); + llvm::Value *IsSubnormal = + Builder.CreateAnd(ExpZero, Builder.CreateNot(ManZero)); + llvm::Value *R = EmitScalarExpr(E->getArg(4)); // zero + R = Builder.CreateSelect(IsSubnormal, EmitScalarExpr(E->getArg(3)), R); + R = Builder.CreateSelect(IsNormal, EmitScalarExpr(E->getArg(2)), R); + R = Builder.CreateSelect(IsInf, EmitScalarExpr(E->getArg(1)), R); + R = Builder.CreateSelect(IsNaN, EmitScalarExpr(E->getArg(0)), R); + Result = RValue::get(R); + return true; + } + + // `infl` and `huge_vall`: Infinity has the significand 0x8000000000000000 + // with an all-ones exponent, and the six padding bytes are zeroes. They + // take no argument at all, so there is nothing to check. + if (BaseName == "infl" || BaseName == "huge_vall") { + llvm::APInt Image(128, 0x8000000000000000ull); + Image |= llvm::APInt(128, 0x7fffull) << 64; + llvm::Type *ImageTy = llvm::IntegerType::get(getLLVMContext(), 128); + Result = RValue::get(llvm::ConstantInt::get(ImageTy, Image)); + return true; + } + + // `nanl` and `nansl`: The quiet NaN has the significand 0xc000000000000000 + // and the signalling one has 0xa000000000000000, both with an all-ones + // exponent. The payload of the tag argument is not propagated, but the + // argument is still evaluated, since it may have side effects. + if (BaseName == "nanl" || BaseName == "nansl") { + if (!E->getArg(0)->getType()->isPointerType()) + return false; + EmitScalarExpr(E->getArg(0)); + llvm::APInt Image(128, BaseName == "nansl" ? 0xa000000000000000ull + : 0xc000000000000000ull); + Image |= llvm::APInt(128, 0x7fffull) << 64; + llvm::Type *ImageTy = llvm::IntegerType::get(getLLVMContext(), 128); + Result = RValue::get(llvm::ConstantInt::get(ImageTy, Image)); + return true; + } + + // `__builtin_isunordered`: True if either operand is a NaN. + // `__builtin_isgreater`, `__builtin_isgreaterequal`, `__builtin_isless` and + // `__builtin_islessequal`: Perform ordered comparisons of two operands. + // `__builtin_islessgreater`: True if the operands are ordered and unequal; + // that is, if either of `x < y` and `x > y` holds. + struct BuiltinComparison { + const char *Name; + CmpInst::Predicate Predicate; + }; + static constexpr BuiltinComparison BuiltinComparisons[] = { + {"__builtin_isunordered", CmpInst::FCMP_UNO}, + {"__builtin_isgreater", CmpInst::FCMP_OGT}, + {"__builtin_isgreaterequal", CmpInst::FCMP_OGE}, + {"__builtin_isless", CmpInst::FCMP_OLT}, + {"__builtin_islessequal", CmpInst::FCMP_OLE}, + {"__builtin_islessgreater", CmpInst::FCMP_ONE}, + }; + for (const auto &BC : BuiltinComparisons) + if (Name == BC.Name) { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble) || + !E->getArg(1)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *A = EmitScalarExpr(E->getArg(0)); + llvm::Value *B = EmitScalarExpr(E->getArg(1)); + llvm::Value *Cmp = EmitSoftX87LongDoubleComparison(A, B, BC.Predicate); + Result = RValue::get(Builder.CreateZExt(Cmp, ConvertType(E->getType()))); + return true; + } + + // `__builtin_isfpclass`: Test the image against a mask of class bits. The + // second argument is an integer constant expression built out of the + // `__FPCLASS_*` values, whose bits number the classes in this order: + // signalling NaN, quiet NaN, -inf, negative normal, negative subnormal, + // -0, +0, positive subnormal, positive normal and +inf. So the value only + // has to be mapped to the ordinal of the class it belongs to, and the bit + // of the mask with that index decides. + if (Name == "__builtin_isfpclass") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + Expr::EvalResult MaskResult; + if (!E->getArg(1)->EvaluateAsInt(MaskResult, getContext())) + return false; + const uint64_t Mask = MaskResult.Val.getInt().getLimitedValue(); + llvm::Value *V = EmitScalarExpr(E->getArg(0)); + llvm::Value *Exp = Builder.CreateAnd( + Builder.CreateTrunc(Builder.CreateLShr(V, 64), Builder.getInt16Ty()), + Builder.getInt16(0x7FFF)); + llvm::Value *Man = Builder.CreateTrunc(V, Builder.getInt64Ty()); + llvm::Value *Negative = + Builder.CreateTrunc(Builder.CreateLShr(V, 79), Builder.getInt1Ty()); + llvm::Value *ExpZero = Builder.CreateICmpEQ(Exp, Builder.getInt16(0)); + llvm::Value *ExpAllOnes = + Builder.CreateICmpEQ(Exp, Builder.getInt16(0x7FFF)); + llvm::Value *ManZero = Builder.CreateICmpEQ(Man, Builder.getInt64(0)); + // Infinity is the all-ones exponent with nothing but the integer bit + // set, and a NaN is any other image with that exponent, quiet when bit + // 62 of the significand is set. An unnormal, whose integer bit is + // clear, is undefined behaviour. + llvm::Value *Infinity = Builder.CreateAnd( + ExpAllOnes, + Builder.CreateICmpEQ(Man, Builder.getInt64(0x8000000000000000ull))); + llvm::Value *Quiet = Builder.CreateICmpNE( + Builder.CreateAnd(Man, Builder.getInt64(0x4000000000000000ull)), + Builder.getInt64(0)); + // The magnitudes are numbered 2 to 5 from the largest downwards, and the + // positive classes continue the sequence in the reverse order, which makes + // them `11 - Magnitude`. A NaN takes 0 or 1 instead of a magnitude. + llvm::Value *Magnitude = Builder.getInt64(2); + Magnitude = Builder.CreateSelect(Builder.CreateNot(ExpAllOnes), + Builder.getInt64(3), Magnitude); + Magnitude = Builder.CreateSelect(ExpZero, Builder.getInt64(4), Magnitude); + Magnitude = Builder.CreateSelect(Builder.CreateAnd(ExpZero, ManZero), + Builder.getInt64(5), Magnitude); + Magnitude = Builder.CreateSelect( + Negative, Magnitude, + Builder.CreateSub(Builder.getInt64(11), Magnitude)); + llvm::Value *Ordinal = + Builder.CreateSelect(Quiet, Builder.getInt64(1), Builder.getInt64(0)); + Ordinal = Builder.CreateSelect( + Builder.CreateAnd(ExpAllOnes, Builder.CreateNot(Infinity)), Ordinal, + Magnitude); + llvm::Value *Bit = + Builder.CreateAnd(Builder.CreateLShr(Builder.getInt64(Mask), Ordinal), + Builder.getInt64(1)); + Result = RValue::get( + Builder.CreateZExt(Builder.CreateICmpNE(Bit, Builder.getInt64(0)), + ConvertType(E->getType()))); + return true; + } + + // `__builtin_complex`: Assemble a complex value out of a real and an + // imaginary part, which become the low and high halves of the image pair. + // Its operands are "custom type checked", so they are not promoted: both + // have the same real floating type already, and they have to be the `long + // double` one for this lowering to apply. + if (Name == "__builtin_complex") { + if (!E->getArg(0)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble) || + !E->getArg(1)->getType()->isSpecificBuiltinType( + BuiltinType::LongDouble)) + return false; + llvm::Value *Real = EmitScalarE... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/237057 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
