//===--- InterpBuiltin.cpp - Interpreter for the constexpr VM ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "../ExprConstShared.h"
#include "Boolean.h"
#include "EvalEmitter.h"
#include "InterpBuiltinBitCast.h"
#include "InterpHelpers.h"
#include "PrimType.h"
#include "Program.h"
#include "clang/AST/InferAlloc.h"
#include "clang/AST/OSLog.h"
#include "clang/AST/RecordLayout.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/AllocToken.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/SipHash.h"

namespace clang {
namespace interp {

[[maybe_unused]] static bool isNoopBuiltin(unsigned ID) {
  switch (ID) {
  case Builtin::BIas_const:
  case Builtin::BIforward:
  case Builtin::BIforward_like:
  case Builtin::BImove:
  case Builtin::BImove_if_noexcept:
  case Builtin::BIaddressof:
  case Builtin::BI__addressof:
  case Builtin::BI__builtin_addressof:
  case Builtin::BI__builtin_launder:
    return true;
  default:
    return false;
  }
  return false;
}

static void discard(InterpStack &Stk, PrimType T) {
  TYPE_SWITCH(T, { Stk.discard<T>(); });
}

static uint64_t popToUInt64(const InterpState &S, const Expr *E) {
  INT_TYPE_SWITCH(*S.getContext().classify(E->getType()),
                  return static_cast<uint64_t>(S.Stk.pop<T>()));
}

static APSInt popToAPSInt(InterpStack &Stk, PrimType T) {
  INT_TYPE_SWITCH(T, return Stk.pop<T>().toAPSInt());
}

static APSInt popToAPSInt(InterpState &S, const Expr *E) {
  return popToAPSInt(S.Stk, *S.getContext().classify(E->getType()));
}
static APSInt popToAPSInt(InterpState &S, QualType T) {
  return popToAPSInt(S.Stk, *S.getContext().classify(T));
}

/// Check for common reasons a pointer can't be read from, which
/// are usually not diagnosed in a builtin function.
static bool isReadable(const Pointer &P) {
  if (P.isDummy())
    return false;
  if (!P.isBlockPointer())
    return false;
  if (!P.isLive())
    return false;
  if (P.isOnePastEnd())
    return false;
  return true;
}

/// Pushes \p Val on the stack as the type given by \p QT.
static void pushInteger(InterpState &S, const APSInt &Val, QualType QT) {
  assert(QT->isSignedIntegerOrEnumerationType() ||
         QT->isUnsignedIntegerOrEnumerationType());
  OptPrimType T = S.getContext().classify(QT);
  assert(T);

  unsigned BitWidth = S.getASTContext().getTypeSize(QT);

  if (T == PT_IntAPS) {
    auto Result = S.allocAP<IntegralAP<true>>(BitWidth);
    Result.copy(Val);
    S.Stk.push<IntegralAP<true>>(Result);
    return;
  }

  if (T == PT_IntAP) {
    auto Result = S.allocAP<IntegralAP<false>>(BitWidth);
    Result.copy(Val);
    S.Stk.push<IntegralAP<false>>(Result);
    return;
  }

  if (QT->isSignedIntegerOrEnumerationType()) {
    int64_t V = Val.getSExtValue();
    INT_TYPE_SWITCH(*T, { S.Stk.push<T>(T::from(V, BitWidth)); });
  } else {
    assert(QT->isUnsignedIntegerOrEnumerationType());
    uint64_t V = Val.getZExtValue();
    INT_TYPE_SWITCH(*T, { S.Stk.push<T>(T::from(V, BitWidth)); });
  }
}

template <typename T>
static void pushInteger(InterpState &S, T Val, QualType QT) {
  if constexpr (std::is_same_v<T, APInt>)
    pushInteger(S, APSInt(Val, !std::is_signed_v<T>), QT);
  else if constexpr (std::is_same_v<T, APSInt>)
    pushInteger(S, Val, QT);
  else
    pushInteger(S,
                APSInt(APInt(sizeof(T) * 8, static_cast<uint64_t>(Val),
                             std::is_signed_v<T>),
                       !std::is_signed_v<T>),
                QT);
}

static void assignInteger(InterpState &S, const Pointer &Dest, PrimType ValueT,
                          const APSInt &Value) {

  if (ValueT == PT_IntAPS) {
    Dest.deref<IntegralAP<true>>() =
        S.allocAP<IntegralAP<true>>(Value.getBitWidth());
    Dest.deref<IntegralAP<true>>().copy(Value);
  } else if (ValueT == PT_IntAP) {
    Dest.deref<IntegralAP<false>>() =
        S.allocAP<IntegralAP<false>>(Value.getBitWidth());
    Dest.deref<IntegralAP<false>>().copy(Value);
  } else {
    INT_TYPE_SWITCH_NO_BOOL(
        ValueT, { Dest.deref<T>() = T::from(static_cast<T>(Value)); });
  }
}

static QualType getElemType(const Pointer &P) {
  const Descriptor *Desc = P.getFieldDesc();
  QualType T = Desc->getType();
  if (Desc->isPrimitive())
    return T;
  if (T->isPointerType())
    return T->castAs<PointerType>()->getPointeeType();
  if (Desc->isArray())
    return Desc->getElemQualType();
  if (const auto *AT = T->getAsArrayTypeUnsafe())
    return AT->getElementType();
  return T;
}

static void diagnoseNonConstexprBuiltin(InterpState &S, CodePtr OpPC,
                                        unsigned ID) {
  if (!S.diagnosing())
    return;

  auto Loc = S.Current->getSource(OpPC);
  if (S.getLangOpts().CPlusPlus11)
    S.CCEDiag(Loc, diag::note_constexpr_invalid_function)
        << /*isConstexpr=*/0 << /*isConstructor=*/0
        << S.getASTContext().BuiltinInfo.getQuotedName(ID);
  else
    S.CCEDiag(Loc, diag::note_invalid_subexpr_in_const_expr);
}

static llvm::APSInt convertBoolVectorToInt(const Pointer &Val) {
  assert(Val.getFieldDesc()->isPrimitiveArray() &&
         Val.getFieldDesc()->getElemQualType()->isBooleanType() &&
         "Not a boolean vector");
  unsigned NumElems = Val.getNumElems();

  // Each element is one bit, so create an integer with NumElts bits.
  llvm::APSInt Result(NumElems, 0);
  for (unsigned I = 0; I != NumElems; ++I) {
    if (Val.elem<bool>(I))
      Result.setBit(I);
  }

  return Result;
}

// Strict double -> float conversion used for X86 PD2PS/cvtsd2ss intrinsics.
// Reject NaN/Inf/Subnormal inputs and any lossy/inexact conversions.
static bool convertDoubleToFloatStrict(APFloat Src, Floating &Dst,
                                       InterpState &S, const Expr *DiagExpr) {
  if (Src.isInfinity()) {
    if (S.diagnosing())
      S.CCEDiag(DiagExpr, diag::note_constexpr_float_arithmetic) << 0;
    return false;
  }
  if (Src.isNaN()) {
    if (S.diagnosing())
      S.CCEDiag(DiagExpr, diag::note_constexpr_float_arithmetic) << 1;
    return false;
  }
  APFloat Val = Src;
  bool LosesInfo = false;
  APFloat::opStatus Status = Val.convert(
      APFloat::IEEEsingle(), APFloat::rmNearestTiesToEven, &LosesInfo);
  if (LosesInfo || Val.isDenormal()) {
    if (S.diagnosing())
      S.CCEDiag(DiagExpr, diag::note_constexpr_float_arithmetic_strict);
    return false;
  }
  if (Status != APFloat::opOK) {
    if (S.diagnosing())
      S.CCEDiag(DiagExpr, diag::note_invalid_subexpr_in_const_expr);
    return false;
  }
  Dst.copy(Val);
  return true;
}

static bool interp__builtin_is_constant_evaluated(InterpState &S, CodePtr OpPC,
                                                  const InterpFrame *Frame,
                                                  const CallExpr *Call) {
  unsigned Depth = S.Current->getDepth();
  auto isStdCall = [](const FunctionDecl *F) -> bool {
    return F && F->isInStdNamespace() && F->getIdentifier() &&
           F->getIdentifier()->isStr("is_constant_evaluated");
  };
  const InterpFrame *Caller = Frame->Caller;
  // The current frame is the one for __builtin_is_constant_evaluated.
  // The one above that, potentially the one for std::is_constant_evaluated().
  if (S.inConstantContext() && !S.checkingPotentialConstantExpression() &&
      S.getEvalStatus().Diag &&
      (Depth == 0 || (Depth == 1 && isStdCall(Frame->getCallee())))) {
    if (Caller && isStdCall(Frame->getCallee())) {
      const Expr *E = Caller->getExpr(Caller->getRetPC());
      S.report(E->getExprLoc(),
               diag::warn_is_constant_evaluated_always_true_constexpr)
          << "std::is_constant_evaluated" << E->getSourceRange();
    } else {
      S.report(Call->getExprLoc(),
               diag::warn_is_constant_evaluated_always_true_constexpr)
          << "__builtin_is_constant_evaluated" << Call->getSourceRange();
    }
  }

  S.Stk.push<Boolean>(Boolean::from(S.inConstantContext()));
  return true;
}

// __builtin_assume(int)
static bool interp__builtin_assume(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call) {
  assert(Call->getNumArgs() == 1);
  discard(S.Stk, *S.getContext().classify(Call->getArg(0)));
  return true;
}

static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call, unsigned ID) {
  uint64_t Limit = ~static_cast<uint64_t>(0);
  if (ID == Builtin::BIstrncmp || ID == Builtin::BI__builtin_strncmp ||
      ID == Builtin::BIwcsncmp || ID == Builtin::BI__builtin_wcsncmp)
    Limit = popToUInt64(S, Call->getArg(2));

  const Pointer &B = S.Stk.pop<Pointer>();
  const Pointer &A = S.Stk.pop<Pointer>();
  if (ID == Builtin::BIstrcmp || ID == Builtin::BIstrncmp ||
      ID == Builtin::BIwcscmp || ID == Builtin::BIwcsncmp)
    diagnoseNonConstexprBuiltin(S, OpPC, ID);

  if (Limit == 0) {
    pushInteger(S, 0, Call->getType());
    return true;
  }

  if (!CheckLive(S, OpPC, A, AK_Read) || !CheckLive(S, OpPC, B, AK_Read))
    return false;

  if (A.isDummy() || B.isDummy())
    return false;
  if (!A.isBlockPointer() || !B.isBlockPointer())
    return false;

  bool IsWide = ID == Builtin::BIwcscmp || ID == Builtin::BIwcsncmp ||
                ID == Builtin::BI__builtin_wcscmp ||
                ID == Builtin::BI__builtin_wcsncmp;
  assert(A.getFieldDesc()->isPrimitiveArray());
  assert(B.getFieldDesc()->isPrimitiveArray());

  // Different element types shouldn't happen, but with casts they can.
  if (!S.getASTContext().hasSameUnqualifiedType(getElemType(A), getElemType(B)))
    return false;

  PrimType ElemT = *S.getContext().classify(getElemType(A));

  auto returnResult = [&](int V) -> bool {
    pushInteger(S, V, Call->getType());
    return true;
  };

  unsigned IndexA = A.getIndex();
  unsigned IndexB = B.getIndex();
  uint64_t Steps = 0;
  for (;; ++IndexA, ++IndexB, ++Steps) {

    if (Steps >= Limit)
      break;
    const Pointer &PA = A.atIndex(IndexA);
    const Pointer &PB = B.atIndex(IndexB);
    if (!CheckRange(S, OpPC, PA, AK_Read) ||
        !CheckRange(S, OpPC, PB, AK_Read)) {
      return false;
    }

    if (IsWide) {
      INT_TYPE_SWITCH(ElemT, {
        T CA = PA.deref<T>();
        T CB = PB.deref<T>();
        if (CA > CB)
          return returnResult(1);
        if (CA < CB)
          return returnResult(-1);
        if (CA.isZero() || CB.isZero())
          return returnResult(0);
      });
      continue;
    }

    uint8_t CA = PA.deref<uint8_t>();
    uint8_t CB = PB.deref<uint8_t>();

    if (CA > CB)
      return returnResult(1);
    if (CA < CB)
      return returnResult(-1);
    if (CA == 0 || CB == 0)
      return returnResult(0);
  }

  return returnResult(0);
}

static bool interp__builtin_strlen(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call, unsigned ID) {
  const Pointer &StrPtr = S.Stk.pop<Pointer>().expand();

  if (ID == Builtin::BIstrlen || ID == Builtin::BIwcslen)
    diagnoseNonConstexprBuiltin(S, OpPC, ID);

  if (!CheckArray(S, OpPC, StrPtr))
    return false;

  if (!CheckLive(S, OpPC, StrPtr, AK_Read))
    return false;

  if (!CheckDummy(S, OpPC, StrPtr.block(), AK_Read))
    return false;

  if (!StrPtr.getFieldDesc()->isPrimitiveArray())
    return false;

  assert(StrPtr.getFieldDesc()->isPrimitiveArray());
  unsigned ElemSize = StrPtr.getFieldDesc()->getElemSize();

  if (ID == Builtin::BI__builtin_wcslen || ID == Builtin::BIwcslen) {
    [[maybe_unused]] const ASTContext &AC = S.getASTContext();
    assert(ElemSize == AC.getTypeSizeInChars(AC.getWCharType()).getQuantity());
  }

  size_t Len = 0;
  for (size_t I = StrPtr.getIndex();; ++I, ++Len) {
    const Pointer &ElemPtr = StrPtr.atIndex(I);

    if (!CheckRange(S, OpPC, ElemPtr, AK_Read))
      return false;

    uint32_t Val;
    switch (ElemSize) {
    case 1:
      Val = ElemPtr.deref<uint8_t>();
      break;
    case 2:
      Val = ElemPtr.deref<uint16_t>();
      break;
    case 4:
      Val = ElemPtr.deref<uint32_t>();
      break;
    default:
      llvm_unreachable("Unsupported char size");
    }
    if (Val == 0)
      break;
  }

  pushInteger(S, Len, Call->getType());

  return true;
}

static bool interp__builtin_nan(InterpState &S, CodePtr OpPC,
                                const InterpFrame *Frame, const CallExpr *Call,
                                bool Signaling) {
  const Pointer &Arg = S.Stk.pop<Pointer>();

  if (!CheckLoad(S, OpPC, Arg))
    return false;

  assert(Arg.getFieldDesc()->isPrimitiveArray());

  // Convert the given string to an integer using StringRef's API.
  llvm::APInt Fill;
  std::string Str;
  assert(Arg.getNumElems() >= 1);
  for (unsigned I = 0;; ++I) {
    const Pointer &Elem = Arg.atIndex(I);

    if (!CheckLoad(S, OpPC, Elem))
      return false;

    if (Elem.deref<int8_t>() == 0)
      break;

    Str += Elem.deref<char>();
  }

  // Treat empty strings as if they were zero.
  if (Str.empty())
    Fill = llvm::APInt(32, 0);
  else if (StringRef(Str).getAsInteger(0, Fill))
    return false;

  const llvm::fltSemantics &TargetSemantics =
      S.getASTContext().getFloatTypeSemantics(
          Call->getDirectCallee()->getReturnType());

  Floating Result = S.allocFloat(TargetSemantics);
  if (S.getASTContext().getTargetInfo().isNan2008()) {
    if (Signaling)
      Result.copy(
          llvm::APFloat::getSNaN(TargetSemantics, /*Negative=*/false, &Fill));
    else
      Result.copy(
          llvm::APFloat::getQNaN(TargetSemantics, /*Negative=*/false, &Fill));
  } else {
    // Prior to IEEE 754-2008, architectures were allowed to choose whether
    // the first bit of their significand was set for qNaN or sNaN. MIPS chose
    // a different encoding to what became a standard in 2008, and for pre-
    // 2008 revisions, MIPS interpreted sNaN-2008 as qNan and qNaN-2008 as
    // sNaN. This is now known as "legacy NaN" encoding.
    if (Signaling)
      Result.copy(
          llvm::APFloat::getQNaN(TargetSemantics, /*Negative=*/false, &Fill));
    else
      Result.copy(
          llvm::APFloat::getSNaN(TargetSemantics, /*Negative=*/false, &Fill));
  }

  S.Stk.push<Floating>(Result);
  return true;
}

static bool interp__builtin_inf(InterpState &S, CodePtr OpPC,
                                const InterpFrame *Frame,
                                const CallExpr *Call) {
  const llvm::fltSemantics &TargetSemantics =
      S.getASTContext().getFloatTypeSemantics(
          Call->getDirectCallee()->getReturnType());

  Floating Result = S.allocFloat(TargetSemantics);
  Result.copy(APFloat::getInf(TargetSemantics));
  S.Stk.push<Floating>(Result);
  return true;
}

static bool interp__builtin_copysign(InterpState &S, CodePtr OpPC,
                                     const InterpFrame *Frame) {
  const Floating &Arg2 = S.Stk.pop<Floating>();
  const Floating &Arg1 = S.Stk.pop<Floating>();
  Floating Result = S.allocFloat(Arg1.getSemantics());

  APFloat Copy = Arg1.getAPFloat();
  Copy.copySign(Arg2.getAPFloat());
  Result.copy(Copy);
  S.Stk.push<Floating>(Result);

  return true;
}

static bool interp__builtin_fmin(InterpState &S, CodePtr OpPC,
                                 const InterpFrame *Frame, bool IsNumBuiltin) {
  const Floating &RHS = S.Stk.pop<Floating>();
  const Floating &LHS = S.Stk.pop<Floating>();
  Floating Result = S.allocFloat(LHS.getSemantics());

  if (IsNumBuiltin)
    Result.copy(llvm::minimumnum(LHS.getAPFloat(), RHS.getAPFloat()));
  else
    Result.copy(minnum(LHS.getAPFloat(), RHS.getAPFloat()));
  S.Stk.push<Floating>(Result);
  return true;
}

static bool interp__builtin_fmax(InterpState &S, CodePtr OpPC,
                                 const InterpFrame *Frame, bool IsNumBuiltin) {
  const Floating &RHS = S.Stk.pop<Floating>();
  const Floating &LHS = S.Stk.pop<Floating>();
  Floating Result = S.allocFloat(LHS.getSemantics());

  if (IsNumBuiltin)
    Result.copy(llvm::maximumnum(LHS.getAPFloat(), RHS.getAPFloat()));
  else
    Result.copy(maxnum(LHS.getAPFloat(), RHS.getAPFloat()));
  S.Stk.push<Floating>(Result);
  return true;
}

/// Defined as __builtin_isnan(...), to accommodate the fact that it can
/// take a float, double, long double, etc.
/// But for us, that's all a Floating anyway.
static bool interp__builtin_isnan(InterpState &S, CodePtr OpPC,
                                  const InterpFrame *Frame,
                                  const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isNan(), Call->getType());
  return true;
}

static bool interp__builtin_issignaling(InterpState &S, CodePtr OpPC,
                                        const InterpFrame *Frame,
                                        const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isSignaling(), Call->getType());
  return true;
}

static bool interp__builtin_isinf(InterpState &S, CodePtr OpPC,
                                  const InterpFrame *Frame, bool CheckSign,
                                  const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();
  APFloat F = Arg.getAPFloat();
  bool IsInf = F.isInfinity();

  if (CheckSign)
    pushInteger(S, IsInf ? (F.isNegative() ? -1 : 1) : 0, Call->getType());
  else
    pushInteger(S, IsInf, Call->getType());
  return true;
}

static bool interp__builtin_isfinite(InterpState &S, CodePtr OpPC,
                                     const InterpFrame *Frame,
                                     const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isFinite(), Call->getType());
  return true;
}

static bool interp__builtin_isnormal(InterpState &S, CodePtr OpPC,
                                     const InterpFrame *Frame,
                                     const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isNormal(), Call->getType());
  return true;
}

static bool interp__builtin_issubnormal(InterpState &S, CodePtr OpPC,
                                        const InterpFrame *Frame,
                                        const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isDenormal(), Call->getType());
  return true;
}

static bool interp__builtin_iszero(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isZero(), Call->getType());
  return true;
}

static bool interp__builtin_signbit(InterpState &S, CodePtr OpPC,
                                    const InterpFrame *Frame,
                                    const CallExpr *Call) {
  const Floating &Arg = S.Stk.pop<Floating>();

  pushInteger(S, Arg.isNegative(), Call->getType());
  return true;
}

static bool interp_floating_comparison(InterpState &S, CodePtr OpPC,
                                       const CallExpr *Call, unsigned ID) {
  const Floating &RHS = S.Stk.pop<Floating>();
  const Floating &LHS = S.Stk.pop<Floating>();

  pushInteger(
      S,
      [&] {
        switch (ID) {
        case Builtin::BI__builtin_isgreater:
          return LHS > RHS;
        case Builtin::BI__builtin_isgreaterequal:
          return LHS >= RHS;
        case Builtin::BI__builtin_isless:
          return LHS < RHS;
        case Builtin::BI__builtin_islessequal:
          return LHS <= RHS;
        case Builtin::BI__builtin_islessgreater: {
          ComparisonCategoryResult Cmp = LHS.compare(RHS);
          return Cmp == ComparisonCategoryResult::Less ||
                 Cmp == ComparisonCategoryResult::Greater;
        }
        case Builtin::BI__builtin_isunordered:
          return LHS.compare(RHS) == ComparisonCategoryResult::Unordered;
        default:
          llvm_unreachable("Unexpected builtin ID: Should be a floating point "
                           "comparison function");
        }
      }(),
      Call->getType());
  return true;
}

/// First parameter to __builtin_isfpclass is the floating value, the
/// second one is an integral value.
static bool interp__builtin_isfpclass(InterpState &S, CodePtr OpPC,
                                      const InterpFrame *Frame,
                                      const CallExpr *Call) {
  APSInt FPClassArg = popToAPSInt(S, Call->getArg(1));
  const Floating &F = S.Stk.pop<Floating>();

  int32_t Result = static_cast<int32_t>(
      (F.classify() & std::move(FPClassArg)).getZExtValue());
  pushInteger(S, Result, Call->getType());

  return true;
}

/// Five int values followed by one floating value.
/// __builtin_fpclassify(int, int, int, int, int, float)
static bool interp__builtin_fpclassify(InterpState &S, CodePtr OpPC,
                                       const InterpFrame *Frame,
                                       const CallExpr *Call) {
  const Floating &Val = S.Stk.pop<Floating>();

  PrimType IntT = *S.getContext().classify(Call->getArg(0));
  APSInt Values[5];
  for (unsigned I = 0; I != 5; ++I)
    Values[4 - I] = popToAPSInt(S.Stk, IntT);

  unsigned Index;
  switch (Val.getCategory()) {
  case APFloat::fcNaN:
    Index = 0;
    break;
  case APFloat::fcInfinity:
    Index = 1;
    break;
  case APFloat::fcNormal:
    Index = Val.isDenormal() ? 3 : 2;
    break;
  case APFloat::fcZero:
    Index = 4;
    break;
  }

  // The last argument is first on the stack.
  assert(Index <= 4);

  pushInteger(S, Values[Index], Call->getType());
  return true;
}

static inline Floating abs(InterpState &S, const Floating &In) {
  if (!In.isNegative())
    return In;

  Floating Output = S.allocFloat(In.getSemantics());
  APFloat New = In.getAPFloat();
  New.changeSign();
  Output.copy(New);
  return Output;
}

// The C standard says "fabs raises no floating-point exceptions,
// even if x is a signaling NaN. The returned value is independent of
// the current rounding direction mode."  Therefore constant folding can
// proceed without regard to the floating point settings.
// Reference, WG14 N2478 F.10.4.3
static bool interp__builtin_fabs(InterpState &S, CodePtr OpPC,
                                 const InterpFrame *Frame) {
  const Floating &Val = S.Stk.pop<Floating>();
  S.Stk.push<Floating>(abs(S, Val));
  return true;
}

static bool interp__builtin_abs(InterpState &S, CodePtr OpPC,
                                const InterpFrame *Frame,
                                const CallExpr *Call) {
  APSInt Val = popToAPSInt(S, Call->getArg(0));
  if (Val ==
      APSInt(APInt::getSignedMinValue(Val.getBitWidth()), /*IsUnsigned=*/false))
    return false;
  if (Val.isNegative())
    Val.negate();
  pushInteger(S, Val, Call->getType());
  return true;
}

static bool interp__builtin_popcount(InterpState &S, CodePtr OpPC,
                                     const InterpFrame *Frame,
                                     const CallExpr *Call) {
  APSInt Val;
  if (Call->getArg(0)->getType()->isExtVectorBoolType()) {
    const Pointer &Arg = S.Stk.pop<Pointer>();
    Val = convertBoolVectorToInt(Arg);
  } else {
    Val = popToAPSInt(S, Call->getArg(0));
  }
  pushInteger(S, Val.popcount(), Call->getType());
  return true;
}

static bool interp__builtin_classify_type(InterpState &S, CodePtr OpPC,
                                          const InterpFrame *Frame,
                                          const CallExpr *Call) {
  // This is an unevaluated call, so there are no arguments on the stack.
  assert(Call->getNumArgs() == 1);
  const Expr *Arg = Call->getArg(0);

  GCCTypeClass ResultClass =
      EvaluateBuiltinClassifyType(Arg->getType(), S.getLangOpts());
  int32_t ReturnVal = static_cast<int32_t>(ResultClass);
  pushInteger(S, ReturnVal, Call->getType());
  return true;
}

// __builtin_expect(long, long)
// __builtin_expect_with_probability(long, long, double)
static bool interp__builtin_expect(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call) {
  // The return value is simply the value of the first parameter.
  // We ignore the probability.
  unsigned NumArgs = Call->getNumArgs();
  assert(NumArgs == 2 || NumArgs == 3);

  PrimType ArgT = *S.getContext().classify(Call->getArg(0)->getType());
  if (NumArgs == 3)
    S.Stk.discard<Floating>();
  discard(S.Stk, ArgT);

  APSInt Val = popToAPSInt(S.Stk, ArgT);
  pushInteger(S, Val, Call->getType());
  return true;
}

static bool interp__builtin_addressof(InterpState &S, CodePtr OpPC,
                                      const InterpFrame *Frame,
                                      const CallExpr *Call) {
#ifndef NDEBUG
  assert(Call->getArg(0)->isLValue());
  PrimType PtrT = S.getContext().classify(Call->getArg(0)).value_or(PT_Ptr);
  assert(PtrT == PT_Ptr &&
         "Unsupported pointer type passed to __builtin_addressof()");
#endif
  return true;
}

static bool interp__builtin_move(InterpState &S, CodePtr OpPC,
                                 const InterpFrame *Frame,
                                 const CallExpr *Call) {
  return Call->getDirectCallee()->isConstexpr();
}

static bool interp__builtin_eh_return_data_regno(InterpState &S, CodePtr OpPC,
                                                 const InterpFrame *Frame,
                                                 const CallExpr *Call) {
  APSInt Arg = popToAPSInt(S, Call->getArg(0));

  int Result = S.getASTContext().getTargetInfo().getEHDataRegisterNumber(
      Arg.getZExtValue());
  pushInteger(S, Result, Call->getType());
  return true;
}

// Two integral values followed by a pointer (lhs, rhs, resultOut)
static bool interp__builtin_overflowop(InterpState &S, CodePtr OpPC,
                                       const CallExpr *Call,
                                       unsigned BuiltinOp) {
  const Pointer &ResultPtr = S.Stk.pop<Pointer>();
  if (ResultPtr.isDummy() || !ResultPtr.isBlockPointer())
    return false;

  PrimType RHST = *S.getContext().classify(Call->getArg(1)->getType());
  PrimType LHST = *S.getContext().classify(Call->getArg(0)->getType());
  APSInt RHS = popToAPSInt(S.Stk, RHST);
  APSInt LHS = popToAPSInt(S.Stk, LHST);
  QualType ResultType = Call->getArg(2)->getType()->getPointeeType();
  PrimType ResultT = *S.getContext().classify(ResultType);
  bool Overflow;

  APSInt Result;
  if (BuiltinOp == Builtin::BI__builtin_add_overflow ||
      BuiltinOp == Builtin::BI__builtin_sub_overflow ||
      BuiltinOp == Builtin::BI__builtin_mul_overflow) {
    bool IsSigned = LHS.isSigned() || RHS.isSigned() ||
                    ResultType->isSignedIntegerOrEnumerationType();
    bool AllSigned = LHS.isSigned() && RHS.isSigned() &&
                     ResultType->isSignedIntegerOrEnumerationType();
    uint64_t LHSSize = LHS.getBitWidth();
    uint64_t RHSSize = RHS.getBitWidth();
    uint64_t ResultSize = S.getASTContext().getTypeSize(ResultType);
    uint64_t MaxBits = std::max(std::max(LHSSize, RHSSize), ResultSize);

    // Add an additional bit if the signedness isn't uniformly agreed to. We
    // could do this ONLY if there is a signed and an unsigned that both have
    // MaxBits, but the code to check that is pretty nasty.  The issue will be
    // caught in the shrink-to-result later anyway.
    if (IsSigned && !AllSigned)
      ++MaxBits;

    LHS = APSInt(LHS.extOrTrunc(MaxBits), !IsSigned);
    RHS = APSInt(RHS.extOrTrunc(MaxBits), !IsSigned);
    Result = APSInt(MaxBits, !IsSigned);
  }

  // Find largest int.
  switch (BuiltinOp) {
  default:
    llvm_unreachable("Invalid value for BuiltinOp");
  case Builtin::BI__builtin_add_overflow:
  case Builtin::BI__builtin_sadd_overflow:
  case Builtin::BI__builtin_saddl_overflow:
  case Builtin::BI__builtin_saddll_overflow:
  case Builtin::BI__builtin_uadd_overflow:
  case Builtin::BI__builtin_uaddl_overflow:
  case Builtin::BI__builtin_uaddll_overflow:
    Result = LHS.isSigned() ? LHS.sadd_ov(RHS, Overflow)
                            : LHS.uadd_ov(RHS, Overflow);
    break;
  case Builtin::BI__builtin_sub_overflow:
  case Builtin::BI__builtin_ssub_overflow:
  case Builtin::BI__builtin_ssubl_overflow:
  case Builtin::BI__builtin_ssubll_overflow:
  case Builtin::BI__builtin_usub_overflow:
  case Builtin::BI__builtin_usubl_overflow:
  case Builtin::BI__builtin_usubll_overflow:
    Result = LHS.isSigned() ? LHS.ssub_ov(RHS, Overflow)
                            : LHS.usub_ov(RHS, Overflow);
    break;
  case Builtin::BI__builtin_mul_overflow:
  case Builtin::BI__builtin_smul_overflow:
  case Builtin::BI__builtin_smull_overflow:
  case Builtin::BI__builtin_smulll_overflow:
  case Builtin::BI__builtin_umul_overflow:
  case Builtin::BI__builtin_umull_overflow:
  case Builtin::BI__builtin_umulll_overflow:
    Result = LHS.isSigned() ? LHS.smul_ov(RHS, Overflow)
                            : LHS.umul_ov(RHS, Overflow);
    break;
  }

  // In the case where multiple sizes are allowed, truncate and see if
  // the values are the same.
  if (BuiltinOp == Builtin::BI__builtin_add_overflow ||
      BuiltinOp == Builtin::BI__builtin_sub_overflow ||
      BuiltinOp == Builtin::BI__builtin_mul_overflow) {
    // APSInt doesn't have a TruncOrSelf, so we use extOrTrunc instead,
    // since it will give us the behavior of a TruncOrSelf in the case where
    // its parameter <= its size.  We previously set Result to be at least the
    // type-size of the result, so getTypeSize(ResultType) <= Resu
    APSInt Temp = Result.extOrTrunc(S.getASTContext().getTypeSize(ResultType));
    Temp.setIsSigned(ResultType->isSignedIntegerOrEnumerationType());

    if (!APSInt::isSameValue(Temp, Result))
      Overflow = true;
    Result = std::move(Temp);
  }

  // Write Result to ResultPtr and put Overflow on the stack.
  assignInteger(S, ResultPtr, ResultT, Result);
  if (ResultPtr.canBeInitialized())
    ResultPtr.initialize();

  assert(Call->getDirectCallee()->getReturnType()->isBooleanType());
  S.Stk.push<Boolean>(Overflow);
  return true;
}

/// Three integral values followed by a pointer (lhs, rhs, carry, carryOut).
static bool interp__builtin_carryop(InterpState &S, CodePtr OpPC,
                                    const InterpFrame *Frame,
                                    const CallExpr *Call, unsigned BuiltinOp) {
  const Pointer &CarryOutPtr = S.Stk.pop<Pointer>();
  PrimType LHST = *S.getContext().classify(Call->getArg(0)->getType());
  PrimType RHST = *S.getContext().classify(Call->getArg(1)->getType());
  APSInt CarryIn = popToAPSInt(S.Stk, LHST);
  APSInt RHS = popToAPSInt(S.Stk, RHST);
  APSInt LHS = popToAPSInt(S.Stk, LHST);

  if (CarryOutPtr.isDummy() || !CarryOutPtr.isBlockPointer())
    return false;

  APSInt CarryOut;

  APSInt Result;
  // Copy the number of bits and sign.
  Result = LHS;
  CarryOut = LHS;

  bool FirstOverflowed = false;
  bool SecondOverflowed = false;
  switch (BuiltinOp) {
  default:
    llvm_unreachable("Invalid value for BuiltinOp");
  case Builtin::BI__builtin_addcb:
  case Builtin::BI__builtin_addcs:
  case Builtin::BI__builtin_addc:
  case Builtin::BI__builtin_addcl:
  case Builtin::BI__builtin_addcll:
    Result =
        LHS.uadd_ov(RHS, FirstOverflowed).uadd_ov(CarryIn, SecondOverflowed);
    break;
  case Builtin::BI__builtin_subcb:
  case Builtin::BI__builtin_subcs:
  case Builtin::BI__builtin_subc:
  case Builtin::BI__builtin_subcl:
  case Builtin::BI__builtin_subcll:
    Result =
        LHS.usub_ov(RHS, FirstOverflowed).usub_ov(CarryIn, SecondOverflowed);
    break;
  }
  // It is possible for both overflows to happen but CGBuiltin uses an OR so
  // this is consistent.
  CarryOut = (uint64_t)(FirstOverflowed | SecondOverflowed);

  QualType CarryOutType = Call->getArg(3)->getType()->getPointeeType();
  PrimType CarryOutT = *S.getContext().classify(CarryOutType);
  assignInteger(S, CarryOutPtr, CarryOutT, CarryOut);
  CarryOutPtr.initialize();

  assert(Call->getType() == Call->getArg(0)->getType());
  pushInteger(S, Result, Call->getType());
  return true;
}

static bool interp__builtin_clz(InterpState &S, CodePtr OpPC,
                                const InterpFrame *Frame, const CallExpr *Call,
                                unsigned BuiltinOp) {

  std::optional<APSInt> Fallback;
  if (BuiltinOp == Builtin::BI__builtin_clzg && Call->getNumArgs() == 2)
    Fallback = popToAPSInt(S, Call->getArg(1));

  APSInt Val;
  if (Call->getArg(0)->getType()->isExtVectorBoolType()) {
    const Pointer &Arg = S.Stk.pop<Pointer>();
    Val = convertBoolVectorToInt(Arg);
  } else {
    Val = popToAPSInt(S, Call->getArg(0));
  }

  // When the argument is 0, the result of GCC builtins is undefined, whereas
  // for Microsoft intrinsics, the result is the bit-width of the argument.
  bool ZeroIsUndefined = BuiltinOp != Builtin::BI__lzcnt16 &&
                         BuiltinOp != Builtin::BI__lzcnt &&
                         BuiltinOp != Builtin::BI__lzcnt64;

  if (Val == 0) {
    if (Fallback) {
      pushInteger(S, *Fallback, Call->getType());
      return true;
    }

    if (ZeroIsUndefined)
      return false;
  }

  pushInteger(S, Val.countl_zero(), Call->getType());
  return true;
}

static bool interp__builtin_ctz(InterpState &S, CodePtr OpPC,
                                const InterpFrame *Frame, const CallExpr *Call,
                                unsigned BuiltinID) {
  std::optional<APSInt> Fallback;
  if (BuiltinID == Builtin::BI__builtin_ctzg && Call->getNumArgs() == 2)
    Fallback = popToAPSInt(S, Call->getArg(1));

  APSInt Val;
  if (Call->getArg(0)->getType()->isExtVectorBoolType()) {
    const Pointer &Arg = S.Stk.pop<Pointer>();
    Val = convertBoolVectorToInt(Arg);
  } else {
    Val = popToAPSInt(S, Call->getArg(0));
  }

  if (Val == 0) {
    if (Fallback) {
      pushInteger(S, *Fallback, Call->getType());
      return true;
    }
    return false;
  }

  pushInteger(S, Val.countr_zero(), Call->getType());
  return true;
}

static bool interp__builtin_bswap(InterpState &S, CodePtr OpPC,
                                  const InterpFrame *Frame,
                                  const CallExpr *Call) {
  const APSInt &Val = popToAPSInt(S, Call->getArg(0));
  if (Val.getBitWidth() == 8)
    pushInteger(S, Val, Call->getType());
  else
    pushInteger(S, Val.byteSwap(), Call->getType());
  return true;
}

/// bool __atomic_always_lock_free(size_t, void const volatile*)
/// bool __atomic_is_lock_free(size_t, void const volatile*)
static bool interp__builtin_atomic_lock_free(InterpState &S, CodePtr OpPC,
                                             const InterpFrame *Frame,
                                             const CallExpr *Call,
                                             unsigned BuiltinOp) {
  auto returnBool = [&S](bool Value) -> bool {
    S.Stk.push<Boolean>(Value);
    return true;
  };

  const Pointer &Ptr = S.Stk.pop<Pointer>();
  uint64_t SizeVal = popToUInt64(S, Call->getArg(0));

  // For __atomic_is_lock_free(sizeof(_Atomic(T))), if the size is a power
  // of two less than or equal to the maximum inline atomic width, we know it
  // is lock-free.  If the size isn't a power of two, or greater than the
  // maximum alignment where we promote atomics, we know it is not lock-free
  // (at least not in the sense of atomic_is_lock_free).  Otherwise,
  // the answer can only be determined at runtime; for example, 16-byte
  // atomics have lock-free implementations on some, but not all,
  // x86-64 processors.

  // Check power-of-two.
  CharUnits Size = CharUnits::fromQuantity(SizeVal);
  if (Size.isPowerOfTwo()) {
    // Check against inlining width.
    unsigned InlineWidthBits =
        S.getASTContext().getTargetInfo().getMaxAtomicInlineWidth();
    if (Size <= S.getASTContext().toCharUnitsFromBits(InlineWidthBits)) {

      // OK, we will inline appropriately-aligned operations of this size,
      // and _Atomic(T) is appropriately-aligned.
      if (Size == CharUnits::One())
        return returnBool(true);

      // Same for null pointers.
      assert(BuiltinOp != Builtin::BI__c11_atomic_is_lock_free);
      if (Ptr.isZero())
        return returnBool(true);

      if (Ptr.isIntegralPointer()) {
        uint64_t IntVal = Ptr.getIntegerRepresentation();
        if (APSInt(APInt(64, IntVal, false), true).isAligned(Size.getAsAlign()))
          return returnBool(true);
      }

      const Expr *PtrArg = Call->getArg(1);
      // Otherwise, check if the type's alignment against Size.
      if (const auto *ICE = dyn_cast<ImplicitCastExpr>(PtrArg)) {
        // Drop the potential implicit-cast to 'const volatile void*', getting
        // the underlying type.
        if (ICE->getCastKind() == CK_BitCast)
          PtrArg = ICE->getSubExpr();
      }

      if (const auto *PtrTy = PtrArg->getType()->getAs<PointerType>()) {
        QualType PointeeType = PtrTy->getPointeeType();
        if (!PointeeType->isIncompleteType() &&
            S.getASTContext().getTypeAlignInChars(PointeeType) >= Size) {
          // OK, we will inline operations on this object.
          return returnBool(true);
        }
      }
    }
  }

  if (BuiltinOp == Builtin::BI__atomic_always_lock_free)
    return returnBool(false);

  return false;
}

/// bool __c11_atomic_is_lock_free(size_t)
static bool interp__builtin_c11_atomic_is_lock_free(InterpState &S,
                                                    CodePtr OpPC,
                                                    const InterpFrame *Frame,
                                                    const CallExpr *Call) {
  uint64_t SizeVal = popToUInt64(S, Call->getArg(0));

  CharUnits Size = CharUnits::fromQuantity(SizeVal);
  if (Size.isPowerOfTwo()) {
    // Check against inlining width.
    unsigned InlineWidthBits =
        S.getASTContext().getTargetInfo().getMaxAtomicInlineWidth();
    if (Size <= S.getASTContext().toCharUnitsFromBits(InlineWidthBits)) {
      S.Stk.push<Boolean>(true);
      return true;
    }
  }

  return false; // returnBool(false);
}

/// __builtin_complex(Float A, float B);
static bool interp__builtin_complex(InterpState &S, CodePtr OpPC,
                                    const InterpFrame *Frame,
                                    const CallExpr *Call) {
  const Floating &Arg2 = S.Stk.pop<Floating>();
  const Floating &Arg1 = S.Stk.pop<Floating>();
  Pointer &Result = S.Stk.peek<Pointer>();

  Result.elem<Floating>(0) = Arg1;
  Result.elem<Floating>(1) = Arg2;
  Result.initializeAllElements();

  return true;
}

/// __builtin_is_aligned()
/// __builtin_align_up()
/// __builtin_align_down()
/// The first parameter is either an integer or a pointer.
/// The second parameter is the requested alignment as an integer.
static bool interp__builtin_is_aligned_up_down(InterpState &S, CodePtr OpPC,
                                               const InterpFrame *Frame,
                                               const CallExpr *Call,
                                               unsigned BuiltinOp) {
  const APSInt &Alignment = popToAPSInt(S, Call->getArg(1));

  if (Alignment < 0 || !Alignment.isPowerOf2()) {
    S.FFDiag(Call, diag::note_constexpr_invalid_alignment) << Alignment;
    return false;
  }
  unsigned SrcWidth = S.getASTContext().getIntWidth(Call->getArg(0)->getType());
  APSInt MaxValue(APInt::getOneBitSet(SrcWidth, SrcWidth - 1));
  if (APSInt::compareValues(Alignment, MaxValue) > 0) {
    S.FFDiag(Call, diag::note_constexpr_alignment_too_big)
        << MaxValue << Call->getArg(0)->getType() << Alignment;
    return false;
  }

  // The first parameter is either an integer or a pointer.
  PrimType FirstArgT = *S.Ctx.classify(Call->getArg(0));

  if (isIntegralType(FirstArgT)) {
    const APSInt &Src = popToAPSInt(S.Stk, FirstArgT);
    APInt AlignMinusOne = Alignment.extOrTrunc(Src.getBitWidth()) - 1;
    if (BuiltinOp == Builtin::BI__builtin_align_up) {
      APSInt AlignedVal =
          APSInt((Src + AlignMinusOne) & ~AlignMinusOne, Src.isUnsigned());
      pushInteger(S, AlignedVal, Call->getType());
    } else if (BuiltinOp == Builtin::BI__builtin_align_down) {
      APSInt AlignedVal = APSInt(Src & ~AlignMinusOne, Src.isUnsigned());
      pushInteger(S, AlignedVal, Call->getType());
    } else {
      assert(*S.Ctx.classify(Call->getType()) == PT_Bool);
      S.Stk.push<Boolean>((Src & AlignMinusOne) == 0);
    }
    return true;
  }
  assert(FirstArgT == PT_Ptr);
  const Pointer &Ptr = S.Stk.pop<Pointer>();
  if (!Ptr.isBlockPointer())
    return false;

  unsigned PtrOffset = Ptr.getIndex();
  CharUnits BaseAlignment =
      S.getASTContext().getDeclAlign(Ptr.getDeclDesc()->asValueDecl());
  CharUnits PtrAlign =
      BaseAlignment.alignmentAtOffset(CharUnits::fromQuantity(PtrOffset));

  if (BuiltinOp == Builtin::BI__builtin_is_aligned) {
    if (PtrAlign.getQuantity() >= Alignment) {
      S.Stk.push<Boolean>(true);
      return true;
    }
    // If the alignment is not known to be sufficient, some cases could still
    // be aligned at run time. However, if the requested alignment is less or
    // equal to the base alignment and the offset is not aligned, we know that
    // the run-time value can never be aligned.
    if (BaseAlignment.getQuantity() >= Alignment &&
        PtrAlign.getQuantity() < Alignment) {
      S.Stk.push<Boolean>(false);
      return true;
    }

    S.FFDiag(Call->getArg(0), diag::note_constexpr_alignment_compute)
        << Alignment;
    return false;
  }

  assert(BuiltinOp == Builtin::BI__builtin_align_down ||
         BuiltinOp == Builtin::BI__builtin_align_up);

  // For align_up/align_down, we can return the same value if the alignment
  // is known to be greater or equal to the requested value.
  if (PtrAlign.getQuantity() >= Alignment) {
    S.Stk.push<Pointer>(Ptr);
    return true;
  }

  // The alignment could be greater than the minimum at run-time, so we cannot
  // infer much about the resulting pointer value. One case is possible:
  // For `_Alignas(32) char buf[N]; __builtin_align_down(&buf[idx], 32)` we
  // can infer the correct index if the requested alignment is smaller than
  // the base alignment so we can perform the computation on the offset.
  if (BaseAlignment.getQuantity() >= Alignment) {
    assert(Alignment.getBitWidth() <= 64 &&
           "Cannot handle > 64-bit address-space");
    uint64_t Alignment64 = Alignment.getZExtValue();
    CharUnits NewOffset =
        CharUnits::fromQuantity(BuiltinOp == Builtin::BI__builtin_align_down
                                    ? llvm::alignDown(PtrOffset, Alignment64)
                                    : llvm::alignTo(PtrOffset, Alignment64));

    S.Stk.push<Pointer>(Ptr.atIndex(NewOffset.getQuantity()));
    return true;
  }

  // Otherwise, we cannot constant-evaluate the result.
  S.FFDiag(Call->getArg(0), diag::note_constexpr_alignment_adjust) << Alignment;
  return false;
}

/// __builtin_assume_aligned(Ptr, Alignment[, ExtraOffset])
static bool interp__builtin_assume_aligned(InterpState &S, CodePtr OpPC,
                                           const InterpFrame *Frame,
                                           const CallExpr *Call) {
  assert(Call->getNumArgs() == 2 || Call->getNumArgs() == 3);

  std::optional<APSInt> ExtraOffset;
  if (Call->getNumArgs() == 3)
    ExtraOffset = popToAPSInt(S.Stk, *S.Ctx.classify(Call->getArg(2)));

  APSInt Alignment = popToAPSInt(S.Stk, *S.Ctx.classify(Call->getArg(1)));
  const Pointer &Ptr = S.Stk.pop<Pointer>();

  CharUnits Align = CharUnits::fromQuantity(Alignment.getZExtValue());

  // If there is a base object, then it must have the correct alignment.
  if (Ptr.isBlockPointer()) {
    CharUnits BaseAlignment;
    if (const auto *VD = Ptr.getDeclDesc()->asValueDecl())
      BaseAlignment = S.getASTContext().getDeclAlign(VD);
    else if (const auto *E = Ptr.getDeclDesc()->asExpr())
      BaseAlignment = GetAlignOfExpr(S.getASTContext(), E, UETT_AlignOf);

    if (BaseAlignment < Align) {
      S.CCEDiag(Call->getArg(0),
                diag::note_constexpr_baa_insufficient_alignment)
          << 0 << BaseAlignment.getQuantity() << Align.getQuantity();
      return false;
    }
  }

  APValue AV = Ptr.toAPValue(S.getASTContext());
  CharUnits AVOffset = AV.getLValueOffset();
  if (ExtraOffset)
    AVOffset -= CharUnits::fromQuantity(ExtraOffset->getZExtValue());
  if (AVOffset.alignTo(Align) != AVOffset) {
    if (Ptr.isBlockPointer())
      S.CCEDiag(Call->getArg(0),
                diag::note_constexpr_baa_insufficient_alignment)
          << 1 << AVOffset.getQuantity() << Align.getQuantity();
    else
      S.CCEDiag(Call->getArg(0),
                diag::note_constexpr_baa_value_insufficient_alignment)
          << AVOffset.getQuantity() << Align.getQuantity();
    return false;
  }

  S.Stk.push<Pointer>(Ptr);
  return true;
}

/// (CarryIn, LHS, RHS, Result)
static bool interp__builtin_ia32_addcarry_subborrow(InterpState &S,
                                                    CodePtr OpPC,
                                                    const InterpFrame *Frame,
                                                    const CallExpr *Call,
                                                    unsigned BuiltinOp) {
  if (Call->getNumArgs() != 4 || !Call->getArg(0)->getType()->isIntegerType() ||
      !Call->getArg(1)->getType()->isIntegerType() ||
      !Call->getArg(2)->getType()->isIntegerType())
    return false;

  const Pointer &CarryOutPtr = S.Stk.pop<Pointer>();

  APSInt RHS = popToAPSInt(S, Call->getArg(2));
  APSInt LHS = popToAPSInt(S, Call->getArg(1));
  APSInt CarryIn = popToAPSInt(S, Call->getArg(0));

  bool IsAdd = BuiltinOp == clang::X86::BI__builtin_ia32_addcarryx_u32 ||
               BuiltinOp == clang::X86::BI__builtin_ia32_addcarryx_u64;

  unsigned BitWidth = LHS.getBitWidth();
  unsigned CarryInBit = CarryIn.ugt(0) ? 1 : 0;
  APInt ExResult =
      IsAdd ? (LHS.zext(BitWidth + 1) + (RHS.zext(BitWidth + 1) + CarryInBit))
            : (LHS.zext(BitWidth + 1) - (RHS.zext(BitWidth + 1) + CarryInBit));

  APInt Result = ExResult.extractBits(BitWidth, 0);
  APSInt CarryOut =
      APSInt(ExResult.extractBits(1, BitWidth), /*IsUnsigned=*/true);

  QualType CarryOutType = Call->getArg(3)->getType()->getPointeeType();
  PrimType CarryOutT = *S.getContext().classify(CarryOutType);
  assignInteger(S, CarryOutPtr, CarryOutT, APSInt(std::move(Result), true));

  pushInteger(S, CarryOut, Call->getType());

  return true;
}

static bool interp__builtin_os_log_format_buffer_size(InterpState &S,
                                                      CodePtr OpPC,
                                                      const InterpFrame *Frame,
                                                      const CallExpr *Call) {
  analyze_os_log::OSLogBufferLayout Layout;
  analyze_os_log::computeOSLogBufferLayout(S.getASTContext(), Call, Layout);
  pushInteger(S, Layout.size().getQuantity(), Call->getType());
  return true;
}

static bool
interp__builtin_ptrauth_string_discriminator(InterpState &S, CodePtr OpPC,
                                             const InterpFrame *Frame,
                                             const CallExpr *Call) {
  const auto &Ptr = S.Stk.pop<Pointer>();
  assert(Ptr.getFieldDesc()->isPrimitiveArray());

  // This should be created for a StringLiteral, so should alway shold at least
  // one array element.
  assert(Ptr.getFieldDesc()->getNumElems() >= 1);
  StringRef R(&Ptr.deref<char>(), Ptr.getFieldDesc()->getNumElems() - 1);
  uint64_t Result = getPointerAuthStableSipHash(R);
  pushInteger(S, Result, Call->getType());
  return true;
}

static bool interp__builtin_infer_alloc_token(InterpState &S, CodePtr OpPC,
                                              const InterpFrame *Frame,
                                              const CallExpr *Call) {
  const ASTContext &ASTCtx = S.getASTContext();
  uint64_t BitWidth = ASTCtx.getTypeSize(ASTCtx.getSizeType());
  auto Mode =
      ASTCtx.getLangOpts().AllocTokenMode.value_or(llvm::DefaultAllocTokenMode);
  auto MaxTokensOpt = ASTCtx.getLangOpts().AllocTokenMax;
  uint64_t MaxTokens =
      MaxTokensOpt.value_or(0) ? *MaxTokensOpt : (~0ULL >> (64 - BitWidth));

  // We do not read any of the arguments; discard them.
  for (int I = Call->getNumArgs() - 1; I >= 0; --I)
    discard(S.Stk, *S.getContext().classify(Call->getArg(I)));

  // Note: Type inference from a surrounding cast is not supported in
  // constexpr evaluation.
  QualType AllocType = infer_alloc::inferPossibleType(Call, ASTCtx, nullptr);
  if (AllocType.isNull()) {
    S.CCEDiag(Call,
              diag::note_constexpr_infer_alloc_token_type_inference_failed);
    return false;
  }

  auto ATMD = infer_alloc::getAllocTokenMetadata(AllocType, ASTCtx);
  if (!ATMD) {
    S.CCEDiag(Call, diag::note_constexpr_infer_alloc_token_no_metadata);
    return false;
  }

  auto MaybeToken = llvm::getAllocToken(Mode, *ATMD, MaxTokens);
  if (!MaybeToken) {
    S.CCEDiag(Call, diag::note_constexpr_infer_alloc_token_stateful_mode);
    return false;
  }

  pushInteger(S, llvm::APInt(BitWidth, *MaybeToken), ASTCtx.getSizeType());
  return true;
}

static bool interp__builtin_operator_new(InterpState &S, CodePtr OpPC,
                                         const InterpFrame *Frame,
                                         const CallExpr *Call) {
  // A call to __operator_new is only valid within std::allocate<>::allocate.
  // Walk up the call stack to find the appropriate caller and get the
  // element type from it.
  auto [NewCall, ElemType] = S.getStdAllocatorCaller("allocate");

  if (ElemType.isNull()) {
    S.FFDiag(Call, S.getLangOpts().CPlusPlus20
                       ? diag::note_constexpr_new_untyped
                       : diag::note_constexpr_new);
    return false;
  }
  assert(NewCall);

  if (ElemType->isIncompleteType() || ElemType->isFunctionType()) {
    S.FFDiag(Call, diag::note_constexpr_new_not_complete_object_type)
        << (ElemType->isIncompleteType() ? 0 : 1) << ElemType;
    return false;
  }

  // We only care about the first parameter (the size), so discard all the
  // others.
  {
    unsigned NumArgs = Call->getNumArgs();
    assert(NumArgs >= 1);

    // The std::nothrow_t arg never gets put on the stack.
    if (Call->getArg(NumArgs - 1)->getType()->isNothrowT())
      --NumArgs;
    auto Args = ArrayRef(Call->getArgs(), Call->getNumArgs());
    // First arg is needed.
    Args = Args.drop_front();

    // Discard the rest.
    for (const Expr *Arg : Args)
      discard(S.Stk, *S.getContext().classify(Arg));
  }

  APSInt Bytes = popToAPSInt(S, Call->getArg(0));
  CharUnits ElemSize = S.getASTContext().getTypeSizeInChars(ElemType);
  assert(!ElemSize.isZero());
  // Divide the number of bytes by sizeof(ElemType), so we get the number of
  // elements we should allocate.
  APInt NumElems, Remainder;
  APInt ElemSizeAP(Bytes.getBitWidth(), ElemSize.getQuantity());
  APInt::udivrem(Bytes, ElemSizeAP, NumElems, Remainder);
  if (Remainder != 0) {
    // This likely indicates a bug in the implementation of 'std::allocator'.
    S.FFDiag(Call, diag::note_constexpr_operator_new_bad_size)
        << Bytes << APSInt(ElemSizeAP, true) << ElemType;
    return false;
  }

  // NB: The same check we're using in CheckArraySize()
  if (NumElems.getActiveBits() >
          ConstantArrayType::getMaxSizeBits(S.getASTContext()) ||
      NumElems.ugt(Descriptor::MaxArrayElemBytes / ElemSize.getQuantity())) {
    // FIXME: NoThrow check?
    const SourceInfo &Loc = S.Current->getSource(OpPC);
    S.FFDiag(Loc, diag::note_constexpr_new_too_large)
        << NumElems.getZExtValue();
    return false;
  }

  if (!CheckArraySize(S, OpPC, NumElems.getZExtValue()))
    return false;

  bool IsArray = NumElems.ugt(1);
  OptPrimType ElemT = S.getContext().classify(ElemType);
  DynamicAllocator &Allocator = S.getAllocator();
  if (ElemT) {
    Block *B =
        Allocator.allocate(NewCall, *ElemT, NumElems.getZExtValue(),
                           S.Ctx.getEvalID(), DynamicAllocator::Form::Operator);
    assert(B);
    S.Stk.push<Pointer>(Pointer(B).atIndex(0));
    return true;
  }

  assert(!ElemT);

  // Composite arrays
  if (IsArray) {
    const Descriptor *Desc =
        S.P.createDescriptor(NewCall, ElemType.getTypePtr(), std::nullopt);
    Block *B =
        Allocator.allocate(Desc, NumElems.getZExtValue(), S.Ctx.getEvalID(),
                           DynamicAllocator::Form::Operator);
    assert(B);
    S.Stk.push<Pointer>(Pointer(B).atIndex(0).narrow());
    return true;
  }

  // Records. Still allocate them as single-element arrays.
  QualType AllocType = S.getASTContext().getConstantArrayType(
      ElemType, NumElems, nullptr, ArraySizeModifier::Normal, 0);

  const Descriptor *Desc = S.P.createDescriptor(NewCall, AllocType.getTypePtr(),
                                                Descriptor::InlineDescMD);
  Block *B = Allocator.allocate(Desc, S.getContext().getEvalID(),
                                DynamicAllocator::Form::Operator);
  assert(B);
  S.Stk.push<Pointer>(Pointer(B).atIndex(0).narrow());
  return true;
}

static bool interp__builtin_operator_delete(InterpState &S, CodePtr OpPC,
                                            const InterpFrame *Frame,
                                            const CallExpr *Call) {
  const Expr *Source = nullptr;
  const Block *BlockToDelete = nullptr;

  if (S.checkingPotentialConstantExpression()) {
    S.Stk.discard<Pointer>();
    return false;
  }

  // This is permitted only within a call to std::allocator<T>::deallocate.
  if (!S.getStdAllocatorCaller("deallocate")) {
    S.FFDiag(Call);
    S.Stk.discard<Pointer>();
    return true;
  }

  {
    const Pointer &Ptr = S.Stk.pop<Pointer>();

    if (Ptr.isZero()) {
      S.CCEDiag(Call, diag::note_constexpr_deallocate_null);
      return true;
    }

    Source = Ptr.getDeclDesc()->asExpr();
    BlockToDelete = Ptr.block();

    if (!BlockToDelete->isDynamic()) {
      S.FFDiag(Call, diag::note_constexpr_delete_not_heap_alloc)
          << Ptr.toDiagnosticString(S.getASTContext());
      if (const auto *D = Ptr.getFieldDesc()->asDecl())
        S.Note(D->getLocation(), diag::note_declared_at);
    }
  }
  assert(BlockToDelete);

  DynamicAllocator &Allocator = S.getAllocator();
  const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
  std::optional<DynamicAllocator::Form> AllocForm =
      Allocator.getAllocationForm(Source);

  if (!Allocator.deallocate(Source, BlockToDelete, S)) {
    // Nothing has been deallocated, this must be a double-delete.
    const SourceInfo &Loc = S.Current->getSource(OpPC);
    S.FFDiag(Loc, diag::note_constexpr_double_delete);
    return false;
  }
  assert(AllocForm);

  return CheckNewDeleteForms(
      S, OpPC, *AllocForm, DynamicAllocator::Form::Operator, BlockDesc, Source);
}

static bool interp__builtin_arithmetic_fence(InterpState &S, CodePtr OpPC,
                                             const InterpFrame *Frame,
                                             const CallExpr *Call) {
  const Floating &Arg0 = S.Stk.pop<Floating>();
  S.Stk.push<Floating>(Arg0);
  return true;
}

static bool interp__builtin_vector_reduce(InterpState &S, CodePtr OpPC,
                                          const CallExpr *Call, unsigned ID) {
  const Pointer &Arg = S.Stk.pop<Pointer>();
  assert(Arg.getFieldDesc()->isPrimitiveArray());

  QualType ElemType = Arg.getFieldDesc()->getElemQualType();
  assert(Call->getType() == ElemType);
  PrimType ElemT = *S.getContext().classify(ElemType);
  unsigned NumElems = Arg.getNumElems();

  INT_TYPE_SWITCH_NO_BOOL(ElemT, {
    T Result = Arg.elem<T>(0);
    unsigned BitWidth = Result.bitWidth();
    for (unsigned I = 1; I != NumElems; ++I) {
      T Elem = Arg.elem<T>(I);
      T PrevResult = Result;

      if (ID == Builtin::BI__builtin_reduce_add) {
        if (T::add(Result, Elem, BitWidth, &Result)) {
          unsigned OverflowBits = BitWidth + 1;
          (void)handleOverflow(S, OpPC,
                               (PrevResult.toAPSInt(OverflowBits) +
                                Elem.toAPSInt(OverflowBits)));
          return false;
        }
      } else if (ID == Builtin::BI__builtin_reduce_mul) {
        if (T::mul(Result, Elem, BitWidth, &Result)) {
          unsigned OverflowBits = BitWidth * 2;
          (void)handleOverflow(S, OpPC,
                               (PrevResult.toAPSInt(OverflowBits) *
                                Elem.toAPSInt(OverflowBits)));
          return false;
        }

      } else if (ID == Builtin::BI__builtin_reduce_and) {
        (void)T::bitAnd(Result, Elem, BitWidth, &Result);
      } else if (ID == Builtin::BI__builtin_reduce_or) {
        (void)T::bitOr(Result, Elem, BitWidth, &Result);
      } else if (ID == Builtin::BI__builtin_reduce_xor) {
        (void)T::bitXor(Result, Elem, BitWidth, &Result);
      } else if (ID == Builtin::BI__builtin_reduce_min) {
        if (Elem < Result)
          Result = Elem;
      } else if (ID == Builtin::BI__builtin_reduce_max) {
        if (Elem > Result)
          Result = Elem;
      } else {
        llvm_unreachable("Unhandled vector reduce builtin");
      }
    }
    pushInteger(S, Result.toAPSInt(), Call->getType());
  });

  return true;
}

static bool interp__builtin_elementwise_abs(InterpState &S, CodePtr OpPC,
                                            const InterpFrame *Frame,
                                            const CallExpr *Call,
                                            unsigned BuiltinID) {
  assert(Call->getNumArgs() == 1);
  QualType Ty = Call->getArg(0)->getType();
  if (Ty->isIntegerType()) {
    APSInt Val = popToAPSInt(S, Call->getArg(0));
    pushInteger(S, Val.abs(), Call->getType());
    return true;
  }

  if (Ty->isFloatingType()) {
    Floating Val = S.Stk.pop<Floating>();
    Floating Result = abs(S, Val);
    S.Stk.push<Floating>(Result);
    return true;
  }

  // Otherwise, the argument must be a vector.
  assert(Call->getArg(0)->getType()->isVectorType());
  const Pointer &Arg = S.Stk.pop<Pointer>();
  assert(Arg.getFieldDesc()->isPrimitiveArray());
  const Pointer &Dst = S.Stk.peek<Pointer>();
  assert(Dst.getFieldDesc()->isPrimitiveArray());
  assert(Arg.getFieldDesc()->getNumElems() ==
         Dst.getFieldDesc()->getNumElems());

  QualType ElemType = Arg.getFieldDesc()->getElemQualType();
  PrimType ElemT = *S.getContext().classify(ElemType);
  unsigned NumElems = Arg.getNumElems();
  // we can either have a vector of integer or a vector of floating point
  for (unsigned I = 0; I != NumElems; ++I) {
    if (ElemType->isIntegerType()) {
      INT_TYPE_SWITCH_NO_BOOL(ElemT, {
        Dst.elem<T>(I) = T::from(static_cast<T>(
            APSInt(Arg.elem<T>(I).toAPSInt().abs(),
                   ElemType->isUnsignedIntegerOrEnumerationType())));
      });
    } else {
      Floating Val = Arg.elem<Floating>(I);
      Dst.elem<Floating>(I) = abs(S, Val);
    }
  }
  Dst.initializeAllElements();

  return true;
}

/// Can be called with an integer or vector as the first and only parameter.
static bool interp__builtin_elementwise_countzeroes(InterpState &S,
                                                    CodePtr OpPC,
                                                    const InterpFrame *Frame,
                                                    const CallExpr *Call,
                                                    unsigned BuiltinID) {
  bool HasZeroArg = Call->getNumArgs() == 2;
  bool IsCTTZ = BuiltinID == Builtin::BI__builtin_elementwise_ctzg;
  assert(Call->getNumArgs() == 1 || HasZeroArg);
  if (Call->getArg(0)->getType()->isIntegerType()) {
    PrimType ArgT = *S.getContext().classify(Call->getArg(0)->getType());
    APSInt Val = popToAPSInt(S.Stk, ArgT);
    std::optional<APSInt> ZeroVal;
    if (HasZeroArg) {
      ZeroVal = Val;
      Val = popToAPSInt(S.Stk, ArgT);
    }

    if (Val.isZero()) {
      if (ZeroVal) {
        pushInteger(S, *ZeroVal, Call->getType());
        return true;
      }
      // If we haven't been provided the second argument, the result is
      // undefined
      S.FFDiag(S.Current->getSource(OpPC),
               diag::note_constexpr_countzeroes_zero)
          << /*IsTrailing=*/IsCTTZ;
      return false;
    }

    if (BuiltinID == Builtin::BI__builtin_elementwise_clzg) {
      pushInteger(S, Val.countLeadingZeros(), Call->getType());
    } else {
      pushInteger(S, Val.countTrailingZeros(), Call->getType());
    }
    return true;
  }
  // Otherwise, the argument must be a vector.
  const ASTContext &ASTCtx = S.getASTContext();
  Pointer ZeroArg;
  if (HasZeroArg) {
    assert(Call->getArg(1)->getType()->isVectorType() &&
           ASTCtx.hasSameUnqualifiedType(Call->getArg(0)->getType(),
                                         Call->getArg(1)->getType()));
    (void)ASTCtx;
    ZeroArg = S.Stk.pop<Pointer>();
    assert(ZeroArg.getFieldDesc()->isPrimitiveArray());
  }
  assert(Call->getArg(0)->getType()->isVectorType());
  const Pointer &Arg = S.Stk.pop<Pointer>();
  assert(Arg.getFieldDesc()->isPrimitiveArray());
  const Pointer &Dst = S.Stk.peek<Pointer>();
  assert(Dst.getFieldDesc()->isPrimitiveArray());
  assert(Arg.getFieldDesc()->getNumElems() ==
         Dst.getFieldDesc()->getNumElems());

  QualType ElemType = Arg.getFieldDesc()->getElemQualType();
  PrimType ElemT = *S.getContext().classify(ElemType);
  unsigned NumElems = Arg.getNumElems();

  // FIXME: Reading from uninitialized vector elements?
  for (unsigned I = 0; I != NumElems; ++I) {
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      APInt EltVal = Arg.atIndex(I).deref<T>().toAPSInt();
      if (EltVal.isZero()) {
        if (HasZeroArg) {
          Dst.atIndex(I).deref<T>() = ZeroArg.atIndex(I).deref<T>();
        } else {
          // If we haven't been provided the second argument, the result is
          // undefined
          S.FFDiag(S.Current->getSource(OpPC),
                   diag::note_constexpr_countzeroes_zero)
              << /*IsTrailing=*/IsCTTZ;
          return false;
        }
      } else if (IsCTTZ) {
        Dst.atIndex(I).deref<T>() = T::from(EltVal.countTrailingZeros());
      } else {
        Dst.atIndex(I).deref<T>() = T::from(EltVal.countLeadingZeros());
      }
      Dst.atIndex(I).initialize();
    });
  }

  return true;
}

static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call, unsigned ID) {
  assert(Call->getNumArgs() == 3);
  const ASTContext &ASTCtx = S.getASTContext();
  uint64_t Size = popToUInt64(S, Call->getArg(2));
  Pointer SrcPtr = S.Stk.pop<Pointer>().expand();
  Pointer DestPtr = S.Stk.pop<Pointer>().expand();

  if (ID == Builtin::BImemcpy || ID == Builtin::BImemmove)
    diagnoseNonConstexprBuiltin(S, OpPC, ID);

  bool Move =
      (ID == Builtin::BI__builtin_memmove || ID == Builtin::BImemmove ||
       ID == Builtin::BI__builtin_wmemmove || ID == Builtin::BIwmemmove);
  bool WChar = ID == Builtin::BIwmemcpy || ID == Builtin::BIwmemmove ||
               ID == Builtin::BI__builtin_wmemcpy ||
               ID == Builtin::BI__builtin_wmemmove;

  // If the size is zero, we treat this as always being a valid no-op.
  if (Size == 0) {
    S.Stk.push<Pointer>(DestPtr);
    return true;
  }

  if (SrcPtr.isZero() || DestPtr.isZero()) {
    Pointer DiagPtr = (SrcPtr.isZero() ? SrcPtr : DestPtr);
    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_memcpy_null)
        << /*IsMove=*/Move << /*IsWchar=*/WChar << !SrcPtr.isZero()
        << DiagPtr.toDiagnosticString(ASTCtx);
    return false;
  }

  // Diagnose integral src/dest pointers specially.
  if (SrcPtr.isIntegralPointer() || DestPtr.isIntegralPointer()) {
    std::string DiagVal = "(void *)";
    DiagVal += SrcPtr.isIntegralPointer()
                   ? std::to_string(SrcPtr.getIntegerRepresentation())
                   : std::to_string(DestPtr.getIntegerRepresentation());
    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_memcpy_null)
        << Move << WChar << DestPtr.isIntegralPointer() << DiagVal;
    return false;
  }

  if (!isReadable(DestPtr) || !isReadable(SrcPtr))
    return false;

  if (DestPtr.getType()->isIncompleteType()) {
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memcpy_incomplete_type)
        << Move << DestPtr.getType();
    return false;
  }
  if (SrcPtr.getType()->isIncompleteType()) {
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memcpy_incomplete_type)
        << Move << SrcPtr.getType();
    return false;
  }

  QualType DestElemType = getElemType(DestPtr);
  if (DestElemType->isIncompleteType()) {
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memcpy_incomplete_type)
        << Move << DestElemType;
    return false;
  }

  size_t RemainingDestElems;
  if (DestPtr.getFieldDesc()->isArray()) {
    RemainingDestElems = DestPtr.isUnknownSizeArray()
                             ? 0
                             : (DestPtr.getNumElems() - DestPtr.getIndex());
  } else {
    RemainingDestElems = 1;
  }
  unsigned DestElemSize = ASTCtx.getTypeSizeInChars(DestElemType).getQuantity();

  if (WChar) {
    uint64_t WCharSize =
        ASTCtx.getTypeSizeInChars(ASTCtx.getWCharType()).getQuantity();
    Size *= WCharSize;
  }

  if (Size % DestElemSize != 0) {
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memcpy_unsupported)
        << Move << WChar << 0 << DestElemType << Size << DestElemSize;
    return false;
  }

  QualType SrcElemType = getElemType(SrcPtr);
  size_t RemainingSrcElems;
  if (SrcPtr.getFieldDesc()->isArray()) {
    RemainingSrcElems = SrcPtr.isUnknownSizeArray()
                            ? 0
                            : (SrcPtr.getNumElems() - SrcPtr.getIndex());
  } else {
    RemainingSrcElems = 1;
  }
  unsigned SrcElemSize = ASTCtx.getTypeSizeInChars(SrcElemType).getQuantity();

  if (!ASTCtx.hasSameUnqualifiedType(DestElemType, SrcElemType)) {
    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_memcpy_type_pun)
        << Move << SrcElemType << DestElemType;
    return false;
  }

  if (!DestElemType.isTriviallyCopyableType(ASTCtx)) {
    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_memcpy_nontrivial)
        << Move << DestElemType;
    return false;
  }

  // Check if we have enough elements to read from and write to.
  size_t RemainingDestBytes = RemainingDestElems * DestElemSize;
  size_t RemainingSrcBytes = RemainingSrcElems * SrcElemSize;
  if (Size > RemainingDestBytes || Size > RemainingSrcBytes) {
    APInt N = APInt(64, Size / DestElemSize);
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memcpy_unsupported)
        << Move << WChar << (Size > RemainingSrcBytes ? 1 : 2) << DestElemType
        << toString(N, 10, /*Signed=*/false);
    return false;
  }

  // Check for overlapping memory regions.
  if (!Move && Pointer::pointToSameBlock(SrcPtr, DestPtr)) {
    // Remove base casts.
    Pointer SrcP = SrcPtr;
    while (SrcP.isBaseClass())
      SrcP = SrcP.getBase();

    Pointer DestP = DestPtr;
    while (DestP.isBaseClass())
      DestP = DestP.getBase();

    unsigned SrcIndex = SrcP.expand().getIndex() * SrcP.elemSize();
    unsigned DstIndex = DestP.expand().getIndex() * DestP.elemSize();

    if ((SrcIndex <= DstIndex && (SrcIndex + Size) > DstIndex) ||
        (DstIndex <= SrcIndex && (DstIndex + Size) > SrcIndex)) {
      S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_memcpy_overlap)
          << /*IsWChar=*/false;
      return false;
    }
  }

  assert(Size % DestElemSize == 0);
  if (!DoMemcpy(S, OpPC, SrcPtr, DestPtr, Bytes(Size).toBits()))
    return false;

  S.Stk.push<Pointer>(DestPtr);
  return true;
}

/// Determine if T is a character type for which we guarantee that
/// sizeof(T) == 1.
static bool isOneByteCharacterType(QualType T) {
  return T->isCharType() || T->isChar8Type();
}

static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
                                   const InterpFrame *Frame,
                                   const CallExpr *Call, unsigned ID) {
  assert(Call->getNumArgs() == 3);
  uint64_t Size = popToUInt64(S, Call->getArg(2));
  const Pointer &PtrB = S.Stk.pop<Pointer>();
  const Pointer &PtrA = S.Stk.pop<Pointer>();

  if (ID == Builtin::BImemcmp || ID == Builtin::BIbcmp ||
      ID == Builtin::BIwmemcmp)
    diagnoseNonConstexprBuiltin(S, OpPC, ID);

  if (Size == 0) {
    pushInteger(S, 0, Call->getType());
    return true;
  }

  if (!PtrA.isBlockPointer() || !PtrB.isBlockPointer())
    return false;

  bool IsWide =
      (ID == Builtin::BIwmemcmp || ID == Builtin::BI__builtin_wmemcmp);

  const ASTContext &ASTCtx = S.getASTContext();
  QualType ElemTypeA = getElemType(PtrA);
  QualType ElemTypeB = getElemType(PtrB);
  // FIXME: This is an arbitrary limitation the current constant interpreter
  // had. We could remove this.
  if (!IsWide && (!isOneByteCharacterType(ElemTypeA) ||
                  !isOneByteCharacterType(ElemTypeB))) {
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memcmp_unsupported)
        << ASTCtx.BuiltinInfo.getQuotedName(ID) << PtrA.getType()
        << PtrB.getType();
    return false;
  }

  if (PtrA.isDummy() || PtrB.isDummy())
    return false;

  if (!CheckRange(S, OpPC, PtrA, AK_Read) ||
      !CheckRange(S, OpPC, PtrB, AK_Read))
    return false;

  // Now, read both pointers to a buffer and compare those.
  BitcastBuffer BufferA(
      Bits(ASTCtx.getTypeSize(ElemTypeA) * PtrA.getNumElems()));
  readPointerToBuffer(S.getContext(), PtrA, BufferA, false);
  // FIXME: The swapping here is UNDOING something we do when reading the
  // data into the buffer.
  if (ASTCtx.getTargetInfo().isBigEndian())
    swapBytes(BufferA.Data.get(), BufferA.byteSize().getQuantity());

  BitcastBuffer BufferB(
      Bits(ASTCtx.getTypeSize(ElemTypeB) * PtrB.getNumElems()));
  readPointerToBuffer(S.getContext(), PtrB, BufferB, false);
  // FIXME: The swapping here is UNDOING something we do when reading the
  // data into the buffer.
  if (ASTCtx.getTargetInfo().isBigEndian())
    swapBytes(BufferB.Data.get(), BufferB.byteSize().getQuantity());

  size_t MinBufferSize = std::min(BufferA.byteSize().getQuantity(),
                                  BufferB.byteSize().getQuantity());

  unsigned ElemSize = 1;
  if (IsWide)
    ElemSize = ASTCtx.getTypeSizeInChars(ASTCtx.getWCharType()).getQuantity();
  // The Size given for the wide variants is in wide-char units. Convert it
  // to bytes.
  size_t ByteSize = Size * ElemSize;
  size_t CmpSize = std::min(MinBufferSize, ByteSize);

  for (size_t I = 0; I != CmpSize; I += ElemSize) {
    if (IsWide) {
      INT_TYPE_SWITCH(*S.getContext().classify(ASTCtx.getWCharType()), {
        T A = *reinterpret_cast<T *>(BufferA.atByte(I));
        T B = *reinterpret_cast<T *>(BufferB.atByte(I));
        if (A < B) {
          pushInteger(S, -1, Call->getType());
          return true;
        }
        if (A > B) {
          pushInteger(S, 1, Call->getType());
          return true;
        }
      });
    } else {
      std::byte A = BufferA.deref<std::byte>(Bytes(I));
      std::byte B = BufferB.deref<std::byte>(Bytes(I));

      if (A < B) {
        pushInteger(S, -1, Call->getType());
        return true;
      }
      if (A > B) {
        pushInteger(S, 1, Call->getType());
        return true;
      }
    }
  }

  // We compared CmpSize bytes above. If the limiting factor was the Size
  // passed, we're done and the result is equality (0).
  if (ByteSize <= CmpSize) {
    pushInteger(S, 0, Call->getType());
    return true;
  }

  // However, if we read all the available bytes but were instructed to read
  // even more, diagnose this as a "read of dereferenced one-past-the-end
  // pointer". This is what would happen if we called CheckLoad() on every array
  // element.
  S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_past_end)
      << AK_Read << S.Current->getRange(OpPC);
  return false;
}

// __builtin_memchr(ptr, int, int)
// __builtin_strchr(ptr, int)
static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
                                   const CallExpr *Call, unsigned ID) {
  if (ID == Builtin::BImemchr || ID == Builtin::BIwcschr ||
      ID == Builtin::BIstrchr || ID == Builtin::BIwmemchr)
    diagnoseNonConstexprBuiltin(S, OpPC, ID);

  std::optional<APSInt> MaxLength;
  if (Call->getNumArgs() == 3)
    MaxLength = popToAPSInt(S, Call->getArg(2));

  APSInt Desired = popToAPSInt(S, Call->getArg(1));
  const Pointer &Ptr = S.Stk.pop<Pointer>();

  if (MaxLength && MaxLength->isZero()) {
    S.Stk.push<Pointer>();
    return true;
  }

  if (Ptr.isDummy()) {
    if (Ptr.getType()->isIncompleteType())
      S.FFDiag(S.Current->getSource(OpPC),
               diag::note_constexpr_ltor_incomplete_type)
          << Ptr.getType();
    return false;
  }

  // Null is only okay if the given size is 0.
  if (Ptr.isZero()) {
    S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_null)
        << AK_Read;
    return false;
  }

  if (!Ptr.isBlockPointer())
    return false;

  QualType ElemTy = Ptr.getFieldDesc()->isArray()
                        ? Ptr.getFieldDesc()->getElemQualType()
                        : Ptr.getFieldDesc()->getType();
  bool IsRawByte = ID == Builtin::BImemchr || ID == Builtin::BI__builtin_memchr;

  // Give up on byte-oriented matching against multibyte elements.
  if (IsRawByte && !isOneByteCharacterType(ElemTy)) {
    S.FFDiag(S.Current->getSource(OpPC),
             diag::note_constexpr_memchr_unsupported)
        << S.getASTContext().BuiltinInfo.getQuotedName(ID) << ElemTy;
    return false;
  }

  if (!isReadable(Ptr))
    return false;

  if (ID == Builtin::BIstrchr || ID == Builtin::BI__builtin_strchr) {
    int64_t DesiredTrunc;
    if (S.getASTContext().CharTy->isSignedIntegerType())
      DesiredTrunc =
          Desired.trunc(S.getASTContext().getCharWidth()).getSExtValue();
    else
      DesiredTrunc =
          Desired.trunc(S.getASTContext().getCharWidth()).getZExtValue();
    // strchr compares directly to the passed integer, and therefore
    // always fails if given an int that is not a char.
    if (Desired != DesiredTrunc) {
      S.Stk.push<Pointer>();
      return true;
    }
  }

  uint64_t DesiredVal;
  if (ID == Builtin::BIwmemchr || ID == Builtin::BI__builtin_wmemchr ||
      ID == Builtin::BIwcschr || ID == Builtin::BI__builtin_wcschr) {
    // wcschr and wmemchr are given a wchar_t to look for. Just use it.
    DesiredVal = Desired.getZExtValue();
  } else {
    DesiredVal = Desired.trunc(S.getASTContext().getCharWidth()).getZExtValue();
  }

  bool StopAtZero =
      (ID == Builtin::BIstrchr || ID == Builtin::BI__builtin_strchr ||
       ID == Builtin::BIwcschr || ID == Builtin::BI__builtin_wcschr);

  PrimType ElemT =
      IsRawByte ? PT_Sint8 : *S.getContext().classify(getElemType(Ptr));

  size_t Index = Ptr.getIndex();
  size_t Step = 0;
  for (;;) {
    const Pointer &ElemPtr =
        (Index + Step) > 0 ? Ptr.atIndex(Index + Step) : Ptr;

    if (!CheckLoad(S, OpPC, ElemPtr))
      return false;

    uint64_t V;
    INT_TYPE_SWITCH_NO_BOOL(
        ElemT, { V = static_cast<uint64_t>(ElemPtr.deref<T>().toUnsigned()); });

    if (V == DesiredVal) {
      S.Stk.push<Pointer>(ElemPtr);
      return true;
    }

    if (StopAtZero && V == 0)
      break;

    ++Step;
    if (MaxLength && Step == MaxLength->getZExtValue())
      break;
  }

  S.Stk.push<Pointer>();
  return true;
}

static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
                                                   const Descriptor *Desc) {
  if (Desc->isPrimitive())
    return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
  if (Desc->isArray())
    return ASTCtx.getTypeSizeInChars(Desc->getElemQualType()).getQuantity() *
           Desc->getNumElems();
  if (Desc->isRecord()) {
    // Can't use Descriptor::getType() as that may return a pointer type. Look
    // at the decl directly.
    return ASTCtx
        .getTypeSizeInChars(
            ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl()))
        .getQuantity();
  }

  return std::nullopt;
}

/// Compute the byte offset of \p Ptr in the full declaration.
static unsigned computePointerOffset(const ASTContext &ASTCtx,
                                     const Pointer &Ptr) {
  unsigned Result = 0;

  Pointer P = Ptr;
  while (P.isField() || P.isArrayElement()) {
    P = P.expand();
    const Descriptor *D = P.getFieldDesc();

    if (P.isArrayElement()) {
      unsigned ElemSize =
          ASTCtx.getTypeSizeInChars(D->getElemQualType()).getQuantity();
      if (P.isOnePastEnd())
        Result += ElemSize * P.getNumElems();
      else
        Result += ElemSize * P.getIndex();
      P = P.expand().getArray();
    } else if (P.isBaseClass()) {
      const auto *RD = cast<CXXRecordDecl>(D->asDecl());
      bool IsVirtual = Ptr.isVirtualBaseClass();
      P = P.getBase();
      const Record *BaseRecord = P.getRecord();

      const ASTRecordLayout &Layout =
          ASTCtx.getASTRecordLayout(cast<CXXRecordDecl>(BaseRecord->getDecl()));
      if (IsVirtual)
        Result += Layout.getVBaseClassOffset(RD).getQuantity();
      else
        Result += Layout.getBaseClassOffset(RD).getQuantity();
    } else if (P.isField()) {
      const FieldDecl *FD = P.getField();
      const ASTRecordLayout &Layout =
          ASTCtx.getASTRecordLayout(FD->getParent());
      unsigned FieldIndex = FD->getFieldIndex();
      uint64_t FieldOffset =
          ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex))
              .getQuantity();
      Result += FieldOffset;
      P = P.getBase();
    } else
      llvm_unreachable("Unhandled descriptor type");
  }

  return Result;
}

/// Does Ptr point to the last subobject?
static bool pointsToLastObject(const Pointer &Ptr) {
  Pointer P = Ptr;
  while (!P.isRoot()) {

    if (P.isArrayElement()) {
      P = P.expand().getArray();
      continue;
    }
    if (P.isBaseClass()) {
      if (P.getRecord()->getNumFields() > 0)
        return false;
      P = P.getBase();
      continue;
    }

    Pointer Base = P.getBase();
    if (const Record *R = Base.getRecord()) {
      assert(P.getField());
      if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
        return false;
    }
    P = Base;
  }

  return true;
}

/// Does Ptr point to the last object AND to a flexible array member?
static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr) {
  auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
    using FAMKind = LangOptions::StrictFlexArraysLevelKind;
    FAMKind StrictFlexArraysLevel =
        Ctx.getLangOpts().getStrictFlexArraysLevel();

    if (StrictFlexArraysLevel == FAMKind::Default)
      return true;

    unsigned NumElems = FieldDesc->getNumElems();
    if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
      return true;

    if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
      return true;
    return false;
  };

  const Descriptor *FieldDesc = Ptr.getFieldDesc();
  if (!FieldDesc->isArray())
    return false;

  return Ptr.isDummy() && pointsToLastObject(Ptr) &&
         isFlexibleArrayMember(FieldDesc);
}

static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
                                        const InterpFrame *Frame,
                                        const CallExpr *Call) {
  const ASTContext &ASTCtx = S.getASTContext();
  // From the GCC docs:
  // Kind is an integer constant from 0 to 3. If the least significant bit is
  // clear, objects are whole variables. If it is set, a closest surrounding
  // subobject is considered the object a pointer points to. The second bit
  // determines if maximum or minimum of remaining bytes is computed.
  unsigned Kind = popToUInt64(S, Call->getArg(1));
  assert(Kind <= 3 && "unexpected kind");
  bool UseFieldDesc = (Kind & 1u);
  bool ReportMinimum = (Kind & 2u);
  const Pointer &Ptr = S.Stk.pop<Pointer>();

  if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
    // "If there are any side effects in them, it returns (size_t) -1
    // for type 0 or 1 and (size_t) 0 for type 2 or 3."
    pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
    return true;
  }

  if (Ptr.isZero() || !Ptr.isBlockPointer())
    return false;

  // We can't load through pointers.
  if (Ptr.isDummy() && Ptr.getType()->isPointerType())
    return false;

  bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
  const Descriptor *DeclDesc = Ptr.getDeclDesc();
  assert(DeclDesc);

  if (!UseFieldDesc || DetermineForCompleteObject) {
    // Lower bound, so we can't fall back to this.
    if (ReportMinimum && !DetermineForCompleteObject)
      return false;

    // Can't read beyond the pointer decl desc.
    if (!UseFieldDesc && !ReportMinimum && DeclDesc->getType()->isPointerType())
      return false;
  } else {
    if (isUserWritingOffTheEnd(ASTCtx, Ptr.expand())) {
      // If we cannot determine the size of the initial allocation, then we
      // can't given an accurate upper-bound. However, we are still able to give
      // conservative lower-bounds for Type=3.
      if (Kind == 1)
        return false;
    }
  }

  const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
  assert(Desc);

  std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
  if (!FullSize)
    return false;

  unsigned ByteOffset;
  if (UseFieldDesc) {
    if (Ptr.isBaseClass())
      ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
                   computePointerOffset(ASTCtx, Ptr);
    else {
      if (Ptr.inArray())
        ByteOffset =
            computePointerOffset(ASTCtx, Ptr) -
            computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
      else
        ByteOffset = 0;
    }
  } else
    ByteOffset = computePointerOffset(ASTCtx, Ptr);

  assert(ByteOffset <= *FullSize);
  unsigned Result = *FullSize - ByteOffset;

  pushInteger(S, Result, Call->getType());
  return true;
}

static bool interp__builtin_is_within_lifetime(InterpState &S, CodePtr OpPC,
                                               const CallExpr *Call) {

  if (!S.inConstantContext())
    return false;

  const Pointer &Ptr = S.Stk.pop<Pointer>();

  auto Error = [&](int Diag) {
    bool CalledFromStd = false;
    const auto *Callee = S.Current->getCallee();
    if (Callee && Callee->isInStdNamespace()) {
      const IdentifierInfo *Identifier = Callee->getIdentifier();
      CalledFromStd = Identifier && Identifier->isStr("is_within_lifetime");
    }
    S.CCEDiag(CalledFromStd
                  ? S.Current->Caller->getSource(S.Current->getRetPC())
                  : S.Current->getSource(OpPC),
              diag::err_invalid_is_within_lifetime)
        << (CalledFromStd ? "std::is_within_lifetime"
                          : "__builtin_is_within_lifetime")
        << Diag;
    return false;
  };

  if (Ptr.isZero())
    return Error(0);
  if (Ptr.isOnePastEnd())
    return Error(1);

  bool Result = Ptr.getLifetime() != Lifetime::Ended;
  if (!Ptr.isActive()) {
    Result = false;
  } else {
    if (!CheckLive(S, OpPC, Ptr, AK_Read))
      return false;
    if (!CheckMutable(S, OpPC, Ptr))
      return false;
    if (!CheckDummy(S, OpPC, Ptr.block(), AK_Read))
      return false;
  }

  // Check if we're currently running an initializer.
  if (llvm::is_contained(S.InitializingBlocks, Ptr.block()))
    return Error(2);
  if (S.EvaluatingDecl && Ptr.getDeclDesc()->asVarDecl() == S.EvaluatingDecl)
    return Error(2);

  pushInteger(S, Result, Call->getType());
  return true;
}

static bool interp__builtin_elementwise_int_unaryop(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APInt(const APSInt &)> Fn) {
  assert(Call->getNumArgs() == 1);

  // Single integer case.
  if (!Call->getArg(0)->getType()->isVectorType()) {
    assert(Call->getType()->isIntegerType());
    APSInt Src = popToAPSInt(S, Call->getArg(0));
    APInt Result = Fn(Src);
    pushInteger(S, APSInt(std::move(Result), !Src.isSigned()), Call->getType());
    return true;
  }

  // Vector case.
  const Pointer &Arg = S.Stk.pop<Pointer>();
  assert(Arg.getFieldDesc()->isPrimitiveArray());
  const Pointer &Dst = S.Stk.peek<Pointer>();
  assert(Dst.getFieldDesc()->isPrimitiveArray());
  assert(Arg.getFieldDesc()->getNumElems() ==
         Dst.getFieldDesc()->getNumElems());

  QualType ElemType = Arg.getFieldDesc()->getElemQualType();
  PrimType ElemT = *S.getContext().classify(ElemType);
  unsigned NumElems = Arg.getNumElems();
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();

  for (unsigned I = 0; I != NumElems; ++I) {
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      APSInt Src = Arg.elem<T>(I).toAPSInt();
      APInt Result = Fn(Src);
      Dst.elem<T>(I) = static_cast<T>(APSInt(std::move(Result), DestUnsigned));
    });
  }
  Dst.initializeAllElements();

  return true;
}

static bool interp__builtin_elementwise_fp_binop(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APFloat(const APFloat &, const APFloat &,
                               std::optional<APSInt> RoundingMode)>
        Fn) {
  assert((Call->getNumArgs() == 2) || (Call->getNumArgs() == 3));
  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  assert(VT->getElementType()->isFloatingType());
  unsigned NumElems = VT->getNumElements();

  // Vector case.
  assert(Call->getArg(0)->getType()->isVectorType() &&
         Call->getArg(1)->getType()->isVectorType());
  assert(VT->getElementType() ==
         Call->getArg(1)->getType()->castAs<VectorType>()->getElementType());
  assert(VT->getNumElements() ==
         Call->getArg(1)->getType()->castAs<VectorType>()->getNumElements());

  std::optional<APSInt> RoundingMode = std::nullopt;
  if (Call->getNumArgs() == 3)
    RoundingMode = popToAPSInt(S, Call->getArg(2));

  const Pointer &BPtr = S.Stk.pop<Pointer>();
  const Pointer &APtr = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  for (unsigned ElemIdx = 0; ElemIdx != NumElems; ++ElemIdx) {
    using T = PrimConv<PT_Float>::T;
    APFloat ElemA = APtr.elem<T>(ElemIdx).getAPFloat();
    APFloat ElemB = BPtr.elem<T>(ElemIdx).getAPFloat();
    if (ElemA.isNaN() || ElemA.isInfinity() || ElemA.isDenormal() ||
        ElemB.isNaN() || ElemB.isInfinity() || ElemB.isDenormal())
      return false;
    Dst.elem<T>(ElemIdx) = static_cast<T>(Fn(ElemA, ElemB, RoundingMode));
  }

  Dst.initializeAllElements();

  return true;
}

static bool interp__builtin_elementwise_int_binop(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APInt(const APSInt &, const APSInt &)> Fn) {
  assert(Call->getNumArgs() == 2);

  // Single integer case.
  if (!Call->getArg(0)->getType()->isVectorType()) {
    assert(!Call->getArg(1)->getType()->isVectorType());
    APSInt RHS = popToAPSInt(S, Call->getArg(1));
    APSInt LHS = popToAPSInt(S, Call->getArg(0));
    APInt Result = Fn(LHS, RHS);
    pushInteger(S, APSInt(std::move(Result), !LHS.isSigned()), Call->getType());
    return true;
  }

  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  assert(VT->getElementType()->isIntegralOrEnumerationType());
  PrimType ElemT = *S.getContext().classify(VT->getElementType());
  unsigned NumElems = VT->getNumElements();
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();

  // Vector + Scalar case.
  if (!Call->getArg(1)->getType()->isVectorType()) {
    assert(Call->getArg(1)->getType()->isIntegralOrEnumerationType());

    APSInt RHS = popToAPSInt(S, Call->getArg(1));
    const Pointer &LHS = S.Stk.pop<Pointer>();
    const Pointer &Dst = S.Stk.peek<Pointer>();

    for (unsigned I = 0; I != NumElems; ++I) {
      INT_TYPE_SWITCH_NO_BOOL(ElemT, {
        Dst.elem<T>(I) = static_cast<T>(
            APSInt(Fn(LHS.elem<T>(I).toAPSInt(), RHS), DestUnsigned));
      });
    }
    Dst.initializeAllElements();
    return true;
  }

  // Vector case.
  assert(Call->getArg(0)->getType()->isVectorType() &&
         Call->getArg(1)->getType()->isVectorType());
  assert(VT->getElementType() ==
         Call->getArg(1)->getType()->castAs<VectorType>()->getElementType());
  assert(VT->getNumElements() ==
         Call->getArg(1)->getType()->castAs<VectorType>()->getNumElements());
  assert(VT->getElementType()->isIntegralOrEnumerationType());

  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  for (unsigned I = 0; I != NumElems; ++I) {
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      APSInt Elem1 = LHS.elem<T>(I).toAPSInt();
      APSInt Elem2 = RHS.elem<T>(I).toAPSInt();
      Dst.elem<T>(I) = static_cast<T>(APSInt(Fn(Elem1, Elem2), DestUnsigned));
    });
  }
  Dst.initializeAllElements();

  return true;
}

static bool
interp__builtin_x86_pack(InterpState &S, CodePtr, const CallExpr *E,
                         llvm::function_ref<APInt(const APSInt &)> PackFn) {
  const auto *VT0 = E->getArg(0)->getType()->castAs<VectorType>();
  [[maybe_unused]] const auto *VT1 =
      E->getArg(1)->getType()->castAs<VectorType>();
  assert(VT0 && VT1 && "pack builtin VT0 and VT1 must be VectorType");
  assert(VT0->getElementType() == VT1->getElementType() &&
         VT0->getNumElements() == VT1->getNumElements() &&
         "pack builtin VT0 and VT1 ElementType must be same");

  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  const ASTContext &ASTCtx = S.getASTContext();
  unsigned SrcBits = ASTCtx.getIntWidth(VT0->getElementType());
  unsigned LHSVecLen = VT0->getNumElements();
  unsigned SrcPerLane = 128 / SrcBits;
  unsigned Lanes = LHSVecLen * SrcBits / 128;

  PrimType SrcT = *S.getContext().classify(VT0->getElementType());
  PrimType DstT = *S.getContext().classify(getElemType(Dst));
  bool IsUnsigend = getElemType(Dst)->isUnsignedIntegerType();

  for (unsigned Lane = 0; Lane != Lanes; ++Lane) {
    unsigned BaseSrc = Lane * SrcPerLane;
    unsigned BaseDst = Lane * (2 * SrcPerLane);

    for (unsigned I = 0; I != SrcPerLane; ++I) {
      INT_TYPE_SWITCH_NO_BOOL(SrcT, {
        APSInt A = LHS.elem<T>(BaseSrc + I).toAPSInt();
        APSInt B = RHS.elem<T>(BaseSrc + I).toAPSInt();

        assignInteger(S, Dst.atIndex(BaseDst + I), DstT,
                      APSInt(PackFn(A), IsUnsigend));
        assignInteger(S, Dst.atIndex(BaseDst + SrcPerLane + I), DstT,
                      APSInt(PackFn(B), IsUnsigend));
      });
    }
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_elementwise_maxmin(InterpState &S, CodePtr OpPC,
                                               const CallExpr *Call,
                                               unsigned BuiltinID) {
  assert(Call->getNumArgs() == 2);

  QualType Arg0Type = Call->getArg(0)->getType();

  // TODO: Support floating-point types.
  if (!(Arg0Type->isIntegerType() ||
        (Arg0Type->isVectorType() &&
         Arg0Type->castAs<VectorType>()->getElementType()->isIntegerType())))
    return false;

  if (!Arg0Type->isVectorType()) {
    assert(!Call->getArg(1)->getType()->isVectorType());
    APSInt RHS = popToAPSInt(S, Call->getArg(1));
    APSInt LHS = popToAPSInt(S, Arg0Type);
    APInt Result;
    if (BuiltinID == Builtin::BI__builtin_elementwise_max) {
      Result = std::max(LHS, RHS);
    } else if (BuiltinID == Builtin::BI__builtin_elementwise_min) {
      Result = std::min(LHS, RHS);
    } else {
      llvm_unreachable("Wrong builtin ID");
    }

    pushInteger(S, APSInt(Result, !LHS.isSigned()), Call->getType());
    return true;
  }

  // Vector case.
  assert(Call->getArg(0)->getType()->isVectorType() &&
         Call->getArg(1)->getType()->isVectorType());
  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  assert(VT->getElementType() ==
         Call->getArg(1)->getType()->castAs<VectorType>()->getElementType());
  assert(VT->getNumElements() ==
         Call->getArg(1)->getType()->castAs<VectorType>()->getNumElements());
  assert(VT->getElementType()->isIntegralOrEnumerationType());

  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  PrimType ElemT = *S.getContext().classify(VT->getElementType());
  unsigned NumElems = VT->getNumElements();
  for (unsigned I = 0; I != NumElems; ++I) {
    APSInt Elem1;
    APSInt Elem2;
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      Elem1 = LHS.elem<T>(I).toAPSInt();
      Elem2 = RHS.elem<T>(I).toAPSInt();
    });

    APSInt Result;
    if (BuiltinID == Builtin::BI__builtin_elementwise_max) {
      Result = APSInt(std::max(Elem1, Elem2),
                      Call->getType()->isUnsignedIntegerOrEnumerationType());
    } else if (BuiltinID == Builtin::BI__builtin_elementwise_min) {
      Result = APSInt(std::min(Elem1, Elem2),
                      Call->getType()->isUnsignedIntegerOrEnumerationType());
    } else {
      llvm_unreachable("Wrong builtin ID");
    }

    INT_TYPE_SWITCH_NO_BOOL(ElemT,
                            { Dst.elem<T>(I) = static_cast<T>(Result); });
  }
  Dst.initializeAllElements();

  return true;
}

static bool interp__builtin_ia32_pmul(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APInt(const APSInt &, const APSInt &, const APSInt &,
                             const APSInt &)>
        Fn) {
  assert(Call->getArg(0)->getType()->isVectorType() &&
         Call->getArg(1)->getType()->isVectorType());
  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  PrimType ElemT = *S.getContext().classify(VT->getElementType());
  unsigned NumElems = VT->getNumElements();
  const auto *DestVT = Call->getType()->castAs<VectorType>();
  PrimType DestElemT = *S.getContext().classify(DestVT->getElementType());
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();

  unsigned DstElem = 0;
  for (unsigned I = 0; I != NumElems; I += 2) {
    APSInt Result;
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      APSInt LoLHS = LHS.elem<T>(I).toAPSInt();
      APSInt HiLHS = LHS.elem<T>(I + 1).toAPSInt();
      APSInt LoRHS = RHS.elem<T>(I).toAPSInt();
      APSInt HiRHS = RHS.elem<T>(I + 1).toAPSInt();
      Result = APSInt(Fn(LoLHS, HiLHS, LoRHS, HiRHS), DestUnsigned);
    });

    INT_TYPE_SWITCH_NO_BOOL(DestElemT,
                            { Dst.elem<T>(DstElem) = static_cast<T>(Result); });
    ++DstElem;
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp_builtin_horizontal_int_binop(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APInt(const APSInt &, const APSInt &)> Fn) {
  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  PrimType ElemT = *S.getContext().classify(VT->getElementType());
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();

  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  unsigned NumElts = VT->getNumElements();
  unsigned EltBits = S.getASTContext().getIntWidth(VT->getElementType());
  unsigned EltsPerLane = 128 / EltBits;
  unsigned Lanes = NumElts * EltBits / 128;
  unsigned DestIndex = 0;

  for (unsigned Lane = 0; Lane < Lanes; ++Lane) {
    unsigned LaneStart = Lane * EltsPerLane;
    for (unsigned I = 0; I < EltsPerLane; I += 2) {
      INT_TYPE_SWITCH_NO_BOOL(ElemT, {
        APSInt Elem1 = LHS.elem<T>(LaneStart + I).toAPSInt();
        APSInt Elem2 = LHS.elem<T>(LaneStart + I + 1).toAPSInt();
        APSInt ResL = APSInt(Fn(Elem1, Elem2), DestUnsigned);
        Dst.elem<T>(DestIndex++) = static_cast<T>(ResL);
      });
    }

    for (unsigned I = 0; I < EltsPerLane; I += 2) {
      INT_TYPE_SWITCH_NO_BOOL(ElemT, {
        APSInt Elem1 = RHS.elem<T>(LaneStart + I).toAPSInt();
        APSInt Elem2 = RHS.elem<T>(LaneStart + I + 1).toAPSInt();
        APSInt ResR = APSInt(Fn(Elem1, Elem2), DestUnsigned);
        Dst.elem<T>(DestIndex++) = static_cast<T>(ResR);
      });
    }
  }
  Dst.initializeAllElements();
  return true;
}

static bool interp_builtin_horizontal_fp_binop(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APFloat(const APFloat &, const APFloat &,
                               llvm::RoundingMode)>
        Fn) {
  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  FPOptions FPO = Call->getFPFeaturesInEffect(S.Ctx.getLangOpts());
  llvm::RoundingMode RM = getRoundingMode(FPO);
  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();

  unsigned NumElts = VT->getNumElements();
  unsigned EltBits = S.getASTContext().getTypeSize(VT->getElementType());
  unsigned NumLanes = NumElts * EltBits / 128;
  unsigned NumElemsPerLane = NumElts / NumLanes;
  unsigned HalfElemsPerLane = NumElemsPerLane / 2;

  for (unsigned L = 0; L != NumElts; L += NumElemsPerLane) {
    using T = PrimConv<PT_Float>::T;
    for (unsigned E = 0; E != HalfElemsPerLane; ++E) {
      APFloat Elem1 = LHS.elem<T>(L + (2 * E) + 0).getAPFloat();
      APFloat Elem2 = LHS.elem<T>(L + (2 * E) + 1).getAPFloat();
      Dst.elem<T>(L + E) = static_cast<T>(Fn(Elem1, Elem2, RM));
    }
    for (unsigned E = 0; E != HalfElemsPerLane; ++E) {
      APFloat Elem1 = RHS.elem<T>(L + (2 * E) + 0).getAPFloat();
      APFloat Elem2 = RHS.elem<T>(L + (2 * E) + 1).getAPFloat();
      Dst.elem<T>(L + E + HalfElemsPerLane) =
          static_cast<T>(Fn(Elem1, Elem2, RM));
    }
  }
  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_addsub(InterpState &S, CodePtr OpPC,
                                        const CallExpr *Call) {
  // Addsub: alternates between subtraction and addition
  // Result[i] = (i % 2 == 0) ? (a[i] - b[i]) : (a[i] + b[i])
  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  FPOptions FPO = Call->getFPFeaturesInEffect(S.Ctx.getLangOpts());
  llvm::RoundingMode RM = getRoundingMode(FPO);
  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  unsigned NumElems = VT->getNumElements();

  using T = PrimConv<PT_Float>::T;
  for (unsigned I = 0; I != NumElems; ++I) {
    APFloat LElem = LHS.elem<T>(I).getAPFloat();
    APFloat RElem = RHS.elem<T>(I).getAPFloat();
    if (I % 2 == 0) {
      // Even indices: subtract
      LElem.subtract(RElem, RM);
    } else {
      // Odd indices: add
      LElem.add(RElem, RM);
    }
    Dst.elem<T>(I) = static_cast<T>(LElem);
  }
  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_pclmulqdq(InterpState &S, CodePtr OpPC,
                                           const CallExpr *Call) {
  // PCLMULQDQ: carry-less multiplication of selected 64-bit halves
  // imm8 bit 0: selects lower (0) or upper (1) 64 bits of first operand
  // imm8 bit 4: selects lower (0) or upper (1) 64 bits of second operand
  assert(Call->getArg(0)->getType()->isVectorType() &&
         Call->getArg(1)->getType()->isVectorType());

  // Extract imm8 argument
  APSInt Imm8 = popToAPSInt(S, Call->getArg(2));
  bool SelectUpperA = (Imm8 & 0x01) != 0;
  bool SelectUpperB = (Imm8 & 0x10) != 0;

  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
  PrimType ElemT = *S.getContext().classify(VT->getElementType());
  unsigned NumElems = VT->getNumElements();
  const auto *DestVT = Call->getType()->castAs<VectorType>();
  PrimType DestElemT = *S.getContext().classify(DestVT->getElementType());
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();

  // Process each 128-bit lane (2 elements at a time)
  for (unsigned Lane = 0; Lane < NumElems; Lane += 2) {
    APSInt A0, A1, B0, B1;
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      A0 = LHS.elem<T>(Lane + 0).toAPSInt();
      A1 = LHS.elem<T>(Lane + 1).toAPSInt();
      B0 = RHS.elem<T>(Lane + 0).toAPSInt();
      B1 = RHS.elem<T>(Lane + 1).toAPSInt();
    });

    // Select the appropriate 64-bit values based on imm8
    APInt A = SelectUpperA ? A1 : A0;
    APInt B = SelectUpperB ? B1 : B0;

    // Extend both operands to 128 bits for carry-less multiplication
    APInt A128 = A.zext(128);
    APInt B128 = B.zext(128);

    // Use APIntOps::clmul for carry-less multiplication
    APInt Result = llvm::APIntOps::clmul(A128, B128);

    // Split the 128-bit result into two 64-bit halves
    APSInt ResultLow(Result.extractBits(64, 0), DestUnsigned);
    APSInt ResultHigh(Result.extractBits(64, 64), DestUnsigned);

    INT_TYPE_SWITCH_NO_BOOL(DestElemT, {
      Dst.elem<T>(Lane + 0) = static_cast<T>(ResultLow);
      Dst.elem<T>(Lane + 1) = static_cast<T>(ResultHigh);
    });
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_elementwise_triop_fp(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APFloat(const APFloat &, const APFloat &,
                               const APFloat &, llvm::RoundingMode)>
        Fn) {
  assert(Call->getNumArgs() == 3);

  FPOptions FPO = Call->getFPFeaturesInEffect(S.Ctx.getLangOpts());
  llvm::RoundingMode RM = getRoundingMode(FPO);
  QualType Arg1Type = Call->getArg(0)->getType();
  QualType Arg2Type = Call->getArg(1)->getType();
  QualType Arg3Type = Call->getArg(2)->getType();

  // Non-vector floating point types.
  if (!Arg1Type->isVectorType()) {
    assert(!Arg2Type->isVectorType());
    assert(!Arg3Type->isVectorType());
    (void)Arg2Type;
    (void)Arg3Type;

    const Floating &Z = S.Stk.pop<Floating>();
    const Floating &Y = S.Stk.pop<Floating>();
    const Floating &X = S.Stk.pop<Floating>();
    APFloat F = Fn(X.getAPFloat(), Y.getAPFloat(), Z.getAPFloat(), RM);
    Floating Result = S.allocFloat(X.getSemantics());
    Result.copy(F);
    S.Stk.push<Floating>(Result);
    return true;
  }

  // Vector type.
  assert(Arg1Type->isVectorType() && Arg2Type->isVectorType() &&
         Arg3Type->isVectorType());

  const VectorType *VecTy = Arg1Type->castAs<VectorType>();
  QualType ElemQT = VecTy->getElementType();
  unsigned NumElems = VecTy->getNumElements();

  assert(ElemQT == Arg2Type->castAs<VectorType>()->getElementType() &&
         ElemQT == Arg3Type->castAs<VectorType>()->getElementType());
  assert(NumElems == Arg2Type->castAs<VectorType>()->getNumElements() &&
         NumElems == Arg3Type->castAs<VectorType>()->getNumElements());
  assert(ElemQT->isRealFloatingType());
  (void)ElemQT;

  const Pointer &VZ = S.Stk.pop<Pointer>();
  const Pointer &VY = S.Stk.pop<Pointer>();
  const Pointer &VX = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  for (unsigned I = 0; I != NumElems; ++I) {
    using T = PrimConv<PT_Float>::T;
    APFloat X = VX.elem<T>(I).getAPFloat();
    APFloat Y = VY.elem<T>(I).getAPFloat();
    APFloat Z = VZ.elem<T>(I).getAPFloat();
    APFloat F = Fn(X, Y, Z, RM);
    Dst.elem<Floating>(I) = Floating(F);
  }
  Dst.initializeAllElements();
  return true;
}

/// AVX512 predicated move: "Result = Mask[] ? LHS[] : RHS[]".
static bool interp__builtin_select(InterpState &S, CodePtr OpPC,
                                   const CallExpr *Call) {
  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();
  APSInt Mask = popToAPSInt(S, Call->getArg(0));
  const Pointer &Dst = S.Stk.peek<Pointer>();

  assert(LHS.getNumElems() == RHS.getNumElems());
  assert(LHS.getNumElems() == Dst.getNumElems());
  unsigned NumElems = LHS.getNumElems();
  PrimType ElemT = LHS.getFieldDesc()->getPrimType();
  PrimType DstElemT = Dst.getFieldDesc()->getPrimType();

  for (unsigned I = 0; I != NumElems; ++I) {
    if (ElemT == PT_Float) {
      assert(DstElemT == PT_Float);
      Dst.elem<Floating>(I) =
          Mask[I] ? LHS.elem<Floating>(I) : RHS.elem<Floating>(I);
    } else {
      APSInt Elem;
      INT_TYPE_SWITCH(ElemT, {
        Elem = Mask[I] ? LHS.elem<T>(I).toAPSInt() : RHS.elem<T>(I).toAPSInt();
      });
      INT_TYPE_SWITCH_NO_BOOL(DstElemT,
                              { Dst.elem<T>(I) = static_cast<T>(Elem); });
    }
  }
  Dst.initializeAllElements();

  return true;
}

/// Scalar variant of AVX512 predicated select:
/// Result[i] = (Mask bit 0) ? LHS[i] : RHS[i], but only element 0 may change.
/// All other elements are taken from RHS.
static bool interp__builtin_select_scalar(InterpState &S,
                                          const CallExpr *Call) {
  unsigned N =
      Call->getArg(1)->getType()->castAs<VectorType>()->getNumElements();

  const Pointer &W = S.Stk.pop<Pointer>();
  const Pointer &A = S.Stk.pop<Pointer>();
  APSInt U = popToAPSInt(S, Call->getArg(0));
  const Pointer &Dst = S.Stk.peek<Pointer>();

  bool TakeA0 = U.getZExtValue() & 1ULL;

  for (unsigned I = TakeA0; I != N; ++I)
    Dst.elem<Floating>(I) = W.elem<Floating>(I);
  if (TakeA0)
    Dst.elem<Floating>(0) = A.elem<Floating>(0);

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_test_op(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<bool(const APInt &A, const APInt &B)> Fn) {
  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();

  assert(LHS.getNumElems() == RHS.getNumElems());

  unsigned SourceLen = LHS.getNumElems();
  QualType ElemQT = getElemType(LHS);
  OptPrimType ElemPT = S.getContext().classify(ElemQT);
  unsigned LaneWidth = S.getASTContext().getTypeSize(ElemQT);

  APInt AWide(LaneWidth * SourceLen, 0);
  APInt BWide(LaneWidth * SourceLen, 0);

  for (unsigned I = 0; I != SourceLen; ++I) {
    APInt ALane;
    APInt BLane;

    if (ElemQT->isIntegerType()) { // Get value.
      INT_TYPE_SWITCH_NO_BOOL(*ElemPT, {
        ALane = LHS.elem<T>(I).toAPSInt();
        BLane = RHS.elem<T>(I).toAPSInt();
      });
    } else if (ElemQT->isFloatingType()) { // Get only sign bit.
      using T = PrimConv<PT_Float>::T;
      ALane = LHS.elem<T>(I).getAPFloat().bitcastToAPInt().isNegative();
      BLane = RHS.elem<T>(I).getAPFloat().bitcastToAPInt().isNegative();
    } else { // Must be integer or floating type.
      return false;
    }
    AWide.insertBits(ALane, I * LaneWidth);
    BWide.insertBits(BLane, I * LaneWidth);
  }
  pushInteger(S, Fn(AWide, BWide), Call->getType());
  return true;
}

static bool interp__builtin_ia32_movmsk_op(InterpState &S, CodePtr OpPC,
                                           const CallExpr *Call) {
  assert(Call->getNumArgs() == 1);

  const Pointer &Source = S.Stk.pop<Pointer>();

  unsigned SourceLen = Source.getNumElems();
  QualType ElemQT = getElemType(Source);
  OptPrimType ElemT = S.getContext().classify(ElemQT);
  unsigned ResultLen =
      S.getASTContext().getTypeSize(Call->getType()); // Always 32-bit integer.
  APInt Result(ResultLen, 0);

  for (unsigned I = 0; I != SourceLen; ++I) {
    APInt Elem;
    if (ElemQT->isIntegerType()) {
      INT_TYPE_SWITCH_NO_BOOL(*ElemT, { Elem = Source.elem<T>(I).toAPSInt(); });
    } else if (ElemQT->isRealFloatingType()) {
      using T = PrimConv<PT_Float>::T;
      Elem = Source.elem<T>(I).getAPFloat().bitcastToAPInt();
    } else {
      return false;
    }
    Result.setBitVal(I, Elem.isNegative());
  }
  pushInteger(S, Result, Call->getType());
  return true;
}

static bool interp__builtin_elementwise_triop(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APInt(const APSInt &, const APSInt &, const APSInt &)>
        Fn) {
  assert(Call->getNumArgs() == 3);

  QualType Arg0Type = Call->getArg(0)->getType();
  QualType Arg2Type = Call->getArg(2)->getType();
  // Non-vector integer types.
  if (!Arg0Type->isVectorType()) {
    const APSInt &Op2 = popToAPSInt(S, Arg2Type);
    const APSInt &Op1 = popToAPSInt(S, Call->getArg(1));
    const APSInt &Op0 = popToAPSInt(S, Arg0Type);
    APSInt Result = APSInt(Fn(Op0, Op1, Op2), Op0.isUnsigned());
    pushInteger(S, Result, Call->getType());
    return true;
  }

  const auto *VecT = Arg0Type->castAs<VectorType>();
  PrimType ElemT = *S.getContext().classify(VecT->getElementType());
  unsigned NumElems = VecT->getNumElements();
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();

  // Vector + Vector + Scalar case.
  if (!Arg2Type->isVectorType()) {
    APSInt Op2 = popToAPSInt(S, Arg2Type);

    const Pointer &Op1 = S.Stk.pop<Pointer>();
    const Pointer &Op0 = S.Stk.pop<Pointer>();
    const Pointer &Dst = S.Stk.peek<Pointer>();
    for (unsigned I = 0; I != NumElems; ++I) {
      INT_TYPE_SWITCH_NO_BOOL(ElemT, {
        Dst.elem<T>(I) = static_cast<T>(APSInt(
            Fn(Op0.elem<T>(I).toAPSInt(), Op1.elem<T>(I).toAPSInt(), Op2),
            DestUnsigned));
      });
    }
    Dst.initializeAllElements();

    return true;
  }

  // Vector type.
  const Pointer &Op2 = S.Stk.pop<Pointer>();
  const Pointer &Op1 = S.Stk.pop<Pointer>();
  const Pointer &Op0 = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();
  for (unsigned I = 0; I != NumElems; ++I) {
    APSInt Val0, Val1, Val2;
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      Val0 = Op0.elem<T>(I).toAPSInt();
      Val1 = Op1.elem<T>(I).toAPSInt();
      Val2 = Op2.elem<T>(I).toAPSInt();
    });
    APSInt Result = APSInt(Fn(Val0, Val1, Val2), Val0.isUnsigned());
    INT_TYPE_SWITCH_NO_BOOL(ElemT,
                            { Dst.elem<T>(I) = static_cast<T>(Result); });
  }
  Dst.initializeAllElements();

  return true;
}

static bool interp__builtin_x86_extract_vector(InterpState &S, CodePtr OpPC,
                                               const CallExpr *Call,
                                               unsigned ID) {
  assert(Call->getNumArgs() == 2);

  APSInt ImmAPS = popToAPSInt(S, Call->getArg(1));
  uint64_t Index = ImmAPS.getZExtValue();

  const Pointer &Src = S.Stk.pop<Pointer>();
  if (!Src.getFieldDesc()->isPrimitiveArray())
    return false;

  const Pointer &Dst = S.Stk.peek<Pointer>();
  if (!Dst.getFieldDesc()->isPrimitiveArray())
    return false;

  unsigned SrcElems = Src.getNumElems();
  unsigned DstElems = Dst.getNumElems();

  unsigned NumLanes = SrcElems / DstElems;
  unsigned Lane = static_cast<unsigned>(Index % NumLanes);
  unsigned ExtractPos = Lane * DstElems;

  PrimType ElemT = Src.getFieldDesc()->getPrimType();

  TYPE_SWITCH(ElemT, {
    for (unsigned I = 0; I != DstElems; ++I) {
      Dst.elem<T>(I) = Src.elem<T>(ExtractPos + I);
    }
  });

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_x86_extract_vector_masked(InterpState &S,
                                                      CodePtr OpPC,
                                                      const CallExpr *Call,
                                                      unsigned ID) {
  assert(Call->getNumArgs() == 4);

  APSInt MaskAPS = popToAPSInt(S, Call->getArg(3));
  const Pointer &Merge = S.Stk.pop<Pointer>();
  APSInt ImmAPS = popToAPSInt(S, Call->getArg(1));
  const Pointer &Src = S.Stk.pop<Pointer>();

  if (!Src.getFieldDesc()->isPrimitiveArray() ||
      !Merge.getFieldDesc()->isPrimitiveArray())
    return false;

  const Pointer &Dst = S.Stk.peek<Pointer>();
  if (!Dst.getFieldDesc()->isPrimitiveArray())
    return false;

  unsigned SrcElems = Src.getNumElems();
  unsigned DstElems = Dst.getNumElems();

  unsigned NumLanes = SrcElems / DstElems;
  unsigned Lane = static_cast<unsigned>(ImmAPS.getZExtValue() % NumLanes);
  unsigned Base = Lane * DstElems;

  PrimType ElemT = Src.getFieldDesc()->getPrimType();

  TYPE_SWITCH(ElemT, {
    for (unsigned I = 0; I != DstElems; ++I) {
      if (MaskAPS[I])
        Dst.elem<T>(I) = Src.elem<T>(Base + I);
      else
        Dst.elem<T>(I) = Merge.elem<T>(I);
    }
  });

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_x86_insert_subvector(InterpState &S, CodePtr OpPC,
                                                 const CallExpr *Call,
                                                 unsigned ID) {
  assert(Call->getNumArgs() == 3);

  APSInt ImmAPS = popToAPSInt(S, Call->getArg(2));
  uint64_t Index = ImmAPS.getZExtValue();

  const Pointer &SubVec = S.Stk.pop<Pointer>();
  if (!SubVec.getFieldDesc()->isPrimitiveArray())
    return false;

  const Pointer &BaseVec = S.Stk.pop<Pointer>();
  if (!BaseVec.getFieldDesc()->isPrimitiveArray())
    return false;

  const Pointer &Dst = S.Stk.peek<Pointer>();

  unsigned BaseElements = BaseVec.getNumElems();
  unsigned SubElements = SubVec.getNumElems();

  assert(SubElements != 0 && BaseElements != 0 &&
         (BaseElements % SubElements) == 0);

  unsigned NumLanes = BaseElements / SubElements;
  unsigned Lane = static_cast<unsigned>(Index % NumLanes);
  unsigned InsertPos = Lane * SubElements;

  PrimType ElemT = BaseVec.getFieldDesc()->getPrimType();

  TYPE_SWITCH(ElemT, {
    for (unsigned I = 0; I != BaseElements; ++I)
      Dst.elem<T>(I) = BaseVec.elem<T>(I);
    for (unsigned I = 0; I != SubElements; ++I)
      Dst.elem<T>(InsertPos + I) = SubVec.elem<T>(I);
  });

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_phminposuw(InterpState &S, CodePtr OpPC,
                                            const CallExpr *Call) {
  assert(Call->getNumArgs() == 1);

  const Pointer &Source = S.Stk.pop<Pointer>();
  const Pointer &Dest = S.Stk.peek<Pointer>();

  unsigned SourceLen = Source.getNumElems();
  QualType ElemQT = getElemType(Source);
  OptPrimType ElemT = S.getContext().classify(ElemQT);
  unsigned ElemBitWidth = S.getASTContext().getTypeSize(ElemQT);

  bool DestUnsigned = Call->getCallReturnType(S.getASTContext())
                          ->castAs<VectorType>()
                          ->getElementType()
                          ->isUnsignedIntegerOrEnumerationType();

  INT_TYPE_SWITCH_NO_BOOL(*ElemT, {
    APSInt MinIndex(ElemBitWidth, DestUnsigned);
    APSInt MinVal = Source.elem<T>(0).toAPSInt();

    for (unsigned I = 1; I != SourceLen; ++I) {
      APSInt Val = Source.elem<T>(I).toAPSInt();
      if (MinVal.ugt(Val)) {
        MinVal = Val;
        MinIndex = I;
      }
    }

    Dest.elem<T>(0) = static_cast<T>(MinVal);
    Dest.elem<T>(1) = static_cast<T>(MinIndex);
    for (unsigned I = 2; I != SourceLen; ++I) {
      Dest.elem<T>(I) = static_cast<T>(APSInt(ElemBitWidth, DestUnsigned));
    }
  });
  Dest.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_pternlog(InterpState &S, CodePtr OpPC,
                                          const CallExpr *Call, bool MaskZ) {
  assert(Call->getNumArgs() == 5);

  APInt U = popToAPSInt(S, Call->getArg(4));   // Lane mask
  APInt Imm = popToAPSInt(S, Call->getArg(3)); // Ternary truth table
  const Pointer &C = S.Stk.pop<Pointer>();
  const Pointer &B = S.Stk.pop<Pointer>();
  const Pointer &A = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  unsigned DstLen = A.getNumElems();
  QualType ElemQT = getElemType(A);
  OptPrimType ElemT = S.getContext().classify(ElemQT);
  unsigned LaneWidth = S.getASTContext().getTypeSize(ElemQT);
  bool DstUnsigned = ElemQT->isUnsignedIntegerOrEnumerationType();

  INT_TYPE_SWITCH_NO_BOOL(*ElemT, {
    for (unsigned I = 0; I != DstLen; ++I) {
      APInt ALane = A.elem<T>(I).toAPSInt();
      APInt BLane = B.elem<T>(I).toAPSInt();
      APInt CLane = C.elem<T>(I).toAPSInt();
      APInt RLane(LaneWidth, 0);
      if (U[I]) { // If lane not masked, compute ternary logic.
        for (unsigned Bit = 0; Bit != LaneWidth; ++Bit) {
          unsigned ABit = ALane[Bit];
          unsigned BBit = BLane[Bit];
          unsigned CBit = CLane[Bit];
          unsigned Idx = (ABit << 2) | (BBit << 1) | (CBit);
          RLane.setBitVal(Bit, Imm[Idx]);
        }
        Dst.elem<T>(I) = static_cast<T>(APSInt(RLane, DstUnsigned));
      } else if (MaskZ) { // If zero masked, zero the lane.
        Dst.elem<T>(I) = static_cast<T>(APSInt(RLane, DstUnsigned));
      } else { // Just masked, put in A lane.
        Dst.elem<T>(I) = static_cast<T>(APSInt(ALane, DstUnsigned));
      }
    }
  });
  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_vec_ext(InterpState &S, CodePtr OpPC,
                                    const CallExpr *Call, unsigned ID) {
  assert(Call->getNumArgs() == 2);

  APSInt ImmAPS = popToAPSInt(S, Call->getArg(1));
  const Pointer &Vec = S.Stk.pop<Pointer>();
  if (!Vec.getFieldDesc()->isPrimitiveArray())
    return false;

  unsigned NumElems = Vec.getNumElems();
  unsigned Index =
      static_cast<unsigned>(ImmAPS.getZExtValue() & (NumElems - 1));

  PrimType ElemT = Vec.getFieldDesc()->getPrimType();
  // FIXME(#161685): Replace float+int split with a numeric-only type switch
  if (ElemT == PT_Float) {
    S.Stk.push<Floating>(Vec.elem<Floating>(Index));
    return true;
  }
  INT_TYPE_SWITCH_NO_BOOL(ElemT, {
    APSInt V = Vec.elem<T>(Index).toAPSInt();
    pushInteger(S, V, Call->getType());
  });

  return true;
}

static bool interp__builtin_vec_set(InterpState &S, CodePtr OpPC,
                                    const CallExpr *Call, unsigned ID) {
  assert(Call->getNumArgs() == 3);

  APSInt ImmAPS = popToAPSInt(S, Call->getArg(2));
  APSInt ValAPS = popToAPSInt(S, Call->getArg(1));

  const Pointer &Base = S.Stk.pop<Pointer>();
  if (!Base.getFieldDesc()->isPrimitiveArray())
    return false;

  const Pointer &Dst = S.Stk.peek<Pointer>();

  unsigned NumElems = Base.getNumElems();
  unsigned Index =
      static_cast<unsigned>(ImmAPS.getZExtValue() & (NumElems - 1));

  PrimType ElemT = Base.getFieldDesc()->getPrimType();
  INT_TYPE_SWITCH_NO_BOOL(ElemT, {
    for (unsigned I = 0; I != NumElems; ++I)
      Dst.elem<T>(I) = Base.elem<T>(I);
    Dst.elem<T>(Index) = static_cast<T>(ValAPS);
  });

  Dst.initializeAllElements();
  return true;
}

static bool evalICmpImm(uint8_t Imm, const APSInt &A, const APSInt &B,
                        bool IsUnsigned) {
  switch (Imm & 0x7) {
  case 0x00: // _MM_CMPINT_EQ
    return (A == B);
  case 0x01: // _MM_CMPINT_LT
    return IsUnsigned ? A.ult(B) : A.slt(B);
  case 0x02: // _MM_CMPINT_LE
    return IsUnsigned ? A.ule(B) : A.sle(B);
  case 0x03: // _MM_CMPINT_FALSE
    return false;
  case 0x04: // _MM_CMPINT_NE
    return (A != B);
  case 0x05: // _MM_CMPINT_NLT
    return IsUnsigned ? A.ugt(B) : A.sgt(B);
  case 0x06: // _MM_CMPINT_NLE
    return IsUnsigned ? A.uge(B) : A.sge(B);
  case 0x07: // _MM_CMPINT_TRUE
    return true;
  default:
    llvm_unreachable("Invalid Op");
  }
}

static bool interp__builtin_ia32_cmp_mask(InterpState &S, CodePtr OpPC,
                                          const CallExpr *Call, unsigned ID,
                                          bool IsUnsigned) {
  assert(Call->getNumArgs() == 4);

  APSInt Mask = popToAPSInt(S, Call->getArg(3));
  APSInt Opcode = popToAPSInt(S, Call->getArg(2));
  unsigned CmpOp = static_cast<unsigned>(Opcode.getZExtValue());
  const Pointer &RHS = S.Stk.pop<Pointer>();
  const Pointer &LHS = S.Stk.pop<Pointer>();

  assert(LHS.getNumElems() == RHS.getNumElems());

  APInt RetMask = APInt::getZero(LHS.getNumElems());
  unsigned VectorLen = LHS.getNumElems();
  PrimType ElemT = LHS.getFieldDesc()->getPrimType();

  for (unsigned ElemNum = 0; ElemNum < VectorLen; ++ElemNum) {
    APSInt A, B;
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      A = LHS.elem<T>(ElemNum).toAPSInt();
      B = RHS.elem<T>(ElemNum).toAPSInt();
    });
    RetMask.setBitVal(ElemNum,
                      Mask[ElemNum] && evalICmpImm(CmpOp, A, B, IsUnsigned));
  }
  pushInteger(S, RetMask, Call->getType());
  return true;
}

static bool interp__builtin_ia32_vpconflict(InterpState &S, CodePtr OpPC,
                                            const CallExpr *Call) {
  assert(Call->getNumArgs() == 1);

  QualType Arg0Type = Call->getArg(0)->getType();
  const auto *VecT = Arg0Type->castAs<VectorType>();
  PrimType ElemT = *S.getContext().classify(VecT->getElementType());
  unsigned NumElems = VecT->getNumElements();
  bool DestUnsigned = Call->getType()->isUnsignedIntegerOrEnumerationType();
  const Pointer &Src = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  for (unsigned I = 0; I != NumElems; ++I) {
    INT_TYPE_SWITCH_NO_BOOL(ElemT, {
      APSInt ElemI = Src.elem<T>(I).toAPSInt();
      APInt ConflictMask(ElemI.getBitWidth(), 0);
      for (unsigned J = 0; J != I; ++J) {
        APSInt ElemJ = Src.elem<T>(J).toAPSInt();
        ConflictMask.setBitVal(J, ElemI == ElemJ);
      }
      Dst.elem<T>(I) = static_cast<T>(APSInt(ConflictMask, DestUnsigned));
    });
  }
  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_cvt_vec2mask(InterpState &S, CodePtr OpPC,
                                              const CallExpr *Call,
                                              unsigned ID) {
  assert(Call->getNumArgs() == 1);

  const Pointer &Vec = S.Stk.pop<Pointer>();
  unsigned RetWidth = S.getASTContext().getIntWidth(Call->getType());
  APInt RetMask(RetWidth, 0);

  unsigned VectorLen = Vec.getNumElems();
  PrimType ElemT = Vec.getFieldDesc()->getPrimType();

  for (unsigned ElemNum = 0; ElemNum != VectorLen; ++ElemNum) {
    APSInt A;
    INT_TYPE_SWITCH_NO_BOOL(ElemT, { A = Vec.elem<T>(ElemNum).toAPSInt(); });
    unsigned MSB = A[A.getBitWidth() - 1];
    RetMask.setBitVal(ElemNum, MSB);
  }
  pushInteger(S, RetMask, Call->getType());
  return true;
}

static bool interp__builtin_ia32_cvt_mask2vec(InterpState &S, CodePtr OpPC,
                                              const CallExpr *Call,
                                              unsigned ID) {
  assert(Call->getNumArgs() == 1);

  APSInt Mask = popToAPSInt(S, Call->getArg(0));

  const Pointer &Vec = S.Stk.peek<Pointer>();
  unsigned NumElems = Vec.getNumElems();
  PrimType ElemT = Vec.getFieldDesc()->getPrimType();

  for (unsigned I = 0; I != NumElems; ++I) {
    bool BitSet = Mask[I];

    INT_TYPE_SWITCH_NO_BOOL(
        ElemT, { Vec.elem<T>(I) = BitSet ? T::from(-1) : T::from(0); });
  }

  Vec.initializeAllElements();

  return true;
}

static bool interp__builtin_ia32_cvtsd2ss(InterpState &S, CodePtr OpPC,
                                          const CallExpr *Call,
                                          bool HasRoundingMask) {
  APSInt Rounding, MaskInt;
  Pointer Src, B, A;

  if (HasRoundingMask) {
    assert(Call->getNumArgs() == 5);
    Rounding = popToAPSInt(S, Call->getArg(4));
    MaskInt = popToAPSInt(S, Call->getArg(3));
    Src = S.Stk.pop<Pointer>();
    B = S.Stk.pop<Pointer>();
    A = S.Stk.pop<Pointer>();
    if (!CheckLoad(S, OpPC, A) || !CheckLoad(S, OpPC, B) ||
        !CheckLoad(S, OpPC, Src))
      return false;
  } else {
    assert(Call->getNumArgs() == 2);
    B = S.Stk.pop<Pointer>();
    A = S.Stk.pop<Pointer>();
    if (!CheckLoad(S, OpPC, A) || !CheckLoad(S, OpPC, B))
      return false;
  }

  const auto *DstVTy = Call->getType()->castAs<VectorType>();
  unsigned NumElems = DstVTy->getNumElements();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  // Copy all elements except lane 0 (overwritten below) from A to Dst.
  for (unsigned I = 1; I != NumElems; ++I)
    Dst.elem<Floating>(I) = A.elem<Floating>(I);

  // Convert element 0 from double to float, or use Src if masked off.
  if (!HasRoundingMask || (MaskInt.getZExtValue() & 0x1)) {
    assert(S.getASTContext().FloatTy == DstVTy->getElementType() &&
           "cvtsd2ss requires float element type in destination vector");

    Floating Conv = S.allocFloat(
        S.getASTContext().getFloatTypeSemantics(DstVTy->getElementType()));
    APFloat SrcVal = B.elem<Floating>(0).getAPFloat();
    if (!convertDoubleToFloatStrict(SrcVal, Conv, S, Call))
      return false;
    Dst.elem<Floating>(0) = Conv;
  } else {
    Dst.elem<Floating>(0) = Src.elem<Floating>(0);
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_cvtpd2ps(InterpState &S, CodePtr OpPC,
                                          const CallExpr *Call, bool IsMasked,
                                          bool HasRounding) {

  APSInt MaskVal;
  Pointer PassThrough;
  Pointer Src;
  APSInt Rounding;

  if (IsMasked) {
    // Pop in reverse order.
    if (HasRounding) {
      Rounding = popToAPSInt(S, Call->getArg(3));
      MaskVal = popToAPSInt(S, Call->getArg(2));
      PassThrough = S.Stk.pop<Pointer>();
      Src = S.Stk.pop<Pointer>();
    } else {
      MaskVal = popToAPSInt(S, Call->getArg(2));
      PassThrough = S.Stk.pop<Pointer>();
      Src = S.Stk.pop<Pointer>();
    }

    if (!CheckLoad(S, OpPC, PassThrough))
      return false;
  } else {
    // Pop source only.
    Src = S.Stk.pop<Pointer>();
  }

  if (!CheckLoad(S, OpPC, Src))
    return false;

  const auto *RetVTy = Call->getType()->castAs<VectorType>();
  unsigned RetElems = RetVTy->getNumElements();
  unsigned SrcElems = Src.getNumElems();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  // Initialize destination with passthrough or zeros.
  for (unsigned I = 0; I != RetElems; ++I)
    if (IsMasked)
      Dst.elem<Floating>(I) = PassThrough.elem<Floating>(I);
    else
      Dst.elem<Floating>(I) = Floating(APFloat(0.0f));

  assert(S.getASTContext().FloatTy == RetVTy->getElementType() &&
         "cvtpd2ps requires float element type in return vector");

  // Convert double to float for enabled elements (only process source elements
  // that exist).
  for (unsigned I = 0; I != SrcElems; ++I) {
    if (IsMasked && !MaskVal[I])
      continue;

    APFloat SrcVal = Src.elem<Floating>(I).getAPFloat();

    Floating Conv = S.allocFloat(
        S.getASTContext().getFloatTypeSemantics(RetVTy->getElementType()));
    if (!convertDoubleToFloatStrict(SrcVal, Conv, S, Call))
      return false;
    Dst.elem<Floating>(I) = Conv;
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_shuffle_generic(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<std::pair<unsigned, int>(unsigned, unsigned)>
        GetSourceIndex) {

  assert(Call->getNumArgs() == 2 || Call->getNumArgs() == 3);

  unsigned ShuffleMask = 0;
  Pointer A, MaskVector, B;
  bool IsVectorMask = false;
  bool IsSingleOperand = (Call->getNumArgs() == 2);

  if (IsSingleOperand) {
    QualType MaskType = Call->getArg(1)->getType();
    if (MaskType->isVectorType()) {
      IsVectorMask = true;
      MaskVector = S.Stk.pop<Pointer>();
      A = S.Stk.pop<Pointer>();
      B = A;
    } else if (MaskType->isIntegerType()) {
      ShuffleMask = popToAPSInt(S, Call->getArg(1)).getZExtValue();
      A = S.Stk.pop<Pointer>();
      B = A;
    } else {
      return false;
    }
  } else {
    QualType Arg2Type = Call->getArg(2)->getType();
    if (Arg2Type->isVectorType()) {
      IsVectorMask = true;
      B = S.Stk.pop<Pointer>();
      MaskVector = S.Stk.pop<Pointer>();
      A = S.Stk.pop<Pointer>();
    } else if (Arg2Type->isIntegerType()) {
      ShuffleMask = popToAPSInt(S, Call->getArg(2)).getZExtValue();
      B = S.Stk.pop<Pointer>();
      A = S.Stk.pop<Pointer>();
    } else {
      return false;
    }
  }

  QualType Arg0Type = Call->getArg(0)->getType();
  const auto *VecT = Arg0Type->castAs<VectorType>();
  PrimType ElemT = *S.getContext().classify(VecT->getElementType());
  unsigned NumElems = VecT->getNumElements();

  const Pointer &Dst = S.Stk.peek<Pointer>();

  PrimType MaskElemT = PT_Uint32;
  if (IsVectorMask) {
    QualType Arg1Type = Call->getArg(1)->getType();
    const auto *MaskVecT = Arg1Type->castAs<VectorType>();
    QualType MaskElemType = MaskVecT->getElementType();
    MaskElemT = *S.getContext().classify(MaskElemType);
  }

  for (unsigned DstIdx = 0; DstIdx != NumElems; ++DstIdx) {
    if (IsVectorMask) {
      INT_TYPE_SWITCH(MaskElemT, {
        ShuffleMask = static_cast<unsigned>(MaskVector.elem<T>(DstIdx));
      });
    }

    auto [SrcVecIdx, SrcIdx] = GetSourceIndex(DstIdx, ShuffleMask);

    if (SrcIdx < 0) {
      // Zero out this element
      if (ElemT == PT_Float) {
        Dst.elem<Floating>(DstIdx) = Floating(
            S.getASTContext().getFloatTypeSemantics(VecT->getElementType()));
      } else {
        INT_TYPE_SWITCH_NO_BOOL(ElemT, { Dst.elem<T>(DstIdx) = T::from(0); });
      }
    } else {
      const Pointer &Src = (SrcVecIdx == 0) ? A : B;
      TYPE_SWITCH(ElemT, { Dst.elem<T>(DstIdx) = Src.elem<T>(SrcIdx); });
    }
  }
  Dst.initializeAllElements();

  return true;
}

static bool interp__builtin_ia32_shift_with_count(
    InterpState &S, CodePtr OpPC, const CallExpr *Call,
    llvm::function_ref<APInt(const APInt &, uint64_t)> ShiftOp,
    llvm::function_ref<APInt(const APInt &, unsigned)> OverflowOp) {

  assert(Call->getNumArgs() == 2);

  const Pointer &Count = S.Stk.pop<Pointer>();
  const Pointer &Source = S.Stk.pop<Pointer>();

  QualType SourceType = Call->getArg(0)->getType();
  QualType CountType = Call->getArg(1)->getType();
  assert(SourceType->isVectorType() && CountType->isVectorType());

  const auto *SourceVecT = SourceType->castAs<VectorType>();
  const auto *CountVecT = CountType->castAs<VectorType>();
  PrimType SourceElemT = *S.getContext().classify(SourceVecT->getElementType());
  PrimType CountElemT = *S.getContext().classify(CountVecT->getElementType());

  const Pointer &Dst = S.Stk.peek<Pointer>();

  unsigned DestEltWidth =
      S.getASTContext().getTypeSize(SourceVecT->getElementType());
  bool IsDestUnsigned = SourceVecT->getElementType()->isUnsignedIntegerType();
  unsigned DestLen = SourceVecT->getNumElements();
  unsigned CountEltWidth =
      S.getASTContext().getTypeSize(CountVecT->getElementType());
  unsigned NumBitsInQWord = 64;
  unsigned NumCountElts = NumBitsInQWord / CountEltWidth;

  uint64_t CountLQWord = 0;
  for (unsigned EltIdx = 0; EltIdx != NumCountElts; ++EltIdx) {
    uint64_t Elt = 0;
    INT_TYPE_SWITCH(CountElemT,
                    { Elt = static_cast<uint64_t>(Count.elem<T>(EltIdx)); });
    CountLQWord |= (Elt << (EltIdx * CountEltWidth));
  }

  for (unsigned EltIdx = 0; EltIdx != DestLen; ++EltIdx) {
    APSInt Elt;
    INT_TYPE_SWITCH(SourceElemT, { Elt = Source.elem<T>(EltIdx).toAPSInt(); });

    APInt Result;
    if (CountLQWord < DestEltWidth) {
      Result = ShiftOp(Elt, CountLQWord);
    } else {
      Result = OverflowOp(Elt, DestEltWidth);
    }
    if (IsDestUnsigned) {
      INT_TYPE_SWITCH(SourceElemT, {
        Dst.elem<T>(EltIdx) = T::from(Result.getZExtValue());
      });
    } else {
      INT_TYPE_SWITCH(SourceElemT, {
        Dst.elem<T>(EltIdx) = T::from(Result.getSExtValue());
      });
    }
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_shufbitqmb_mask(InterpState &S, CodePtr OpPC,
                                                 const CallExpr *Call) {

  assert(Call->getNumArgs() == 3);

  QualType SourceType = Call->getArg(0)->getType();
  QualType ShuffleMaskType = Call->getArg(1)->getType();
  QualType ZeroMaskType = Call->getArg(2)->getType();
  if (!SourceType->isVectorType() || !ShuffleMaskType->isVectorType() ||
      !ZeroMaskType->isIntegerType()) {
    return false;
  }

  Pointer Source, ShuffleMask;
  APSInt ZeroMask = popToAPSInt(S, Call->getArg(2));
  ShuffleMask = S.Stk.pop<Pointer>();
  Source = S.Stk.pop<Pointer>();

  const auto *SourceVecT = SourceType->castAs<VectorType>();
  const auto *ShuffleMaskVecT = ShuffleMaskType->castAs<VectorType>();
  assert(SourceVecT->getNumElements() == ShuffleMaskVecT->getNumElements());
  assert(ZeroMask.getBitWidth() == SourceVecT->getNumElements());

  PrimType SourceElemT = *S.getContext().classify(SourceVecT->getElementType());
  PrimType ShuffleMaskElemT =
      *S.getContext().classify(ShuffleMaskVecT->getElementType());

  unsigned NumBytesInQWord = 8;
  unsigned NumBitsInByte = 8;
  unsigned NumBytes = SourceVecT->getNumElements();
  unsigned NumQWords = NumBytes / NumBytesInQWord;
  unsigned RetWidth = ZeroMask.getBitWidth();
  APSInt RetMask(llvm::APInt(RetWidth, 0), /*isUnsigned=*/true);

  for (unsigned QWordId = 0; QWordId != NumQWords; ++QWordId) {
    APInt SourceQWord(64, 0);
    for (unsigned ByteIdx = 0; ByteIdx != NumBytesInQWord; ++ByteIdx) {
      uint64_t Byte = 0;
      INT_TYPE_SWITCH(SourceElemT, {
        Byte = static_cast<uint64_t>(
            Source.elem<T>(QWordId * NumBytesInQWord + ByteIdx));
      });
      SourceQWord.insertBits(APInt(8, Byte & 0xFF), ByteIdx * NumBitsInByte);
    }

    for (unsigned ByteIdx = 0; ByteIdx != NumBytesInQWord; ++ByteIdx) {
      unsigned SelIdx = QWordId * NumBytesInQWord + ByteIdx;
      unsigned M = 0;
      INT_TYPE_SWITCH(ShuffleMaskElemT, {
        M = static_cast<unsigned>(ShuffleMask.elem<T>(SelIdx)) & 0x3F;
      });

      if (ZeroMask[SelIdx]) {
        RetMask.setBitVal(SelIdx, SourceQWord[M]);
      }
    }
  }

  pushInteger(S, RetMask, Call->getType());
  return true;
}

static bool interp__builtin_ia32_vcvtps2ph(InterpState &S, CodePtr OpPC,
                                           const CallExpr *Call) {
  // Arguments are: vector of floats, rounding immediate
  assert(Call->getNumArgs() == 2);

  APSInt Imm = popToAPSInt(S, Call->getArg(1));
  const Pointer &Src = S.Stk.pop<Pointer>();
  const Pointer &Dst = S.Stk.peek<Pointer>();

  assert(Src.getFieldDesc()->isPrimitiveArray());
  assert(Dst.getFieldDesc()->isPrimitiveArray());

  const auto *SrcVTy = Call->getArg(0)->getType()->castAs<VectorType>();
  unsigned SrcNumElems = SrcVTy->getNumElements();
  const auto *DstVTy = Call->getType()->castAs<VectorType>();
  unsigned DstNumElems = DstVTy->getNumElements();

  const llvm::fltSemantics &HalfSem =
      S.getASTContext().getFloatTypeSemantics(S.getASTContext().HalfTy);

  // imm[2] == 1 means use MXCSR rounding mode.
  // In that case, we can only evaluate if the conversion is exact.
  int ImmVal = Imm.getZExtValue();
  bool UseMXCSR = (ImmVal & 4) != 0;
  bool IsFPConstrained =
      Call->getFPFeaturesInEffect(S.getASTContext().getLangOpts())
          .isFPConstrained();

  llvm::RoundingMode RM;
  if (!UseMXCSR) {
    switch (ImmVal & 3) {
    case 0:
      RM = llvm::RoundingMode::NearestTiesToEven;
      break;
    case 1:
      RM = llvm::RoundingMode::TowardNegative;
      break;
    case 2:
      RM = llvm::RoundingMode::TowardPositive;
      break;
    case 3:
      RM = llvm::RoundingMode::TowardZero;
      break;
    default:
      llvm_unreachable("Invalid immediate rounding mode");
    }
  } else {
    // For MXCSR, we must check for exactness. We can use any rounding mode
    // for the trial conversion since the result is the same if it's exact.
    RM = llvm::RoundingMode::NearestTiesToEven;
  }

  QualType DstElemQT = Dst.getFieldDesc()->getElemQualType();
  PrimType DstElemT = *S.getContext().classify(DstElemQT);

  for (unsigned I = 0; I != SrcNumElems; ++I) {
    Floating SrcVal = Src.elem<Floating>(I);
    APFloat DstVal = SrcVal.getAPFloat();

    bool LostInfo;
    APFloat::opStatus St = DstVal.convert(HalfSem, RM, &LostInfo);

    if (UseMXCSR && IsFPConstrained && St != APFloat::opOK) {
      S.FFDiag(S.Current->getSource(OpPC),
               diag::note_constexpr_dynamic_rounding);
      return false;
    }

    INT_TYPE_SWITCH_NO_BOOL(DstElemT, {
      // Convert the destination value's bit pattern to an unsigned integer,
      // then reconstruct the element using the target type's 'from' method.
      uint64_t RawBits = DstVal.bitcastToAPInt().getZExtValue();
      Dst.elem<T>(I) = T::from(RawBits);
    });
  }

  // Zero out remaining elements if the destination has more elements
  // (e.g., vcvtps2ph converting 4 floats to 8 shorts).
  if (DstNumElems > SrcNumElems) {
    for (unsigned I = SrcNumElems; I != DstNumElems; ++I) {
      INT_TYPE_SWITCH_NO_BOOL(DstElemT, { Dst.elem<T>(I) = T::from(0); });
    }
  }

  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_multishiftqb(InterpState &S, CodePtr OpPC,
                                              const CallExpr *Call) {
  assert(Call->getNumArgs() == 2);

  QualType ATy = Call->getArg(0)->getType();
  QualType BTy = Call->getArg(1)->getType();
  if (!ATy->isVectorType() || !BTy->isVectorType()) {
    return false;
  }

  const Pointer &BPtr = S.Stk.pop<Pointer>();
  const Pointer &APtr = S.Stk.pop<Pointer>();
  const auto *AVecT = ATy->castAs<VectorType>();
  assert(AVecT->getNumElements() ==
         BTy->castAs<VectorType>()->getNumElements());

  PrimType ElemT = *S.getContext().classify(AVecT->getElementType());

  unsigned NumBytesInQWord = 8;
  unsigned NumBitsInByte = 8;
  unsigned NumBytes = AVecT->getNumElements();
  unsigned NumQWords = NumBytes / NumBytesInQWord;
  const Pointer &Dst = S.Stk.peek<Pointer>();

  for (unsigned QWordId = 0; QWordId != NumQWords; ++QWordId) {
    APInt BQWord(64, 0);
    for (unsigned ByteIdx = 0; ByteIdx != NumBytesInQWord; ++ByteIdx) {
      unsigned Idx = QWordId * NumBytesInQWord + ByteIdx;
      INT_TYPE_SWITCH(ElemT, {
        uint64_t Byte = static_cast<uint64_t>(BPtr.elem<T>(Idx));
        BQWord.insertBits(APInt(8, Byte & 0xFF), ByteIdx * NumBitsInByte);
      });
    }

    for (unsigned ByteIdx = 0; ByteIdx != NumBytesInQWord; ++ByteIdx) {
      unsigned Idx = QWordId * NumBytesInQWord + ByteIdx;
      uint64_t Ctrl = 0;
      INT_TYPE_SWITCH(
          ElemT, { Ctrl = static_cast<uint64_t>(APtr.elem<T>(Idx)) & 0x3F; });

      APInt Byte(8, 0);
      for (unsigned BitIdx = 0; BitIdx != NumBitsInByte; ++BitIdx) {
        Byte.setBitVal(BitIdx, BQWord[(Ctrl + BitIdx) & 0x3F]);
      }
      INT_TYPE_SWITCH(ElemT,
                      { Dst.elem<T>(Idx) = T::from(Byte.getZExtValue()); });
    }
  }

  Dst.initializeAllElements();

  return true;
}

static bool interp_builtin_ia32_gfni_affine(InterpState &S, CodePtr OpPC,
                                            const CallExpr *Call,
                                            bool Inverse) {
  assert(Call->getNumArgs() == 3);
  QualType XType = Call->getArg(0)->getType();
  QualType AType = Call->getArg(1)->getType();
  QualType ImmType = Call->getArg(2)->getType();
  if (!XType->isVectorType() || !AType->isVectorType() ||
      !ImmType->isIntegerType()) {
    return false;
  }

  Pointer X, A;
  APSInt Imm = popToAPSInt(S, Call->getArg(2));
  A = S.Stk.pop<Pointer>();
  X = S.Stk.pop<Pointer>();

  const Pointer &Dst = S.Stk.peek<Pointer>();
  const auto *AVecT = AType->castAs<VectorType>();
  assert(XType->castAs<VectorType>()->getNumElements() ==
         AVecT->getNumElements());
  unsigned NumBytesInQWord = 8;
  unsigned NumBytes = AVecT->getNumElements();
  unsigned NumBitsInQWord = 64;
  unsigned NumQWords = NumBytes / NumBytesInQWord;
  unsigned NumBitsInByte = 8;
  PrimType AElemT = *S.getContext().classify(AVecT->getElementType());

  // computing A*X + Imm
  for (unsigned QWordIdx = 0; QWordIdx != NumQWords; ++QWordIdx) {
    // Extract the QWords from X, A
    APInt XQWord(NumBitsInQWord, 0);
    APInt AQWord(NumBitsInQWord, 0);
    for (unsigned ByteIdx = 0; ByteIdx != NumBytesInQWord; ++ByteIdx) {
      unsigned Idx = QWordIdx * NumBytesInQWord + ByteIdx;
      uint8_t XByte;
      uint8_t AByte;
      INT_TYPE_SWITCH(AElemT, {
        XByte = static_cast<uint8_t>(X.elem<T>(Idx));
        AByte = static_cast<uint8_t>(A.elem<T>(Idx));
      });

      XQWord.insertBits(APInt(NumBitsInByte, XByte), ByteIdx * NumBitsInByte);
      AQWord.insertBits(APInt(NumBitsInByte, AByte), ByteIdx * NumBitsInByte);
    }

    for (unsigned ByteIdx = 0; ByteIdx != NumBytesInQWord; ++ByteIdx) {
      unsigned Idx = QWordIdx * NumBytesInQWord + ByteIdx;
      uint8_t XByte =
          XQWord.lshr(ByteIdx * NumBitsInByte).getLoBits(8).getZExtValue();
      INT_TYPE_SWITCH(AElemT, {
        Dst.elem<T>(Idx) = T::from(GFNIAffine(XByte, AQWord, Imm, Inverse));
      });
    }
  }
  Dst.initializeAllElements();
  return true;
}

static bool interp__builtin_ia32_gfni_mul(InterpState &S, CodePtr OpPC,
                                          const CallExpr *Call) {
  assert(Call->getNumArgs() == 2);

  QualType AType = Call->getArg(0)->getType();
  QualType BType = Call->getArg(1)->getType();
  if (!AType->isVectorType() || !BType->isVectorType()) {
    return false;
  }

  Pointer A, B;
  B = S.Stk.pop<Pointer>();
  A = S.Stk.pop<Pointer>();

  const Pointer &Dst = S.Stk.peek<Pointer>();
  const auto *AVecT = AType->castAs<VectorType>();
  assert(AVecT->getNumElements() ==
         BType->castAs<VectorType>()->getNumElements());

  PrimType AElemT = *S.getContext().classify(AVecT->getElementType());
  unsigned NumBytes = A.getNumElems();

  for (unsigned ByteIdx = 0; ByteIdx != NumBytes; ++ByteIdx) {
    uint8_t AByte, BByte;
    INT_TYPE_SWITCH(AElemT, {
      AByte = static_cast<uint8_t>(A.elem<T>(ByteIdx));
      BByte = static_cast<uint8_t>(B.elem<T>(ByteIdx));
      Dst.elem<T>(ByteIdx) = T::from(GFNIMul(AByte, BByte));
    });
  }

  Dst.initializeAllElements();
  return true;
}

bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
                      uint32_t BuiltinID) {
  if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(BuiltinID))
    return Invalid(S, OpPC);

  const InterpFrame *Frame = S.Current;
  switch (BuiltinID) {
  case Builtin::BI__builtin_is_constant_evaluated:
    return interp__builtin_is_constant_evaluated(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_assume:
  case Builtin::BI__assume:
    return interp__builtin_assume(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_strcmp:
  case Builtin::BIstrcmp:
  case Builtin::BI__builtin_strncmp:
  case Builtin::BIstrncmp:
  case Builtin::BI__builtin_wcsncmp:
  case Builtin::BIwcsncmp:
  case Builtin::BI__builtin_wcscmp:
  case Builtin::BIwcscmp:
    return interp__builtin_strcmp(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_strlen:
  case Builtin::BIstrlen:
  case Builtin::BI__builtin_wcslen:
  case Builtin::BIwcslen:
    return interp__builtin_strlen(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_nan:
  case Builtin::BI__builtin_nanf:
  case Builtin::BI__builtin_nanl:
  case Builtin::BI__builtin_nanf16:
  case Builtin::BI__builtin_nanf128:
    return interp__builtin_nan(S, OpPC, Frame, Call, /*Signaling=*/false);

  case Builtin::BI__builtin_nans:
  case Builtin::BI__builtin_nansf:
  case Builtin::BI__builtin_nansl:
  case Builtin::BI__builtin_nansf16:
  case Builtin::BI__builtin_nansf128:
    return interp__builtin_nan(S, OpPC, Frame, Call, /*Signaling=*/true);

  case Builtin::BI__builtin_huge_val:
  case Builtin::BI__builtin_huge_valf:
  case Builtin::BI__builtin_huge_vall:
  case Builtin::BI__builtin_huge_valf16:
  case Builtin::BI__builtin_huge_valf128:
  case Builtin::BI__builtin_inf:
  case Builtin::BI__builtin_inff:
  case Builtin::BI__builtin_infl:
  case Builtin::BI__builtin_inff16:
  case Builtin::BI__builtin_inff128:
    return interp__builtin_inf(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_copysign:
  case Builtin::BI__builtin_copysignf:
  case Builtin::BI__builtin_copysignl:
  case Builtin::BI__builtin_copysignf128:
    return interp__builtin_copysign(S, OpPC, Frame);

  case Builtin::BI__builtin_fmin:
  case Builtin::BI__builtin_fminf:
  case Builtin::BI__builtin_fminl:
  case Builtin::BI__builtin_fminf16:
  case Builtin::BI__builtin_fminf128:
    return interp__builtin_fmin(S, OpPC, Frame, /*IsNumBuiltin=*/false);

  case Builtin::BI__builtin_fminimum_num:
  case Builtin::BI__builtin_fminimum_numf:
  case Builtin::BI__builtin_fminimum_numl:
  case Builtin::BI__builtin_fminimum_numf16:
  case Builtin::BI__builtin_fminimum_numf128:
    return interp__builtin_fmin(S, OpPC, Frame, /*IsNumBuiltin=*/true);

  case Builtin::BI__builtin_fmax:
  case Builtin::BI__builtin_fmaxf:
  case Builtin::BI__builtin_fmaxl:
  case Builtin::BI__builtin_fmaxf16:
  case Builtin::BI__builtin_fmaxf128:
    return interp__builtin_fmax(S, OpPC, Frame, /*IsNumBuiltin=*/false);

  case Builtin::BI__builtin_fmaximum_num:
  case Builtin::BI__builtin_fmaximum_numf:
  case Builtin::BI__builtin_fmaximum_numl:
  case Builtin::BI__builtin_fmaximum_numf16:
  case Builtin::BI__builtin_fmaximum_numf128:
    return interp__builtin_fmax(S, OpPC, Frame, /*IsNumBuiltin=*/true);

  case Builtin::BI__builtin_isnan:
    return interp__builtin_isnan(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_issignaling:
    return interp__builtin_issignaling(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_isinf:
    return interp__builtin_isinf(S, OpPC, Frame, /*Sign=*/false, Call);

  case Builtin::BI__builtin_isinf_sign:
    return interp__builtin_isinf(S, OpPC, Frame, /*Sign=*/true, Call);

  case Builtin::BI__builtin_isfinite:
    return interp__builtin_isfinite(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_isnormal:
    return interp__builtin_isnormal(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_issubnormal:
    return interp__builtin_issubnormal(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_iszero:
    return interp__builtin_iszero(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_signbit:
  case Builtin::BI__builtin_signbitf:
  case Builtin::BI__builtin_signbitl:
    return interp__builtin_signbit(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_isgreater:
  case Builtin::BI__builtin_isgreaterequal:
  case Builtin::BI__builtin_isless:
  case Builtin::BI__builtin_islessequal:
  case Builtin::BI__builtin_islessgreater:
  case Builtin::BI__builtin_isunordered:
    return interp_floating_comparison(S, OpPC, Call, BuiltinID);

  case Builtin::BI__builtin_isfpclass:
    return interp__builtin_isfpclass(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_fpclassify:
    return interp__builtin_fpclassify(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_fabs:
  case Builtin::BI__builtin_fabsf:
  case Builtin::BI__builtin_fabsl:
  case Builtin::BI__builtin_fabsf128:
    return interp__builtin_fabs(S, OpPC, Frame);

  case Builtin::BI__builtin_abs:
  case Builtin::BI__builtin_labs:
  case Builtin::BI__builtin_llabs:
    return interp__builtin_abs(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_popcount:
  case Builtin::BI__builtin_popcountl:
  case Builtin::BI__builtin_popcountll:
  case Builtin::BI__builtin_popcountg:
  case Builtin::BI__popcnt16: // Microsoft variants of popcount
  case Builtin::BI__popcnt:
  case Builtin::BI__popcnt64:
    return interp__builtin_popcount(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_parity:
  case Builtin::BI__builtin_parityl:
  case Builtin::BI__builtin_parityll:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Val) {
          return APInt(Val.getBitWidth(), Val.popcount() % 2);
        });
  case Builtin::BI__builtin_clrsb:
  case Builtin::BI__builtin_clrsbl:
  case Builtin::BI__builtin_clrsbll:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Val) {
          return APInt(Val.getBitWidth(),
                       Val.getBitWidth() - Val.getSignificantBits());
        });
  case Builtin::BI__builtin_bitreverse8:
  case Builtin::BI__builtin_bitreverse16:
  case Builtin::BI__builtin_bitreverse32:
  case Builtin::BI__builtin_bitreverse64:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Val) { return Val.reverseBits(); });

  case Builtin::BI__builtin_classify_type:
    return interp__builtin_classify_type(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_expect:
  case Builtin::BI__builtin_expect_with_probability:
    return interp__builtin_expect(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_rotateleft8:
  case Builtin::BI__builtin_rotateleft16:
  case Builtin::BI__builtin_rotateleft32:
  case Builtin::BI__builtin_rotateleft64:
  case Builtin::BI_rotl8: // Microsoft variants of rotate left
  case Builtin::BI_rotl16:
  case Builtin::BI_rotl:
  case Builtin::BI_lrotl:
  case Builtin::BI_rotl64:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &Value, const APSInt &Amount) {
          return Value.rotl(Amount);
        });

  case Builtin::BI__builtin_rotateright8:
  case Builtin::BI__builtin_rotateright16:
  case Builtin::BI__builtin_rotateright32:
  case Builtin::BI__builtin_rotateright64:
  case Builtin::BI_rotr8: // Microsoft variants of rotate right
  case Builtin::BI_rotr16:
  case Builtin::BI_rotr:
  case Builtin::BI_lrotr:
  case Builtin::BI_rotr64:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &Value, const APSInt &Amount) {
          return Value.rotr(Amount);
        });

  case Builtin::BI__builtin_ffs:
  case Builtin::BI__builtin_ffsl:
  case Builtin::BI__builtin_ffsll:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Val) {
          return APInt(Val.getBitWidth(),
                       Val.isZero() ? 0u : Val.countTrailingZeros() + 1u);
        });

  case Builtin::BIaddressof:
  case Builtin::BI__addressof:
  case Builtin::BI__builtin_addressof:
    assert(isNoopBuiltin(BuiltinID));
    return interp__builtin_addressof(S, OpPC, Frame, Call);

  case Builtin::BIas_const:
  case Builtin::BIforward:
  case Builtin::BIforward_like:
  case Builtin::BImove:
  case Builtin::BImove_if_noexcept:
    assert(isNoopBuiltin(BuiltinID));
    return interp__builtin_move(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_eh_return_data_regno:
    return interp__builtin_eh_return_data_regno(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_launder:
    assert(isNoopBuiltin(BuiltinID));
    return true;

  case Builtin::BI__builtin_add_overflow:
  case Builtin::BI__builtin_sub_overflow:
  case Builtin::BI__builtin_mul_overflow:
  case Builtin::BI__builtin_sadd_overflow:
  case Builtin::BI__builtin_uadd_overflow:
  case Builtin::BI__builtin_uaddl_overflow:
  case Builtin::BI__builtin_uaddll_overflow:
  case Builtin::BI__builtin_usub_overflow:
  case Builtin::BI__builtin_usubl_overflow:
  case Builtin::BI__builtin_usubll_overflow:
  case Builtin::BI__builtin_umul_overflow:
  case Builtin::BI__builtin_umull_overflow:
  case Builtin::BI__builtin_umulll_overflow:
  case Builtin::BI__builtin_saddl_overflow:
  case Builtin::BI__builtin_saddll_overflow:
  case Builtin::BI__builtin_ssub_overflow:
  case Builtin::BI__builtin_ssubl_overflow:
  case Builtin::BI__builtin_ssubll_overflow:
  case Builtin::BI__builtin_smul_overflow:
  case Builtin::BI__builtin_smull_overflow:
  case Builtin::BI__builtin_smulll_overflow:
    return interp__builtin_overflowop(S, OpPC, Call, BuiltinID);

  case Builtin::BI__builtin_addcb:
  case Builtin::BI__builtin_addcs:
  case Builtin::BI__builtin_addc:
  case Builtin::BI__builtin_addcl:
  case Builtin::BI__builtin_addcll:
  case Builtin::BI__builtin_subcb:
  case Builtin::BI__builtin_subcs:
  case Builtin::BI__builtin_subc:
  case Builtin::BI__builtin_subcl:
  case Builtin::BI__builtin_subcll:
    return interp__builtin_carryop(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_clz:
  case Builtin::BI__builtin_clzl:
  case Builtin::BI__builtin_clzll:
  case Builtin::BI__builtin_clzs:
  case Builtin::BI__builtin_clzg:
  case Builtin::BI__lzcnt16: // Microsoft variants of count leading-zeroes
  case Builtin::BI__lzcnt:
  case Builtin::BI__lzcnt64:
    return interp__builtin_clz(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_ctz:
  case Builtin::BI__builtin_ctzl:
  case Builtin::BI__builtin_ctzll:
  case Builtin::BI__builtin_ctzs:
  case Builtin::BI__builtin_ctzg:
    return interp__builtin_ctz(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_elementwise_clzg:
  case Builtin::BI__builtin_elementwise_ctzg:
    return interp__builtin_elementwise_countzeroes(S, OpPC, Frame, Call,
                                                   BuiltinID);
  case Builtin::BI__builtin_bswapg:
  case Builtin::BI__builtin_bswap16:
  case Builtin::BI__builtin_bswap32:
  case Builtin::BI__builtin_bswap64:
    return interp__builtin_bswap(S, OpPC, Frame, Call);

  case Builtin::BI__atomic_always_lock_free:
  case Builtin::BI__atomic_is_lock_free:
    return interp__builtin_atomic_lock_free(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__c11_atomic_is_lock_free:
    return interp__builtin_c11_atomic_is_lock_free(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_complex:
    return interp__builtin_complex(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_is_aligned:
  case Builtin::BI__builtin_align_up:
  case Builtin::BI__builtin_align_down:
    return interp__builtin_is_aligned_up_down(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_assume_aligned:
    return interp__builtin_assume_aligned(S, OpPC, Frame, Call);

  case clang::X86::BI__builtin_ia32_bextr_u32:
  case clang::X86::BI__builtin_ia32_bextr_u64:
  case clang::X86::BI__builtin_ia32_bextri_u32:
  case clang::X86::BI__builtin_ia32_bextri_u64:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &Val, const APSInt &Idx) {
          unsigned BitWidth = Val.getBitWidth();
          uint64_t Shift = Idx.extractBitsAsZExtValue(8, 0);
          uint64_t Length = Idx.extractBitsAsZExtValue(8, 8);
          if (Length > BitWidth) {
            Length = BitWidth;
          }

          // Handle out of bounds cases.
          if (Length == 0 || Shift >= BitWidth)
            return APInt(BitWidth, 0);

          uint64_t Result = Val.getZExtValue() >> Shift;
          Result &= llvm::maskTrailingOnes<uint64_t>(Length);
          return APInt(BitWidth, Result);
        });

  case clang::X86::BI__builtin_ia32_bzhi_si:
  case clang::X86::BI__builtin_ia32_bzhi_di:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &Val, const APSInt &Idx) {
          unsigned BitWidth = Val.getBitWidth();
          uint64_t Index = Idx.extractBitsAsZExtValue(8, 0);
          APSInt Result = Val;

          if (Index < BitWidth)
            Result.clearHighBits(BitWidth - Index);

          return Result;
        });

  case clang::X86::BI__builtin_ia32_ktestcqi:
  case clang::X86::BI__builtin_ia32_ktestchi:
  case clang::X86::BI__builtin_ia32_ktestcsi:
  case clang::X86::BI__builtin_ia32_ktestcdi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B) {
          return APInt(sizeof(unsigned char) * 8, (~A & B) == 0);
        });

  case clang::X86::BI__builtin_ia32_ktestzqi:
  case clang::X86::BI__builtin_ia32_ktestzhi:
  case clang::X86::BI__builtin_ia32_ktestzsi:
  case clang::X86::BI__builtin_ia32_ktestzdi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B) {
          return APInt(sizeof(unsigned char) * 8, (A & B) == 0);
        });

  case clang::X86::BI__builtin_ia32_kortestcqi:
  case clang::X86::BI__builtin_ia32_kortestchi:
  case clang::X86::BI__builtin_ia32_kortestcsi:
  case clang::X86::BI__builtin_ia32_kortestcdi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B) {
          return APInt(sizeof(unsigned char) * 8, ~(A | B) == 0);
        });

  case clang::X86::BI__builtin_ia32_kortestzqi:
  case clang::X86::BI__builtin_ia32_kortestzhi:
  case clang::X86::BI__builtin_ia32_kortestzsi:
  case clang::X86::BI__builtin_ia32_kortestzdi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B) {
          return APInt(sizeof(unsigned char) * 8, (A | B) == 0);
        });

  case clang::X86::BI__builtin_ia32_kshiftliqi:
  case clang::X86::BI__builtin_ia32_kshiftlihi:
  case clang::X86::BI__builtin_ia32_kshiftlisi:
  case clang::X86::BI__builtin_ia32_kshiftlidi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          unsigned Amt = RHS.getZExtValue() & 0xFF;
          if (Amt >= LHS.getBitWidth())
            return APInt::getZero(LHS.getBitWidth());
          return LHS.shl(Amt);
        });

  case clang::X86::BI__builtin_ia32_kshiftriqi:
  case clang::X86::BI__builtin_ia32_kshiftrihi:
  case clang::X86::BI__builtin_ia32_kshiftrisi:
  case clang::X86::BI__builtin_ia32_kshiftridi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          unsigned Amt = RHS.getZExtValue() & 0xFF;
          if (Amt >= LHS.getBitWidth())
            return APInt::getZero(LHS.getBitWidth());
          return LHS.lshr(Amt);
        });

  case clang::X86::BI__builtin_ia32_lzcnt_u16:
  case clang::X86::BI__builtin_ia32_lzcnt_u32:
  case clang::X86::BI__builtin_ia32_lzcnt_u64:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Src) {
          return APInt(Src.getBitWidth(), Src.countLeadingZeros());
        });

  case clang::X86::BI__builtin_ia32_tzcnt_u16:
  case clang::X86::BI__builtin_ia32_tzcnt_u32:
  case clang::X86::BI__builtin_ia32_tzcnt_u64:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Src) {
          return APInt(Src.getBitWidth(), Src.countTrailingZeros());
        });

  case clang::X86::BI__builtin_ia32_pdep_si:
  case clang::X86::BI__builtin_ia32_pdep_di:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &Val, const APSInt &Mask) {
          unsigned BitWidth = Val.getBitWidth();
          APInt Result = APInt::getZero(BitWidth);

          for (unsigned I = 0, P = 0; I != BitWidth; ++I) {
            if (Mask[I])
              Result.setBitVal(I, Val[P++]);
          }

          return Result;
        });

  case clang::X86::BI__builtin_ia32_pext_si:
  case clang::X86::BI__builtin_ia32_pext_di:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &Val, const APSInt &Mask) {
          unsigned BitWidth = Val.getBitWidth();
          APInt Result = APInt::getZero(BitWidth);

          for (unsigned I = 0, P = 0; I != BitWidth; ++I) {
            if (Mask[I])
              Result.setBitVal(P++, Val[I]);
          }

          return Result;
        });

  case clang::X86::BI__builtin_ia32_addcarryx_u32:
  case clang::X86::BI__builtin_ia32_addcarryx_u64:
  case clang::X86::BI__builtin_ia32_subborrow_u32:
  case clang::X86::BI__builtin_ia32_subborrow_u64:
    return interp__builtin_ia32_addcarry_subborrow(S, OpPC, Frame, Call,
                                                   BuiltinID);

  case Builtin::BI__builtin_os_log_format_buffer_size:
    return interp__builtin_os_log_format_buffer_size(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_ptrauth_string_discriminator:
    return interp__builtin_ptrauth_string_discriminator(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_infer_alloc_token:
    return interp__builtin_infer_alloc_token(S, OpPC, Frame, Call);

  case Builtin::BI__noop:
    pushInteger(S, 0, Call->getType());
    return true;

  case Builtin::BI__builtin_operator_new:
    return interp__builtin_operator_new(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_operator_delete:
    return interp__builtin_operator_delete(S, OpPC, Frame, Call);

  case Builtin::BI__arithmetic_fence:
    return interp__builtin_arithmetic_fence(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_reduce_add:
  case Builtin::BI__builtin_reduce_mul:
  case Builtin::BI__builtin_reduce_and:
  case Builtin::BI__builtin_reduce_or:
  case Builtin::BI__builtin_reduce_xor:
  case Builtin::BI__builtin_reduce_min:
  case Builtin::BI__builtin_reduce_max:
    return interp__builtin_vector_reduce(S, OpPC, Call, BuiltinID);

  case Builtin::BI__builtin_elementwise_popcount:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Src) {
          return APInt(Src.getBitWidth(), Src.popcount());
        });
  case Builtin::BI__builtin_elementwise_bitreverse:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Src) { return Src.reverseBits(); });

  case Builtin::BI__builtin_elementwise_abs:
    return interp__builtin_elementwise_abs(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_memcpy:
  case Builtin::BImemcpy:
  case Builtin::BI__builtin_wmemcpy:
  case Builtin::BIwmemcpy:
  case Builtin::BI__builtin_memmove:
  case Builtin::BImemmove:
  case Builtin::BI__builtin_wmemmove:
  case Builtin::BIwmemmove:
    return interp__builtin_memcpy(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BI__builtin_memcmp:
  case Builtin::BImemcmp:
  case Builtin::BI__builtin_bcmp:
  case Builtin::BIbcmp:
  case Builtin::BI__builtin_wmemcmp:
  case Builtin::BIwmemcmp:
    return interp__builtin_memcmp(S, OpPC, Frame, Call, BuiltinID);

  case Builtin::BImemchr:
  case Builtin::BI__builtin_memchr:
  case Builtin::BIstrchr:
  case Builtin::BI__builtin_strchr:
  case Builtin::BIwmemchr:
  case Builtin::BI__builtin_wmemchr:
  case Builtin::BIwcschr:
  case Builtin::BI__builtin_wcschr:
  case Builtin::BI__builtin_char_memchr:
    return interp__builtin_memchr(S, OpPC, Call, BuiltinID);

  case Builtin::BI__builtin_object_size:
  case Builtin::BI__builtin_dynamic_object_size:
    return interp__builtin_object_size(S, OpPC, Frame, Call);

  case Builtin::BI__builtin_is_within_lifetime:
    return interp__builtin_is_within_lifetime(S, OpPC, Call);

  case Builtin::BI__builtin_elementwise_add_sat:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          return LHS.isSigned() ? LHS.sadd_sat(RHS) : LHS.uadd_sat(RHS);
        });

  case Builtin::BI__builtin_elementwise_sub_sat:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          return LHS.isSigned() ? LHS.ssub_sat(RHS) : LHS.usub_sat(RHS);
        });
  case X86::BI__builtin_ia32_extract128i256:
  case X86::BI__builtin_ia32_vextractf128_pd256:
  case X86::BI__builtin_ia32_vextractf128_ps256:
  case X86::BI__builtin_ia32_vextractf128_si256:
    return interp__builtin_x86_extract_vector(S, OpPC, Call, BuiltinID);

  case X86::BI__builtin_ia32_extractf32x4_256_mask:
  case X86::BI__builtin_ia32_extractf32x4_mask:
  case X86::BI__builtin_ia32_extractf32x8_mask:
  case X86::BI__builtin_ia32_extractf64x2_256_mask:
  case X86::BI__builtin_ia32_extractf64x2_512_mask:
  case X86::BI__builtin_ia32_extractf64x4_mask:
  case X86::BI__builtin_ia32_extracti32x4_256_mask:
  case X86::BI__builtin_ia32_extracti32x4_mask:
  case X86::BI__builtin_ia32_extracti32x8_mask:
  case X86::BI__builtin_ia32_extracti64x2_256_mask:
  case X86::BI__builtin_ia32_extracti64x2_512_mask:
  case X86::BI__builtin_ia32_extracti64x4_mask:
    return interp__builtin_x86_extract_vector_masked(S, OpPC, Call, BuiltinID);

  case clang::X86::BI__builtin_ia32_pmulhrsw128:
  case clang::X86::BI__builtin_ia32_pmulhrsw256:
  case clang::X86::BI__builtin_ia32_pmulhrsw512:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          return (llvm::APIntOps::mulsExtended(LHS, RHS).ashr(14) + 1)
              .extractBits(16, 1);
        });

  case clang::X86::BI__builtin_ia32_movmskps:
  case clang::X86::BI__builtin_ia32_movmskpd:
  case clang::X86::BI__builtin_ia32_pmovmskb128:
  case clang::X86::BI__builtin_ia32_pmovmskb256:
  case clang::X86::BI__builtin_ia32_movmskps256:
  case clang::X86::BI__builtin_ia32_movmskpd256: {
    return interp__builtin_ia32_movmsk_op(S, OpPC, Call);
  }

  case X86::BI__builtin_ia32_psignb128:
  case X86::BI__builtin_ia32_psignb256:
  case X86::BI__builtin_ia32_psignw128:
  case X86::BI__builtin_ia32_psignw256:
  case X86::BI__builtin_ia32_psignd128:
  case X86::BI__builtin_ia32_psignd256:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APInt &AElem, const APInt &BElem) {
          if (BElem.isZero())
            return APInt::getZero(AElem.getBitWidth());
          if (BElem.isNegative())
            return -AElem;
          return AElem;
        });

  case clang::X86::BI__builtin_ia32_pavgb128:
  case clang::X86::BI__builtin_ia32_pavgw128:
  case clang::X86::BI__builtin_ia32_pavgb256:
  case clang::X86::BI__builtin_ia32_pavgw256:
  case clang::X86::BI__builtin_ia32_pavgb512:
  case clang::X86::BI__builtin_ia32_pavgw512:
    return interp__builtin_elementwise_int_binop(S, OpPC, Call,
                                                 llvm::APIntOps::avgCeilU);

  case clang::X86::BI__builtin_ia32_pmaddubsw128:
  case clang::X86::BI__builtin_ia32_pmaddubsw256:
  case clang::X86::BI__builtin_ia32_pmaddubsw512:
    return interp__builtin_ia32_pmul(
        S, OpPC, Call,
        [](const APSInt &LoLHS, const APSInt &HiLHS, const APSInt &LoRHS,
           const APSInt &HiRHS) {
          unsigned BitWidth = 2 * LoLHS.getBitWidth();
          return (LoLHS.zext(BitWidth) * LoRHS.sext(BitWidth))
              .sadd_sat((HiLHS.zext(BitWidth) * HiRHS.sext(BitWidth)));
        });

  case clang::X86::BI__builtin_ia32_pmaddwd128:
  case clang::X86::BI__builtin_ia32_pmaddwd256:
  case clang::X86::BI__builtin_ia32_pmaddwd512:
    return interp__builtin_ia32_pmul(
        S, OpPC, Call,
        [](const APSInt &LoLHS, const APSInt &HiLHS, const APSInt &LoRHS,
           const APSInt &HiRHS) {
          unsigned BitWidth = 2 * LoLHS.getBitWidth();
          return (LoLHS.sext(BitWidth) * LoRHS.sext(BitWidth)) +
                 (HiLHS.sext(BitWidth) * HiRHS.sext(BitWidth));
        });

  case clang::X86::BI__builtin_ia32_pmulhuw128:
  case clang::X86::BI__builtin_ia32_pmulhuw256:
  case clang::X86::BI__builtin_ia32_pmulhuw512:
    return interp__builtin_elementwise_int_binop(S, OpPC, Call,
                                                 llvm::APIntOps::mulhu);

  case clang::X86::BI__builtin_ia32_pmulhw128:
  case clang::X86::BI__builtin_ia32_pmulhw256:
  case clang::X86::BI__builtin_ia32_pmulhw512:
    return interp__builtin_elementwise_int_binop(S, OpPC, Call,
                                                 llvm::APIntOps::mulhs);

  case clang::X86::BI__builtin_ia32_psllv2di:
  case clang::X86::BI__builtin_ia32_psllv4di:
  case clang::X86::BI__builtin_ia32_psllv4si:
  case clang::X86::BI__builtin_ia32_psllv8di:
  case clang::X86::BI__builtin_ia32_psllv8hi:
  case clang::X86::BI__builtin_ia32_psllv8si:
  case clang::X86::BI__builtin_ia32_psllv16hi:
  case clang::X86::BI__builtin_ia32_psllv16si:
  case clang::X86::BI__builtin_ia32_psllv32hi:
  case clang::X86::BI__builtin_ia32_psllwi128:
  case clang::X86::BI__builtin_ia32_psllwi256:
  case clang::X86::BI__builtin_ia32_psllwi512:
  case clang::X86::BI__builtin_ia32_pslldi128:
  case clang::X86::BI__builtin_ia32_pslldi256:
  case clang::X86::BI__builtin_ia32_pslldi512:
  case clang::X86::BI__builtin_ia32_psllqi128:
  case clang::X86::BI__builtin_ia32_psllqi256:
  case clang::X86::BI__builtin_ia32_psllqi512:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          if (RHS.uge(LHS.getBitWidth())) {
            return APInt::getZero(LHS.getBitWidth());
          }
          return LHS.shl(RHS.getZExtValue());
        });

  case clang::X86::BI__builtin_ia32_psrav4si:
  case clang::X86::BI__builtin_ia32_psrav8di:
  case clang::X86::BI__builtin_ia32_psrav8hi:
  case clang::X86::BI__builtin_ia32_psrav8si:
  case clang::X86::BI__builtin_ia32_psrav16hi:
  case clang::X86::BI__builtin_ia32_psrav16si:
  case clang::X86::BI__builtin_ia32_psrav32hi:
  case clang::X86::BI__builtin_ia32_psravq128:
  case clang::X86::BI__builtin_ia32_psravq256:
  case clang::X86::BI__builtin_ia32_psrawi128:
  case clang::X86::BI__builtin_ia32_psrawi256:
  case clang::X86::BI__builtin_ia32_psrawi512:
  case clang::X86::BI__builtin_ia32_psradi128:
  case clang::X86::BI__builtin_ia32_psradi256:
  case clang::X86::BI__builtin_ia32_psradi512:
  case clang::X86::BI__builtin_ia32_psraqi128:
  case clang::X86::BI__builtin_ia32_psraqi256:
  case clang::X86::BI__builtin_ia32_psraqi512:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          if (RHS.uge(LHS.getBitWidth())) {
            return LHS.ashr(LHS.getBitWidth() - 1);
          }
          return LHS.ashr(RHS.getZExtValue());
        });

  case clang::X86::BI__builtin_ia32_psrlv2di:
  case clang::X86::BI__builtin_ia32_psrlv4di:
  case clang::X86::BI__builtin_ia32_psrlv4si:
  case clang::X86::BI__builtin_ia32_psrlv8di:
  case clang::X86::BI__builtin_ia32_psrlv8hi:
  case clang::X86::BI__builtin_ia32_psrlv8si:
  case clang::X86::BI__builtin_ia32_psrlv16hi:
  case clang::X86::BI__builtin_ia32_psrlv16si:
  case clang::X86::BI__builtin_ia32_psrlv32hi:
  case clang::X86::BI__builtin_ia32_psrlwi128:
  case clang::X86::BI__builtin_ia32_psrlwi256:
  case clang::X86::BI__builtin_ia32_psrlwi512:
  case clang::X86::BI__builtin_ia32_psrldi128:
  case clang::X86::BI__builtin_ia32_psrldi256:
  case clang::X86::BI__builtin_ia32_psrldi512:
  case clang::X86::BI__builtin_ia32_psrlqi128:
  case clang::X86::BI__builtin_ia32_psrlqi256:
  case clang::X86::BI__builtin_ia32_psrlqi512:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &LHS, const APSInt &RHS) {
          if (RHS.uge(LHS.getBitWidth())) {
            return APInt::getZero(LHS.getBitWidth());
          }
          return LHS.lshr(RHS.getZExtValue());
        });
  case clang::X86::BI__builtin_ia32_packsswb128:
  case clang::X86::BI__builtin_ia32_packsswb256:
  case clang::X86::BI__builtin_ia32_packsswb512:
  case clang::X86::BI__builtin_ia32_packssdw128:
  case clang::X86::BI__builtin_ia32_packssdw256:
  case clang::X86::BI__builtin_ia32_packssdw512:
    return interp__builtin_x86_pack(S, OpPC, Call, [](const APSInt &Src) {
      return APInt(Src).truncSSat(Src.getBitWidth() / 2);
    });
  case clang::X86::BI__builtin_ia32_packusdw128:
  case clang::X86::BI__builtin_ia32_packusdw256:
  case clang::X86::BI__builtin_ia32_packusdw512:
  case clang::X86::BI__builtin_ia32_packuswb128:
  case clang::X86::BI__builtin_ia32_packuswb256:
  case clang::X86::BI__builtin_ia32_packuswb512:
    return interp__builtin_x86_pack(S, OpPC, Call, [](const APSInt &Src) {
      unsigned DstBits = Src.getBitWidth() / 2;
      if (Src.isNegative())
        return APInt::getZero(DstBits);
      if (Src.isIntN(DstBits))
        return APInt(Src).trunc(DstBits);
      return APInt::getAllOnes(DstBits);
    });

  case clang::X86::BI__builtin_ia32_selectss_128:
  case clang::X86::BI__builtin_ia32_selectsd_128:
  case clang::X86::BI__builtin_ia32_selectsh_128:
  case clang::X86::BI__builtin_ia32_selectsbf_128:
    return interp__builtin_select_scalar(S, Call);
  case clang::X86::BI__builtin_ia32_vprotbi:
  case clang::X86::BI__builtin_ia32_vprotdi:
  case clang::X86::BI__builtin_ia32_vprotqi:
  case clang::X86::BI__builtin_ia32_vprotwi:
  case clang::X86::BI__builtin_ia32_prold128:
  case clang::X86::BI__builtin_ia32_prold256:
  case clang::X86::BI__builtin_ia32_prold512:
  case clang::X86::BI__builtin_ia32_prolq128:
  case clang::X86::BI__builtin_ia32_prolq256:
  case clang::X86::BI__builtin_ia32_prolq512:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS.rotl(RHS); });

  case clang::X86::BI__builtin_ia32_prord128:
  case clang::X86::BI__builtin_ia32_prord256:
  case clang::X86::BI__builtin_ia32_prord512:
  case clang::X86::BI__builtin_ia32_prorq128:
  case clang::X86::BI__builtin_ia32_prorq256:
  case clang::X86::BI__builtin_ia32_prorq512:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS.rotr(RHS); });

  case Builtin::BI__builtin_elementwise_max:
  case Builtin::BI__builtin_elementwise_min:
    return interp__builtin_elementwise_maxmin(S, OpPC, Call, BuiltinID);

  case clang::X86::BI__builtin_ia32_phaddw128:
  case clang::X86::BI__builtin_ia32_phaddw256:
  case clang::X86::BI__builtin_ia32_phaddd128:
  case clang::X86::BI__builtin_ia32_phaddd256:
    return interp_builtin_horizontal_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS + RHS; });
  case clang::X86::BI__builtin_ia32_phaddsw128:
  case clang::X86::BI__builtin_ia32_phaddsw256:
    return interp_builtin_horizontal_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS.sadd_sat(RHS); });
  case clang::X86::BI__builtin_ia32_phsubw128:
  case clang::X86::BI__builtin_ia32_phsubw256:
  case clang::X86::BI__builtin_ia32_phsubd128:
  case clang::X86::BI__builtin_ia32_phsubd256:
    return interp_builtin_horizontal_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS - RHS; });
  case clang::X86::BI__builtin_ia32_phsubsw128:
  case clang::X86::BI__builtin_ia32_phsubsw256:
    return interp_builtin_horizontal_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS.ssub_sat(RHS); });
  case clang::X86::BI__builtin_ia32_haddpd:
  case clang::X86::BI__builtin_ia32_haddps:
  case clang::X86::BI__builtin_ia32_haddpd256:
  case clang::X86::BI__builtin_ia32_haddps256:
    return interp_builtin_horizontal_fp_binop(
        S, OpPC, Call,
        [](const APFloat &LHS, const APFloat &RHS, llvm::RoundingMode RM) {
          APFloat F = LHS;
          F.add(RHS, RM);
          return F;
        });
  case clang::X86::BI__builtin_ia32_hsubpd:
  case clang::X86::BI__builtin_ia32_hsubps:
  case clang::X86::BI__builtin_ia32_hsubpd256:
  case clang::X86::BI__builtin_ia32_hsubps256:
    return interp_builtin_horizontal_fp_binop(
        S, OpPC, Call,
        [](const APFloat &LHS, const APFloat &RHS, llvm::RoundingMode RM) {
          APFloat F = LHS;
          F.subtract(RHS, RM);
          return F;
        });
  case clang::X86::BI__builtin_ia32_addsubpd:
  case clang::X86::BI__builtin_ia32_addsubps:
  case clang::X86::BI__builtin_ia32_addsubpd256:
  case clang::X86::BI__builtin_ia32_addsubps256:
    return interp__builtin_ia32_addsub(S, OpPC, Call);

  case clang::X86::BI__builtin_ia32_pmuldq128:
  case clang::X86::BI__builtin_ia32_pmuldq256:
  case clang::X86::BI__builtin_ia32_pmuldq512:
    return interp__builtin_ia32_pmul(
        S, OpPC, Call,
        [](const APSInt &LoLHS, const APSInt &HiLHS, const APSInt &LoRHS,
           const APSInt &HiRHS) {
          return llvm::APIntOps::mulsExtended(LoLHS, LoRHS);
        });

  case clang::X86::BI__builtin_ia32_pmuludq128:
  case clang::X86::BI__builtin_ia32_pmuludq256:
  case clang::X86::BI__builtin_ia32_pmuludq512:
    return interp__builtin_ia32_pmul(
        S, OpPC, Call,
        [](const APSInt &LoLHS, const APSInt &HiLHS, const APSInt &LoRHS,
           const APSInt &HiRHS) {
          return llvm::APIntOps::muluExtended(LoLHS, LoRHS);
        });

  case clang::X86::BI__builtin_ia32_pclmulqdq128:
  case clang::X86::BI__builtin_ia32_pclmulqdq256:
  case clang::X86::BI__builtin_ia32_pclmulqdq512:
    return interp__builtin_ia32_pclmulqdq(S, OpPC, Call);

  case Builtin::BI__builtin_elementwise_fma:
    return interp__builtin_elementwise_triop_fp(
        S, OpPC, Call,
        [](const APFloat &X, const APFloat &Y, const APFloat &Z,
           llvm::RoundingMode RM) {
          APFloat F = X;
          F.fusedMultiplyAdd(Y, Z, RM);
          return F;
        });

  case X86::BI__builtin_ia32_vpmadd52luq128:
  case X86::BI__builtin_ia32_vpmadd52luq256:
  case X86::BI__builtin_ia32_vpmadd52luq512:
    return interp__builtin_elementwise_triop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B, const APSInt &C) {
          return A + (B.trunc(52) * C.trunc(52)).zext(64);
        });
  case X86::BI__builtin_ia32_vpmadd52huq128:
  case X86::BI__builtin_ia32_vpmadd52huq256:
  case X86::BI__builtin_ia32_vpmadd52huq512:
    return interp__builtin_elementwise_triop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B, const APSInt &C) {
          return A + llvm::APIntOps::mulhu(B.trunc(52), C.trunc(52)).zext(64);
        });

  case X86::BI__builtin_ia32_vpshldd128:
  case X86::BI__builtin_ia32_vpshldd256:
  case X86::BI__builtin_ia32_vpshldd512:
  case X86::BI__builtin_ia32_vpshldq128:
  case X86::BI__builtin_ia32_vpshldq256:
  case X86::BI__builtin_ia32_vpshldq512:
  case X86::BI__builtin_ia32_vpshldw128:
  case X86::BI__builtin_ia32_vpshldw256:
  case X86::BI__builtin_ia32_vpshldw512:
    return interp__builtin_elementwise_triop(
        S, OpPC, Call,
        [](const APSInt &Hi, const APSInt &Lo, const APSInt &Amt) {
          return llvm::APIntOps::fshl(Hi, Lo, Amt);
        });

  case X86::BI__builtin_ia32_vpshrdd128:
  case X86::BI__builtin_ia32_vpshrdd256:
  case X86::BI__builtin_ia32_vpshrdd512:
  case X86::BI__builtin_ia32_vpshrdq128:
  case X86::BI__builtin_ia32_vpshrdq256:
  case X86::BI__builtin_ia32_vpshrdq512:
  case X86::BI__builtin_ia32_vpshrdw128:
  case X86::BI__builtin_ia32_vpshrdw256:
  case X86::BI__builtin_ia32_vpshrdw512:
    // NOTE: Reversed Hi/Lo operands.
    return interp__builtin_elementwise_triop(
        S, OpPC, Call,
        [](const APSInt &Lo, const APSInt &Hi, const APSInt &Amt) {
          return llvm::APIntOps::fshr(Hi, Lo, Amt);
        });
  case X86::BI__builtin_ia32_vpconflictsi_128:
  case X86::BI__builtin_ia32_vpconflictsi_256:
  case X86::BI__builtin_ia32_vpconflictsi_512:
  case X86::BI__builtin_ia32_vpconflictdi_128:
  case X86::BI__builtin_ia32_vpconflictdi_256:
  case X86::BI__builtin_ia32_vpconflictdi_512:
    return interp__builtin_ia32_vpconflict(S, OpPC, Call);
  case clang::X86::BI__builtin_ia32_blendpd:
  case clang::X86::BI__builtin_ia32_blendpd256:
  case clang::X86::BI__builtin_ia32_blendps:
  case clang::X86::BI__builtin_ia32_blendps256:
  case clang::X86::BI__builtin_ia32_pblendw128:
  case clang::X86::BI__builtin_ia32_pblendw256:
  case clang::X86::BI__builtin_ia32_pblendd128:
  case clang::X86::BI__builtin_ia32_pblendd256:
    return interp__builtin_ia32_shuffle_generic(
      S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
        // Bit index for mask.
        unsigned MaskBit = (ShuffleMask >> (DstIdx % 8)) & 0x1;
        unsigned SrcVecIdx = MaskBit ? 1 : 0;  // 1 = TrueVec, 0 = FalseVec
        return std::pair<unsigned, int>{SrcVecIdx, static_cast<int>(DstIdx)};
      });



  case clang::X86::BI__builtin_ia32_blendvpd:
  case clang::X86::BI__builtin_ia32_blendvpd256:
  case clang::X86::BI__builtin_ia32_blendvps:
  case clang::X86::BI__builtin_ia32_blendvps256:
    return interp__builtin_elementwise_triop_fp(
        S, OpPC, Call,
        [](const APFloat &F, const APFloat &T, const APFloat &C,
           llvm::RoundingMode) { return C.isNegative() ? T : F; });

  case clang::X86::BI__builtin_ia32_pblendvb128:
  case clang::X86::BI__builtin_ia32_pblendvb256:
    return interp__builtin_elementwise_triop(
        S, OpPC, Call, [](const APSInt &F, const APSInt &T, const APSInt &C) {
          return ((APInt)C).isNegative() ? T : F;
        });
  case X86::BI__builtin_ia32_ptestz128:
  case X86::BI__builtin_ia32_ptestz256:
  case X86::BI__builtin_ia32_vtestzps:
  case X86::BI__builtin_ia32_vtestzps256:
  case X86::BI__builtin_ia32_vtestzpd:
  case X86::BI__builtin_ia32_vtestzpd256:
    return interp__builtin_ia32_test_op(
        S, OpPC, Call,
        [](const APInt &A, const APInt &B) { return (A & B) == 0; });
  case X86::BI__builtin_ia32_ptestc128:
  case X86::BI__builtin_ia32_ptestc256:
  case X86::BI__builtin_ia32_vtestcps:
  case X86::BI__builtin_ia32_vtestcps256:
  case X86::BI__builtin_ia32_vtestcpd:
  case X86::BI__builtin_ia32_vtestcpd256:
    return interp__builtin_ia32_test_op(
        S, OpPC, Call,
        [](const APInt &A, const APInt &B) { return (~A & B) == 0; });
  case X86::BI__builtin_ia32_ptestnzc128:
  case X86::BI__builtin_ia32_ptestnzc256:
  case X86::BI__builtin_ia32_vtestnzcps:
  case X86::BI__builtin_ia32_vtestnzcps256:
  case X86::BI__builtin_ia32_vtestnzcpd:
  case X86::BI__builtin_ia32_vtestnzcpd256:
    return interp__builtin_ia32_test_op(
        S, OpPC, Call, [](const APInt &A, const APInt &B) {
          return ((A & B) != 0) && ((~A & B) != 0);
        });
  case X86::BI__builtin_ia32_selectb_128:
  case X86::BI__builtin_ia32_selectb_256:
  case X86::BI__builtin_ia32_selectb_512:
  case X86::BI__builtin_ia32_selectw_128:
  case X86::BI__builtin_ia32_selectw_256:
  case X86::BI__builtin_ia32_selectw_512:
  case X86::BI__builtin_ia32_selectd_128:
  case X86::BI__builtin_ia32_selectd_256:
  case X86::BI__builtin_ia32_selectd_512:
  case X86::BI__builtin_ia32_selectq_128:
  case X86::BI__builtin_ia32_selectq_256:
  case X86::BI__builtin_ia32_selectq_512:
  case X86::BI__builtin_ia32_selectph_128:
  case X86::BI__builtin_ia32_selectph_256:
  case X86::BI__builtin_ia32_selectph_512:
  case X86::BI__builtin_ia32_selectpbf_128:
  case X86::BI__builtin_ia32_selectpbf_256:
  case X86::BI__builtin_ia32_selectpbf_512:
  case X86::BI__builtin_ia32_selectps_128:
  case X86::BI__builtin_ia32_selectps_256:
  case X86::BI__builtin_ia32_selectps_512:
  case X86::BI__builtin_ia32_selectpd_128:
  case X86::BI__builtin_ia32_selectpd_256:
  case X86::BI__builtin_ia32_selectpd_512:
    return interp__builtin_select(S, OpPC, Call);

  case X86::BI__builtin_ia32_shufps:
  case X86::BI__builtin_ia32_shufps256:
  case X86::BI__builtin_ia32_shufps512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned NumElemPerLane = 4;
          unsigned NumSelectableElems = NumElemPerLane / 2;
          unsigned BitsPerElem = 2;
          unsigned IndexMask = 0x3;
          unsigned MaskBits = 8;
          unsigned Lane = DstIdx / NumElemPerLane;
          unsigned ElemInLane = DstIdx % NumElemPerLane;
          unsigned LaneOffset = Lane * NumElemPerLane;
          unsigned SrcIdx = ElemInLane >= NumSelectableElems ? 1 : 0;
          unsigned BitIndex = (DstIdx * BitsPerElem) % MaskBits;
          unsigned Index = (ShuffleMask >> BitIndex) & IndexMask;
          return std::pair<unsigned, int>{SrcIdx,
                                          static_cast<int>(LaneOffset + Index)};
        });
  case X86::BI__builtin_ia32_shufpd:
  case X86::BI__builtin_ia32_shufpd256:
  case X86::BI__builtin_ia32_shufpd512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned NumElemPerLane = 2;
          unsigned NumSelectableElems = NumElemPerLane / 2;
          unsigned BitsPerElem = 1;
          unsigned IndexMask = 0x1;
          unsigned MaskBits = 8;
          unsigned Lane = DstIdx / NumElemPerLane;
          unsigned ElemInLane = DstIdx % NumElemPerLane;
          unsigned LaneOffset = Lane * NumElemPerLane;
          unsigned SrcIdx = ElemInLane >= NumSelectableElems ? 1 : 0;
          unsigned BitIndex = (DstIdx * BitsPerElem) % MaskBits;
          unsigned Index = (ShuffleMask >> BitIndex) & IndexMask;
          return std::pair<unsigned, int>{SrcIdx,
                                          static_cast<int>(LaneOffset + Index)};
        });

  case X86::BI__builtin_ia32_vgf2p8affineinvqb_v16qi:
  case X86::BI__builtin_ia32_vgf2p8affineinvqb_v32qi:
  case X86::BI__builtin_ia32_vgf2p8affineinvqb_v64qi:
    return interp_builtin_ia32_gfni_affine(S, OpPC, Call, true);
  case X86::BI__builtin_ia32_vgf2p8affineqb_v16qi:
  case X86::BI__builtin_ia32_vgf2p8affineqb_v32qi:
  case X86::BI__builtin_ia32_vgf2p8affineqb_v64qi:
    return interp_builtin_ia32_gfni_affine(S, OpPC, Call, false);

  case X86::BI__builtin_ia32_vgf2p8mulb_v16qi:
  case X86::BI__builtin_ia32_vgf2p8mulb_v32qi:
  case X86::BI__builtin_ia32_vgf2p8mulb_v64qi:
    return interp__builtin_ia32_gfni_mul(S, OpPC, Call);

  case X86::BI__builtin_ia32_insertps128:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned Mask) {
          // Bits [3:0]: zero mask - if bit is set, zero this element
          if ((Mask & (1 << DstIdx)) != 0) {
            return std::pair<unsigned, int>{0, -1};
          }
          // Bits [7:6]: select element from source vector Y (0-3)
          // Bits [5:4]: select destination position (0-3)
          unsigned SrcElem = (Mask >> 6) & 0x3;
          unsigned DstElem = (Mask >> 4) & 0x3;
          if (DstIdx == DstElem) {
            // Insert element from source vector (B) at this position
            return std::pair<unsigned, int>{1, static_cast<int>(SrcElem)};
          } else {
            // Copy from destination vector (A)
            return std::pair<unsigned, int>{0, static_cast<int>(DstIdx)};
          }
        });
  case X86::BI__builtin_ia32_permvarsi256:
  case X86::BI__builtin_ia32_permvarsf256:
  case X86::BI__builtin_ia32_permvardf512:
  case X86::BI__builtin_ia32_permvardi512:
  case X86::BI__builtin_ia32_permvarhi128:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x7;
          return std::pair<unsigned, int>{0, Offset};
        });
  case X86::BI__builtin_ia32_permvarqi128:
  case X86::BI__builtin_ia32_permvarhi256:
  case X86::BI__builtin_ia32_permvarsi512:
  case X86::BI__builtin_ia32_permvarsf512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0xF;
          return std::pair<unsigned, int>{0, Offset};
        });
  case X86::BI__builtin_ia32_permvardi256:
  case X86::BI__builtin_ia32_permvardf256:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x3;
          return std::pair<unsigned, int>{0, Offset};
        });
  case X86::BI__builtin_ia32_permvarqi256:
  case X86::BI__builtin_ia32_permvarhi512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x1F;
          return std::pair<unsigned, int>{0, Offset};
        });
  case X86::BI__builtin_ia32_permvarqi512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x3F;
          return std::pair<unsigned, int>{0, Offset};
        });
  case X86::BI__builtin_ia32_vpermi2varq128:
  case X86::BI__builtin_ia32_vpermi2varpd128:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x1;
          unsigned SrcIdx = (ShuffleMask >> 1) & 0x1;
          return std::pair<unsigned, int>{SrcIdx, Offset};
        });
  case X86::BI__builtin_ia32_vpermi2vard128:
  case X86::BI__builtin_ia32_vpermi2varps128:
  case X86::BI__builtin_ia32_vpermi2varq256:
  case X86::BI__builtin_ia32_vpermi2varpd256:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x3;
          unsigned SrcIdx = (ShuffleMask >> 2) & 0x1;
          return std::pair<unsigned, int>{SrcIdx, Offset};
        });
  case X86::BI__builtin_ia32_vpermi2varhi128:
  case X86::BI__builtin_ia32_vpermi2vard256:
  case X86::BI__builtin_ia32_vpermi2varps256:
  case X86::BI__builtin_ia32_vpermi2varq512:
  case X86::BI__builtin_ia32_vpermi2varpd512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x7;
          unsigned SrcIdx = (ShuffleMask >> 3) & 0x1;
          return std::pair<unsigned, int>{SrcIdx, Offset};
        });
  case X86::BI__builtin_ia32_vpermi2varqi128:
  case X86::BI__builtin_ia32_vpermi2varhi256:
  case X86::BI__builtin_ia32_vpermi2vard512:
  case X86::BI__builtin_ia32_vpermi2varps512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0xF;
          unsigned SrcIdx = (ShuffleMask >> 4) & 0x1;
          return std::pair<unsigned, int>{SrcIdx, Offset};
        });
  case X86::BI__builtin_ia32_vpermi2varqi256:
  case X86::BI__builtin_ia32_vpermi2varhi512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x1F;
          unsigned SrcIdx = (ShuffleMask >> 5) & 0x1;
          return std::pair<unsigned, int>{SrcIdx, Offset};
        });
  case X86::BI__builtin_ia32_vpermi2varqi512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          int Offset = ShuffleMask & 0x3F;
          unsigned SrcIdx = (ShuffleMask >> 6) & 0x1;
          return std::pair<unsigned, int>{SrcIdx, Offset};
        });
  case X86::BI__builtin_ia32_vperm2f128_pd256:
  case X86::BI__builtin_ia32_vperm2f128_ps256:
  case X86::BI__builtin_ia32_vperm2f128_si256:
  case X86::BI__builtin_ia32_permti256: {
    unsigned NumElements =
        Call->getArg(0)->getType()->castAs<VectorType>()->getNumElements();
    unsigned PreservedBitsCnt = NumElements >> 2;
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call,
        [PreservedBitsCnt](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned ControlBitsCnt = DstIdx >> PreservedBitsCnt << 2;
          unsigned ControlBits = ShuffleMask >> ControlBitsCnt;

          if (ControlBits & 0b1000)
            return std::make_pair(0u, -1);

          unsigned SrcVecIdx = (ControlBits & 0b10) >> 1;
          unsigned PreservedBitsMask = (1 << PreservedBitsCnt) - 1;
          int SrcIdx = ((ControlBits & 0b1) << PreservedBitsCnt) |
                       (DstIdx & PreservedBitsMask);
          return std::make_pair(SrcVecIdx, SrcIdx);
        });
  }
  case X86::BI__builtin_ia32_pshufb128:
  case X86::BI__builtin_ia32_pshufb256:
  case X86::BI__builtin_ia32_pshufb512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          uint8_t Ctlb = static_cast<uint8_t>(ShuffleMask);
          if (Ctlb & 0x80)
            return std::make_pair(0, -1);

          unsigned LaneBase = (DstIdx / 16) * 16;
          unsigned SrcOffset = Ctlb & 0x0F;
          unsigned SrcIdx = LaneBase + SrcOffset;
          return std::make_pair(0, static_cast<int>(SrcIdx));
        });

  case X86::BI__builtin_ia32_pshuflw:
  case X86::BI__builtin_ia32_pshuflw256:
  case X86::BI__builtin_ia32_pshuflw512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned LaneBase = (DstIdx / 8) * 8;
          unsigned LaneIdx = DstIdx % 8;
          if (LaneIdx < 4) {
            unsigned Sel = (ShuffleMask >> (2 * LaneIdx)) & 0x3;
            return std::make_pair(0, static_cast<int>(LaneBase + Sel));
          }

          return std::make_pair(0, static_cast<int>(DstIdx));
        });

  case X86::BI__builtin_ia32_pshufhw:
  case X86::BI__builtin_ia32_pshufhw256:
  case X86::BI__builtin_ia32_pshufhw512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned LaneBase = (DstIdx / 8) * 8;
          unsigned LaneIdx = DstIdx % 8;
          if (LaneIdx >= 4) {
            unsigned Sel = (ShuffleMask >> (2 * (LaneIdx - 4))) & 0x3;
            return std::make_pair(0, static_cast<int>(LaneBase + 4 + Sel));
          }

          return std::make_pair(0, static_cast<int>(DstIdx));
        });

  case X86::BI__builtin_ia32_pshufd:
  case X86::BI__builtin_ia32_pshufd256:
  case X86::BI__builtin_ia32_pshufd512:
  case X86::BI__builtin_ia32_vpermilps:
  case X86::BI__builtin_ia32_vpermilps256:
  case X86::BI__builtin_ia32_vpermilps512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned LaneBase = (DstIdx / 4) * 4;
          unsigned LaneIdx = DstIdx % 4;
          unsigned Sel = (ShuffleMask >> (2 * LaneIdx)) & 0x3;
          return std::make_pair(0, static_cast<int>(LaneBase + Sel));
        });

  case X86::BI__builtin_ia32_vpermilvarpd:
  case X86::BI__builtin_ia32_vpermilvarpd256:
  case X86::BI__builtin_ia32_vpermilvarpd512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned NumElemPerLane = 2;
          unsigned Lane = DstIdx / NumElemPerLane;
          unsigned Offset = ShuffleMask & 0b10 ? 1 : 0;
          return std::make_pair(
              0, static_cast<int>(Lane * NumElemPerLane + Offset));
        });

  case X86::BI__builtin_ia32_vpermilvarps:
  case X86::BI__builtin_ia32_vpermilvarps256:
  case X86::BI__builtin_ia32_vpermilvarps512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned ShuffleMask) {
          unsigned NumElemPerLane = 4;
          unsigned Lane = DstIdx / NumElemPerLane;
          unsigned Offset = ShuffleMask & 0b11;
          return std::make_pair(
              0, static_cast<int>(Lane * NumElemPerLane + Offset));
        });

  case X86::BI__builtin_ia32_vpermilpd:
  case X86::BI__builtin_ia32_vpermilpd256:
  case X86::BI__builtin_ia32_vpermilpd512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned Control) {
          unsigned NumElemPerLane = 2;
          unsigned BitsPerElem = 1;
          unsigned MaskBits = 8;
          unsigned IndexMask = 0x1;
          unsigned Lane = DstIdx / NumElemPerLane;
          unsigned LaneOffset = Lane * NumElemPerLane;
          unsigned BitIndex = (DstIdx * BitsPerElem) % MaskBits;
          unsigned Index = (Control >> BitIndex) & IndexMask;
          return std::make_pair(0, static_cast<int>(LaneOffset + Index));
        });

  case X86::BI__builtin_ia32_permdf256:
  case X86::BI__builtin_ia32_permdi256:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned Control) {
          // permute4x64 operates on 4 64-bit elements
          // For element i (0-3), extract bits [2*i+1:2*i] from Control
          unsigned Index = (Control >> (2 * DstIdx)) & 0x3;
          return std::make_pair(0, static_cast<int>(Index));
        });

  case X86::BI__builtin_ia32_vpmultishiftqb128:
  case X86::BI__builtin_ia32_vpmultishiftqb256:
  case X86::BI__builtin_ia32_vpmultishiftqb512:
    return interp__builtin_ia32_multishiftqb(S, OpPC, Call);
  case X86::BI__builtin_ia32_kandqi:
  case X86::BI__builtin_ia32_kandhi:
  case X86::BI__builtin_ia32_kandsi:
  case X86::BI__builtin_ia32_kanddi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS & RHS; });

  case X86::BI__builtin_ia32_kandnqi:
  case X86::BI__builtin_ia32_kandnhi:
  case X86::BI__builtin_ia32_kandnsi:
  case X86::BI__builtin_ia32_kandndi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return ~LHS & RHS; });

  case X86::BI__builtin_ia32_korqi:
  case X86::BI__builtin_ia32_korhi:
  case X86::BI__builtin_ia32_korsi:
  case X86::BI__builtin_ia32_kordi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS | RHS; });

  case X86::BI__builtin_ia32_kxnorqi:
  case X86::BI__builtin_ia32_kxnorhi:
  case X86::BI__builtin_ia32_kxnorsi:
  case X86::BI__builtin_ia32_kxnordi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return ~(LHS ^ RHS); });

  case X86::BI__builtin_ia32_kxorqi:
  case X86::BI__builtin_ia32_kxorhi:
  case X86::BI__builtin_ia32_kxorsi:
  case X86::BI__builtin_ia32_kxordi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS ^ RHS; });

  case X86::BI__builtin_ia32_knotqi:
  case X86::BI__builtin_ia32_knothi:
  case X86::BI__builtin_ia32_knotsi:
  case X86::BI__builtin_ia32_knotdi:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Src) { return ~Src; });

  case X86::BI__builtin_ia32_kaddqi:
  case X86::BI__builtin_ia32_kaddhi:
  case X86::BI__builtin_ia32_kaddsi:
  case X86::BI__builtin_ia32_kadddi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call,
        [](const APSInt &LHS, const APSInt &RHS) { return LHS + RHS; });

  case X86::BI__builtin_ia32_kmovb:
  case X86::BI__builtin_ia32_kmovw:
  case X86::BI__builtin_ia32_kmovd:
  case X86::BI__builtin_ia32_kmovq:
    return interp__builtin_elementwise_int_unaryop(
        S, OpPC, Call, [](const APSInt &Src) { return Src; });

  case X86::BI__builtin_ia32_kunpckhi:
  case X86::BI__builtin_ia32_kunpckdi:
  case X86::BI__builtin_ia32_kunpcksi:
    return interp__builtin_elementwise_int_binop(
        S, OpPC, Call, [](const APSInt &A, const APSInt &B) {
          // Generic kunpack: extract lower half of each operand and concatenate
          // Result = A[HalfWidth-1:0] concat B[HalfWidth-1:0]
          unsigned BW = A.getBitWidth();
          return APSInt(A.trunc(BW / 2).concat(B.trunc(BW / 2)),
                        A.isUnsigned());
        });

  case X86::BI__builtin_ia32_phminposuw128:
    return interp__builtin_ia32_phminposuw(S, OpPC, Call);

  case X86::BI__builtin_ia32_psraq128:
  case X86::BI__builtin_ia32_psraq256:
  case X86::BI__builtin_ia32_psraq512:
  case X86::BI__builtin_ia32_psrad128:
  case X86::BI__builtin_ia32_psrad256:
  case X86::BI__builtin_ia32_psrad512:
  case X86::BI__builtin_ia32_psraw128:
  case X86::BI__builtin_ia32_psraw256:
  case X86::BI__builtin_ia32_psraw512:
    return interp__builtin_ia32_shift_with_count(
        S, OpPC, Call,
        [](const APInt &Elt, uint64_t Count) { return Elt.ashr(Count); },
        [](const APInt &Elt, unsigned Width) { return Elt.ashr(Width - 1); });

  case X86::BI__builtin_ia32_psllq128:
  case X86::BI__builtin_ia32_psllq256:
  case X86::BI__builtin_ia32_psllq512:
  case X86::BI__builtin_ia32_pslld128:
  case X86::BI__builtin_ia32_pslld256:
  case X86::BI__builtin_ia32_pslld512:
  case X86::BI__builtin_ia32_psllw128:
  case X86::BI__builtin_ia32_psllw256:
  case X86::BI__builtin_ia32_psllw512:
    return interp__builtin_ia32_shift_with_count(
        S, OpPC, Call,
        [](const APInt &Elt, uint64_t Count) { return Elt.shl(Count); },
        [](const APInt &Elt, unsigned Width) { return APInt::getZero(Width); });

  case X86::BI__builtin_ia32_psrlq128:
  case X86::BI__builtin_ia32_psrlq256:
  case X86::BI__builtin_ia32_psrlq512:
  case X86::BI__builtin_ia32_psrld128:
  case X86::BI__builtin_ia32_psrld256:
  case X86::BI__builtin_ia32_psrld512:
  case X86::BI__builtin_ia32_psrlw128:
  case X86::BI__builtin_ia32_psrlw256:
  case X86::BI__builtin_ia32_psrlw512:
    return interp__builtin_ia32_shift_with_count(
        S, OpPC, Call,
        [](const APInt &Elt, uint64_t Count) { return Elt.lshr(Count); },
        [](const APInt &Elt, unsigned Width) { return APInt::getZero(Width); });

  case X86::BI__builtin_ia32_pternlogd128_mask:
  case X86::BI__builtin_ia32_pternlogd256_mask:
  case X86::BI__builtin_ia32_pternlogd512_mask:
  case X86::BI__builtin_ia32_pternlogq128_mask:
  case X86::BI__builtin_ia32_pternlogq256_mask:
  case X86::BI__builtin_ia32_pternlogq512_mask:
    return interp__builtin_ia32_pternlog(S, OpPC, Call, /*MaskZ=*/false);
  case X86::BI__builtin_ia32_pternlogd128_maskz:
  case X86::BI__builtin_ia32_pternlogd256_maskz:
  case X86::BI__builtin_ia32_pternlogd512_maskz:
  case X86::BI__builtin_ia32_pternlogq128_maskz:
  case X86::BI__builtin_ia32_pternlogq256_maskz:
  case X86::BI__builtin_ia32_pternlogq512_maskz:
    return interp__builtin_ia32_pternlog(S, OpPC, Call, /*MaskZ=*/true);
  case Builtin::BI__builtin_elementwise_fshl:
    return interp__builtin_elementwise_triop(S, OpPC, Call,
                                             llvm::APIntOps::fshl);
  case Builtin::BI__builtin_elementwise_fshr:
    return interp__builtin_elementwise_triop(S, OpPC, Call,
                                             llvm::APIntOps::fshr);

  case X86::BI__builtin_ia32_shuf_f32x4_256:
  case X86::BI__builtin_ia32_shuf_i32x4_256:
  case X86::BI__builtin_ia32_shuf_f64x2_256:
  case X86::BI__builtin_ia32_shuf_i64x2_256:
  case X86::BI__builtin_ia32_shuf_f32x4:
  case X86::BI__builtin_ia32_shuf_i32x4:
  case X86::BI__builtin_ia32_shuf_f64x2:
  case X86::BI__builtin_ia32_shuf_i64x2: {
    // Destination and sources A, B all have the same type.
    QualType VecQT = Call->getArg(0)->getType();
    const auto *VecT = VecQT->castAs<VectorType>();
    unsigned NumElems = VecT->getNumElements();
    unsigned ElemBits = S.getASTContext().getTypeSize(VecT->getElementType());
    unsigned LaneBits = 128u;
    unsigned NumLanes = (NumElems * ElemBits) / LaneBits;
    unsigned NumElemsPerLane = LaneBits / ElemBits;

    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call,
        [NumLanes, NumElemsPerLane](unsigned DstIdx, unsigned ShuffleMask) {
          // DstIdx determines source. ShuffleMask selects lane in source.
          unsigned BitsPerElem = NumLanes / 2;
          unsigned IndexMask = (1u << BitsPerElem) - 1;
          unsigned Lane = DstIdx / NumElemsPerLane;
          unsigned SrcIdx = (Lane < NumLanes / 2) ? 0 : 1;
          unsigned BitIdx = BitsPerElem * Lane;
          unsigned SrcLaneIdx = (ShuffleMask >> BitIdx) & IndexMask;
          unsigned ElemInLane = DstIdx % NumElemsPerLane;
          unsigned IdxToPick = SrcLaneIdx * NumElemsPerLane + ElemInLane;
          return std::pair<unsigned, int>{SrcIdx, IdxToPick};
        });
  }

  case X86::BI__builtin_ia32_insertf32x4_256:
  case X86::BI__builtin_ia32_inserti32x4_256:
  case X86::BI__builtin_ia32_insertf64x2_256:
  case X86::BI__builtin_ia32_inserti64x2_256:
  case X86::BI__builtin_ia32_insertf32x4:
  case X86::BI__builtin_ia32_inserti32x4:
  case X86::BI__builtin_ia32_insertf64x2_512:
  case X86::BI__builtin_ia32_inserti64x2_512:
  case X86::BI__builtin_ia32_insertf32x8:
  case X86::BI__builtin_ia32_inserti32x8:
  case X86::BI__builtin_ia32_insertf64x4:
  case X86::BI__builtin_ia32_inserti64x4:
  case X86::BI__builtin_ia32_vinsertf128_ps256:
  case X86::BI__builtin_ia32_vinsertf128_pd256:
  case X86::BI__builtin_ia32_vinsertf128_si256:
  case X86::BI__builtin_ia32_insert128i256:
    return interp__builtin_x86_insert_subvector(S, OpPC, Call, BuiltinID);

  case clang::X86::BI__builtin_ia32_vcvtps2ph:
  case clang::X86::BI__builtin_ia32_vcvtps2ph256:
    return interp__builtin_ia32_vcvtps2ph(S, OpPC, Call);

  case X86::BI__builtin_ia32_vec_ext_v4hi:
  case X86::BI__builtin_ia32_vec_ext_v16qi:
  case X86::BI__builtin_ia32_vec_ext_v8hi:
  case X86::BI__builtin_ia32_vec_ext_v4si:
  case X86::BI__builtin_ia32_vec_ext_v2di:
  case X86::BI__builtin_ia32_vec_ext_v32qi:
  case X86::BI__builtin_ia32_vec_ext_v16hi:
  case X86::BI__builtin_ia32_vec_ext_v8si:
  case X86::BI__builtin_ia32_vec_ext_v4di:
  case X86::BI__builtin_ia32_vec_ext_v4sf:
    return interp__builtin_vec_ext(S, OpPC, Call, BuiltinID);

  case X86::BI__builtin_ia32_vec_set_v4hi:
  case X86::BI__builtin_ia32_vec_set_v16qi:
  case X86::BI__builtin_ia32_vec_set_v8hi:
  case X86::BI__builtin_ia32_vec_set_v4si:
  case X86::BI__builtin_ia32_vec_set_v2di:
  case X86::BI__builtin_ia32_vec_set_v32qi:
  case X86::BI__builtin_ia32_vec_set_v16hi:
  case X86::BI__builtin_ia32_vec_set_v8si:
  case X86::BI__builtin_ia32_vec_set_v4di:
    return interp__builtin_vec_set(S, OpPC, Call, BuiltinID);

  case X86::BI__builtin_ia32_cvtb2mask128:
  case X86::BI__builtin_ia32_cvtb2mask256:
  case X86::BI__builtin_ia32_cvtb2mask512:
  case X86::BI__builtin_ia32_cvtw2mask128:
  case X86::BI__builtin_ia32_cvtw2mask256:
  case X86::BI__builtin_ia32_cvtw2mask512:
  case X86::BI__builtin_ia32_cvtd2mask128:
  case X86::BI__builtin_ia32_cvtd2mask256:
  case X86::BI__builtin_ia32_cvtd2mask512:
  case X86::BI__builtin_ia32_cvtq2mask128:
  case X86::BI__builtin_ia32_cvtq2mask256:
  case X86::BI__builtin_ia32_cvtq2mask512:
    return interp__builtin_ia32_cvt_vec2mask(S, OpPC, Call, BuiltinID);

  case X86::BI__builtin_ia32_cvtmask2b128:
  case X86::BI__builtin_ia32_cvtmask2b256:
  case X86::BI__builtin_ia32_cvtmask2b512:
  case X86::BI__builtin_ia32_cvtmask2w128:
  case X86::BI__builtin_ia32_cvtmask2w256:
  case X86::BI__builtin_ia32_cvtmask2w512:
  case X86::BI__builtin_ia32_cvtmask2d128:
  case X86::BI__builtin_ia32_cvtmask2d256:
  case X86::BI__builtin_ia32_cvtmask2d512:
  case X86::BI__builtin_ia32_cvtmask2q128:
  case X86::BI__builtin_ia32_cvtmask2q256:
  case X86::BI__builtin_ia32_cvtmask2q512:
    return interp__builtin_ia32_cvt_mask2vec(S, OpPC, Call, BuiltinID);

  case X86::BI__builtin_ia32_cvtsd2ss:
    return interp__builtin_ia32_cvtsd2ss(S, OpPC, Call, false);

  case X86::BI__builtin_ia32_cvtsd2ss_round_mask:
    return interp__builtin_ia32_cvtsd2ss(S, OpPC, Call, true);

  case X86::BI__builtin_ia32_cvtpd2ps:
  case X86::BI__builtin_ia32_cvtpd2ps256:
    return interp__builtin_ia32_cvtpd2ps(S, OpPC, Call, false, false);
  case X86::BI__builtin_ia32_cvtpd2ps_mask:
    return interp__builtin_ia32_cvtpd2ps(S, OpPC, Call, true, false);
  case X86::BI__builtin_ia32_cvtpd2ps512_mask:
    return interp__builtin_ia32_cvtpd2ps(S, OpPC, Call, true, true);

  case X86::BI__builtin_ia32_cmpb128_mask:
  case X86::BI__builtin_ia32_cmpw128_mask:
  case X86::BI__builtin_ia32_cmpd128_mask:
  case X86::BI__builtin_ia32_cmpq128_mask:
  case X86::BI__builtin_ia32_cmpb256_mask:
  case X86::BI__builtin_ia32_cmpw256_mask:
  case X86::BI__builtin_ia32_cmpd256_mask:
  case X86::BI__builtin_ia32_cmpq256_mask:
  case X86::BI__builtin_ia32_cmpb512_mask:
  case X86::BI__builtin_ia32_cmpw512_mask:
  case X86::BI__builtin_ia32_cmpd512_mask:
  case X86::BI__builtin_ia32_cmpq512_mask:
    return interp__builtin_ia32_cmp_mask(S, OpPC, Call, BuiltinID,
                                         /*IsUnsigned=*/false);

  case X86::BI__builtin_ia32_ucmpb128_mask:
  case X86::BI__builtin_ia32_ucmpw128_mask:
  case X86::BI__builtin_ia32_ucmpd128_mask:
  case X86::BI__builtin_ia32_ucmpq128_mask:
  case X86::BI__builtin_ia32_ucmpb256_mask:
  case X86::BI__builtin_ia32_ucmpw256_mask:
  case X86::BI__builtin_ia32_ucmpd256_mask:
  case X86::BI__builtin_ia32_ucmpq256_mask:
  case X86::BI__builtin_ia32_ucmpb512_mask:
  case X86::BI__builtin_ia32_ucmpw512_mask:
  case X86::BI__builtin_ia32_ucmpd512_mask:
  case X86::BI__builtin_ia32_ucmpq512_mask:
    return interp__builtin_ia32_cmp_mask(S, OpPC, Call, BuiltinID,
                                         /*IsUnsigned=*/true);

  case X86::BI__builtin_ia32_vpshufbitqmb128_mask:
  case X86::BI__builtin_ia32_vpshufbitqmb256_mask:
  case X86::BI__builtin_ia32_vpshufbitqmb512_mask:
    return interp__builtin_ia32_shufbitqmb_mask(S, OpPC, Call);

  case X86::BI__builtin_ia32_pslldqi128_byteshift:
  case X86::BI__builtin_ia32_pslldqi256_byteshift:
  case X86::BI__builtin_ia32_pslldqi512_byteshift:
    // These SLLDQ intrinsics always operate on byte elements (8 bits).
    // The lane width is hardcoded to 16 to match the SIMD register size,
    // but the algorithm processes one byte per iteration,
    // so APInt(8, ...) is correct and intentional.
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call,
        [](unsigned DstIdx, unsigned Shift) -> std::pair<unsigned, int> {
          unsigned LaneBase = (DstIdx / 16) * 16;
          unsigned LaneIdx = DstIdx % 16;
          if (LaneIdx < Shift)
            return std::make_pair(0, -1);

          return std::make_pair(0,
                                static_cast<int>(LaneBase + LaneIdx - Shift));
        });

  case X86::BI__builtin_ia32_psrldqi128_byteshift:
  case X86::BI__builtin_ia32_psrldqi256_byteshift:
  case X86::BI__builtin_ia32_psrldqi512_byteshift:
    // These SRLDQ intrinsics always operate on byte elements (8 bits).
    // The lane width is hardcoded to 16 to match the SIMD register size,
    // but the algorithm processes one byte per iteration,
    // so APInt(8, ...) is correct and intentional.
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call,
        [](unsigned DstIdx, unsigned Shift) -> std::pair<unsigned, int> {
          unsigned LaneBase = (DstIdx / 16) * 16;
          unsigned LaneIdx = DstIdx % 16;
          if (LaneIdx + Shift < 16)
            return std::make_pair(0,
                                  static_cast<int>(LaneBase + LaneIdx + Shift));

          return std::make_pair(0, -1);
        });

  case X86::BI__builtin_ia32_palignr128:
  case X86::BI__builtin_ia32_palignr256:
  case X86::BI__builtin_ia32_palignr512:
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [](unsigned DstIdx, unsigned Shift) {
          // Default to -1 → zero-fill this destination element
          unsigned VecIdx = 1;
          int ElemIdx = -1;

          int Lane = DstIdx / 16;
          int Offset = DstIdx % 16;

          // Elements come from VecB first, then VecA after the shift boundary
          unsigned ShiftedIdx = Offset + (Shift & 0xFF);
          if (ShiftedIdx < 16) { // from VecB
            ElemIdx = ShiftedIdx + (Lane * 16);
          } else if (ShiftedIdx < 32) { // from VecA
            VecIdx = 0;
            ElemIdx = (ShiftedIdx - 16) + (Lane * 16);
          }

          return std::pair<unsigned, int>{VecIdx, ElemIdx};
        });

  case X86::BI__builtin_ia32_alignd128:
  case X86::BI__builtin_ia32_alignd256:
  case X86::BI__builtin_ia32_alignd512:
  case X86::BI__builtin_ia32_alignq128:
  case X86::BI__builtin_ia32_alignq256:
  case X86::BI__builtin_ia32_alignq512: {
    unsigned NumElems = Call->getType()->castAs<VectorType>()->getNumElements();
    return interp__builtin_ia32_shuffle_generic(
        S, OpPC, Call, [NumElems](unsigned DstIdx, unsigned Shift) {
          unsigned Imm = Shift & 0xFF;
          unsigned EffectiveShift = Imm & (NumElems - 1);
          unsigned SourcePos = DstIdx + EffectiveShift;
          unsigned VecIdx = SourcePos < NumElems ? 1u : 0u;
          unsigned ElemIdx = SourcePos & (NumElems - 1);
          return std::pair<unsigned, int>{VecIdx, static_cast<int>(ElemIdx)};
        });
  }

  case clang::X86::BI__builtin_ia32_minps:
  case clang::X86::BI__builtin_ia32_minpd:
  case clang::X86::BI__builtin_ia32_minph128:
  case clang::X86::BI__builtin_ia32_minph256:
  case clang::X86::BI__builtin_ia32_minps256:
  case clang::X86::BI__builtin_ia32_minpd256:
  case clang::X86::BI__builtin_ia32_minps512:
  case clang::X86::BI__builtin_ia32_minpd512:
  case clang::X86::BI__builtin_ia32_minph512:
    return interp__builtin_elementwise_fp_binop(
        S, OpPC, Call,
        [](const APFloat &A, const APFloat &B, std::optional<APSInt>) {
          if (A.isZero() && B.isZero())
            return B;
          return llvm::minimum(A, B);
        });

  case clang::X86::BI__builtin_ia32_maxps:
  case clang::X86::BI__builtin_ia32_maxpd:
  case clang::X86::BI__builtin_ia32_maxph128:
  case clang::X86::BI__builtin_ia32_maxph256:
  case clang::X86::BI__builtin_ia32_maxps256:
  case clang::X86::BI__builtin_ia32_maxpd256:
  case clang::X86::BI__builtin_ia32_maxps512:
  case clang::X86::BI__builtin_ia32_maxpd512:
  case clang::X86::BI__builtin_ia32_maxph512:
    return interp__builtin_elementwise_fp_binop(
        S, OpPC, Call,
        [](const APFloat &A, const APFloat &B, std::optional<APSInt>) {
          if (A.isZero() && B.isZero())
            return B;
          return llvm::maximum(A, B);
        });

  default:
    S.FFDiag(S.Current->getLocation(OpPC),
             diag::note_invalid_subexpr_in_const_expr)
        << S.Current->getRange(OpPC);

    return false;
  }

  llvm_unreachable("Unhandled builtin ID");
}

bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E,
                       ArrayRef<int64_t> ArrayIndices, int64_t &IntResult) {
  CharUnits Result;
  unsigned N = E->getNumComponents();
  assert(N > 0);

  unsigned ArrayIndex = 0;
  QualType CurrentType = E->getTypeSourceInfo()->getType();
  for (unsigned I = 0; I != N; ++I) {
    const OffsetOfNode &Node = E->getComponent(I);
    switch (Node.getKind()) {
    case OffsetOfNode::Field: {
      const FieldDecl *MemberDecl = Node.getField();
      const auto *RD = CurrentType->getAsRecordDecl();
      if (!RD || RD->isInvalidDecl())
        return false;
      const ASTRecordLayout &RL = S.getASTContext().getASTRecordLayout(RD);
      unsigned FieldIndex = MemberDecl->getFieldIndex();
      assert(FieldIndex < RL.getFieldCount() && "offsetof field in wrong type");
      Result +=
          S.getASTContext().toCharUnitsFromBits(RL.getFieldOffset(FieldIndex));
      CurrentType = MemberDecl->getType().getNonReferenceType();
      break;
    }
    case OffsetOfNode::Array: {
      // When generating bytecode, we put all the index expressions as Sint64 on
      // the stack.
      int64_t Index = ArrayIndices[ArrayIndex];
      const ArrayType *AT = S.getASTContext().getAsArrayType(CurrentType);
      if (!AT)
        return false;
      CurrentType = AT->getElementType();
      CharUnits ElementSize = S.getASTContext().getTypeSizeInChars(CurrentType);
      Result += Index * ElementSize;
      ++ArrayIndex;
      break;
    }
    case OffsetOfNode::Base: {
      const CXXBaseSpecifier *BaseSpec = Node.getBase();
      if (BaseSpec->isVirtual())
        return false;

      // Find the layout of the class whose base we are looking into.
      const auto *RD = CurrentType->getAsCXXRecordDecl();
      if (!RD || RD->isInvalidDecl())
        return false;
      const ASTRecordLayout &RL = S.getASTContext().getASTRecordLayout(RD);

      // Find the base class itself.
      CurrentType = BaseSpec->getType();
      const auto *BaseRD = CurrentType->getAsCXXRecordDecl();
      if (!BaseRD)
        return false;

      // Add the offset to the base.
      Result += RL.getBaseClassOffset(BaseRD);
      break;
    }
    case OffsetOfNode::Identifier:
      llvm_unreachable("Dependent OffsetOfExpr?");
    }
  }

  IntResult = Result.getQuantity();

  return true;
}

bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC,
                                const Pointer &Ptr, const APSInt &IntValue) {

  const Record *R = Ptr.getRecord();
  assert(R);
  assert(R->getNumFields() == 1);

  unsigned FieldOffset = R->getField(0u)->Offset;
  const Pointer &FieldPtr = Ptr.atField(FieldOffset);
  PrimType FieldT = *S.getContext().classify(FieldPtr.getType());

  INT_TYPE_SWITCH(FieldT,
                  FieldPtr.deref<T>() = T::from(IntValue.getSExtValue()));
  FieldPtr.initialize();
  return true;
}

static void zeroAll(Pointer &Dest) {
  const Descriptor *Desc = Dest.getFieldDesc();

  if (Desc->isPrimitive()) {
    TYPE_SWITCH(Desc->getPrimType(), {
      Dest.deref<T>().~T();
      new (&Dest.deref<T>()) T();
    });
    return;
  }

  if (Desc->isRecord()) {
    const Record *R = Desc->ElemRecord;
    for (const Record::Field &F : R->fields()) {
      Pointer FieldPtr = Dest.atField(F.Offset);
      zeroAll(FieldPtr);
    }
    return;
  }

  if (Desc->isPrimitiveArray()) {
    for (unsigned I = 0, N = Desc->getNumElems(); I != N; ++I) {
      TYPE_SWITCH(Desc->getPrimType(), {
        Dest.deref<T>().~T();
        new (&Dest.deref<T>()) T();
      });
    }
    return;
  }

  if (Desc->isCompositeArray()) {
    for (unsigned I = 0, N = Desc->getNumElems(); I != N; ++I) {
      Pointer ElemPtr = Dest.atIndex(I).narrow();
      zeroAll(ElemPtr);
    }
    return;
  }
}

static bool copyComposite(InterpState &S, CodePtr OpPC, const Pointer &Src,
                          Pointer &Dest, bool Activate);
static bool copyRecord(InterpState &S, CodePtr OpPC, const Pointer &Src,
                       Pointer &Dest, bool Activate = false) {
  [[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc();
  const Descriptor *DestDesc = Dest.getFieldDesc();

  auto copyField = [&](const Record::Field &F, bool Activate) -> bool {
    Pointer DestField = Dest.atField(F.Offset);
    if (OptPrimType FT = S.Ctx.classify(F.Decl->getType())) {
      TYPE_SWITCH(*FT, {
        DestField.deref<T>() = Src.atField(F.Offset).deref<T>();
        if (Src.atField(F.Offset).isInitialized())
          DestField.initialize();
        if (Activate)
          DestField.activate();
      });
      return true;
    }
    // Composite field.
    return copyComposite(S, OpPC, Src.atField(F.Offset), DestField, Activate);
  };

  assert(SrcDesc->isRecord());
  assert(SrcDesc->ElemRecord == DestDesc->ElemRecord);
  const Record *R = DestDesc->ElemRecord;
  for (const Record::Field &F : R->fields()) {
    if (R->isUnion()) {
      // For unions, only copy the active field. Zero all others.
      const Pointer &SrcField = Src.atField(F.Offset);
      if (SrcField.isActive()) {
        if (!copyField(F, /*Activate=*/true))
          return false;
      } else {
        if (!CheckMutable(S, OpPC, Src.atField(F.Offset)))
          return false;
        Pointer DestField = Dest.atField(F.Offset);
        zeroAll(DestField);
      }
    } else {
      if (!copyField(F, Activate))
        return false;
    }
  }

  for (const Record::Base &B : R->bases()) {
    Pointer DestBase = Dest.atField(B.Offset);
    if (!copyRecord(S, OpPC, Src.atField(B.Offset), DestBase, Activate))
      return false;
  }

  Dest.initialize();
  return true;
}

static bool copyComposite(InterpState &S, CodePtr OpPC, const Pointer &Src,
                          Pointer &Dest, bool Activate = false) {
  assert(Src.isLive() && Dest.isLive());

  [[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc();
  const Descriptor *DestDesc = Dest.getFieldDesc();

  assert(!DestDesc->isPrimitive() && !SrcDesc->isPrimitive());

  if (DestDesc->isPrimitiveArray()) {
    assert(SrcDesc->isPrimitiveArray());
    assert(SrcDesc->getNumElems() == DestDesc->getNumElems());
    PrimType ET = DestDesc->getPrimType();
    for (unsigned I = 0, N = DestDesc->getNumElems(); I != N; ++I) {
      Pointer DestElem = Dest.atIndex(I);
      TYPE_SWITCH(ET, {
        DestElem.deref<T>() = Src.elem<T>(I);
        DestElem.initialize();
      });
    }
    return true;
  }

  if (DestDesc->isCompositeArray()) {
    assert(SrcDesc->isCompositeArray());
    assert(SrcDesc->getNumElems() == DestDesc->getNumElems());
    for (unsigned I = 0, N = DestDesc->getNumElems(); I != N; ++I) {
      const Pointer &SrcElem = Src.atIndex(I).narrow();
      Pointer DestElem = Dest.atIndex(I).narrow();
      if (!copyComposite(S, OpPC, SrcElem, DestElem, Activate))
        return false;
    }
    return true;
  }

  if (DestDesc->isRecord())
    return copyRecord(S, OpPC, Src, Dest, Activate);
  return Invalid(S, OpPC);
}

bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest) {
  return copyComposite(S, OpPC, Src, Dest);
}

} // namespace interp
} // namespace clang
