[clang] [Clang][CodeGen][Xtensa] Implement Xtensa ABI. (PR #225216)
Andrei Safronov via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 22 11:47:57 PDT 2026
https://github.com/andreisfr updated https://github.com/llvm/llvm-project/pull/225216
>From fef16b4e6dfb79a39c618d37a66b4da6413e2284 Mon Sep 17 00:00:00 2001
From: Andrei Safronov <safronov at espressif.com>
Date: Tue, 22 Sep 2026 01:29:35 +0300
Subject: [PATCH 1/2] [Clang][CodeGen][Xtensa] Implement Xtensa ABI.
Implement initial version of the Xtensa ABI with test.
---
clang/lib/CodeGen/CMakeLists.txt | 1 +
clang/lib/CodeGen/CodeGenModule.cpp | 2 +
clang/lib/CodeGen/TargetInfo.h | 3 +
clang/lib/CodeGen/Targets/Xtensa.cpp | 249 +++++++++++++++
clang/test/CodeGen/Xtensa/xtensa-abi.c | 413 +++++++++++++++++++++++++
5 files changed, 668 insertions(+)
create mode 100644 clang/lib/CodeGen/Targets/Xtensa.cpp
create mode 100644 clang/test/CodeGen/Xtensa/xtensa-abi.c
diff --git a/clang/lib/CodeGen/CMakeLists.txt b/clang/lib/CodeGen/CMakeLists.txt
index d3986ddecfe124..5a226dab33cfff 100644
--- a/clang/lib/CodeGen/CMakeLists.txt
+++ b/clang/lib/CodeGen/CMakeLists.txt
@@ -160,6 +160,7 @@ add_clang_library(clangCodeGen
Targets/WebAssembly.cpp
Targets/X86.cpp
Targets/XCore.cpp
+ Targets/Xtensa.cpp
TrapReasonBuilder.cpp
VarBypassDetector.cpp
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 70445dd65ea2bc..fbd4c62121ff7c 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -337,6 +337,8 @@ createTargetCodeGenInfo(CodeGenModule &CGM) {
return createLoongArchTargetCodeGenInfo(
CGM, Target.getPointerWidth(LangAS::Default), ABIFRLen);
}
+ case llvm::Triple::xtensa:
+ return createXtensaTargetCodeGenInfo(CGM);
}
}
diff --git a/clang/lib/CodeGen/TargetInfo.h b/clang/lib/CodeGen/TargetInfo.h
index fae32068b1570a..2c91986e9a14a7 100644
--- a/clang/lib/CodeGen/TargetInfo.h
+++ b/clang/lib/CodeGen/TargetInfo.h
@@ -652,6 +652,9 @@ createWinX86_64TargetCodeGenInfo(CodeGenModule &CGM, X86AVXABILevel AVXLevel);
std::unique_ptr<TargetCodeGenInfo>
createXCoreTargetCodeGenInfo(CodeGenModule &CGM);
+std::unique_ptr<TargetCodeGenInfo>
+createXtensaTargetCodeGenInfo(CodeGenModule &CGM);
+
} // namespace CodeGen
} // namespace clang
diff --git a/clang/lib/CodeGen/Targets/Xtensa.cpp b/clang/lib/CodeGen/Targets/Xtensa.cpp
new file mode 100644
index 00000000000000..13417f33381d79
--- /dev/null
+++ b/clang/lib/CodeGen/Targets/Xtensa.cpp
@@ -0,0 +1,249 @@
+//===- Xtensa.cpp ---------------------------------------------------------===//
+//
+// 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 "ABIInfoImpl.h"
+#include "TargetInfo.h"
+
+using namespace clang;
+using namespace clang::CodeGen;
+//===----------------------------------------------------------------------===//
+// Xtensa ABI Implementation
+//===----------------------------------------------------------------------===//
+
+namespace {
+class XtensaABIInfo : public DefaultABIInfo {
+private:
+ static const int MaxNumArgGPRs = 6;
+ static const int MaxNumRetGPRs = 4;
+
+public:
+ XtensaABIInfo(CodeGen::CodeGenTypes &CGT) : DefaultABIInfo(CGT) {}
+
+ // DefaultABIInfo's classifyReturnType and classifyArgumentType are
+ // non-virtual, but computeInfo is virtual, so we overload it.
+ void computeInfo(CGFunctionInfo &FI) const override;
+
+ ABIArgInfo classifyArgumentType(QualType Ty, int &ArgGPRsLeft) const;
+
+ ABIArgInfo classifyReturnType(QualType RetTy) const;
+
+ RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty,
+ AggValueSlot Slot) const override;
+
+ ABIArgInfo extendType(QualType Ty) const;
+};
+} // end anonymous namespace
+
+void XtensaABIInfo::computeInfo(CGFunctionInfo &FI) const {
+ QualType RetTy = FI.getReturnType();
+ if (!getCXXABI().classifyReturnType(FI))
+ FI.getReturnInfo() = classifyReturnType(RetTy);
+
+ int ArgGPRsLeft = MaxNumArgGPRs;
+ for (auto &ArgInfo : FI.arguments()) {
+ ArgInfo.info = classifyArgumentType(ArgInfo.type, ArgGPRsLeft);
+ }
+}
+
+ABIArgInfo XtensaABIInfo::classifyArgumentType(QualType Ty,
+ int &ArgGPRsLeft) const {
+ assert(ArgGPRsLeft <= MaxNumArgGPRs && "Arg GPR tracking underflow");
+ Ty = useFirstFieldIfTransparentUnion(Ty);
+ // Structures with either a non-trivial destructor or a non-trivial
+ // copy constructor are always passed indirectly.
+ if (CGCXXABI::RecordArgABI RAA = getRecordArgABI(Ty, getCXXABI())) {
+ if (ArgGPRsLeft)
+ ArgGPRsLeft -= 1;
+ return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
+ /*ByVal=*/RAA ==
+ CGCXXABI::RAA_DirectInMemory);
+ }
+
+ // Ignore empty structs/unions.
+ if (isEmptyRecord(getContext(), Ty, true))
+ return ABIArgInfo::getIgnore();
+
+ uint64_t Size = getContext().getTypeSize(Ty);
+ uint64_t NeededAlign = getContext().getTypeAlign(Ty);
+ bool MustUseStack = false;
+ int NeededArgGPRs = (Size + 31) / 32;
+
+ if (NeededAlign == (2 * 32))
+ NeededArgGPRs += (ArgGPRsLeft % 2);
+
+ // Put on stack objects which are not fit to 6 registers,
+ // also on stack object which alignment more then 16 bytes and
+ // object with 16-byte alignment if it isn't the first argument.
+ if ((NeededArgGPRs > ArgGPRsLeft) || (NeededAlign > (4 * 32)) ||
+ ((ArgGPRsLeft < 6) && (NeededAlign == (4 * 32)))) {
+ MustUseStack = true;
+ NeededArgGPRs = ArgGPRsLeft;
+ }
+ ArgGPRsLeft -= NeededArgGPRs;
+
+ if (!isAggregateTypeForABI(Ty) && !Ty->isVectorType() && !MustUseStack) {
+ // Treat an enum type as its underlying type.
+ if (const auto *ED = Ty->getAsEnumDecl())
+ Ty = ED->getIntegerType();
+ // All integral types are promoted to XLen width, unless passed on the
+ // stack.
+ if (Size < 32 && Ty->isIntegralOrEnumerationType() && !MustUseStack) {
+ return extendType(Ty);
+ }
+ // Assume that type has 32, 64 or 128 bits
+ return ABIArgInfo::getDirect(llvm::IntegerType::get(getVMContext(), Size));
+ }
+
+ // Aggregates which are <= 6*32 will be passed in registers if possible,
+ // so coerce to integers.
+ if ((Size <= (MaxNumArgGPRs * 32)) && (!MustUseStack)) {
+ if (Size <= 32) {
+ return ABIArgInfo::getDirect(llvm::IntegerType::get(getVMContext(), 32));
+ } else if (NeededAlign == (2 * 32)) {
+ return ABIArgInfo::getDirect(llvm::ArrayType::get(
+ llvm::IntegerType::get(getVMContext(), 64), NeededArgGPRs / 2));
+ } else if (NeededAlign == (4 * 32)) {
+ return ABIArgInfo::getDirect(llvm::IntegerType::get(getVMContext(), 128));
+ } else {
+ return ABIArgInfo::getDirect(llvm::ArrayType::get(
+ llvm::IntegerType::get(getVMContext(), 32), NeededArgGPRs));
+ }
+ }
+#undef MAX_STRUCT_IN_REGS_SIZE
+ return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
+ /*ByVal=*/true);
+}
+
+ABIArgInfo XtensaABIInfo::classifyReturnType(QualType RetTy) const {
+ if (RetTy->isVoidType())
+ return ABIArgInfo::getIgnore();
+
+ int ArgGPRsLeft = MaxNumRetGPRs;
+ auto RetSize = llvm::alignTo(getContext().getTypeSize(RetTy), 32) / 32;
+
+ // The rules for return and argument with type size more then 4 bytes
+ // are the same, so defer to classifyArgumentType.
+ if (RetSize > 1)
+ return classifyArgumentType(RetTy, ArgGPRsLeft);
+
+ return DefaultABIInfo::classifyReturnType(RetTy);
+}
+
+RValue XtensaABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
+ QualType Ty, AggValueSlot Slot) const {
+ // The va_list structure memory layout:
+ // struct __va_list_tag {
+ // int32_t *va_stk;
+ // int32_t *va_reg;
+ // int32_t va_ndx;
+ // };
+ CGBuilderTy &Builder = CGF.Builder;
+
+ Address OverflowAreaPtr = Builder.CreateStructGEP(VAListAddr, 0, "__va_stk");
+ Address OverflowArea = Address(Builder.CreateLoad(OverflowAreaPtr, ""),
+ CGF.Int32Ty, CharUnits::fromQuantity(4));
+ Address RegSaveAreaPtr = Builder.CreateStructGEP(VAListAddr, 1, "__va_reg");
+ Address RegSaveArea = Address(Builder.CreateLoad(RegSaveAreaPtr, ""),
+ CGF.Int32Ty, CharUnits::fromQuantity(4));
+ Address ARAreaPtr = Builder.CreateStructGEP(VAListAddr, 2, "__va_ndx");
+ llvm::Value *ARIndex = Builder.CreateLoad(ARAreaPtr, "");
+
+ ARIndex = Builder.CreateLShr(ARIndex, Builder.getInt32(2));
+
+ unsigned Align = getContext().getTypeAlign(Ty) / 32;
+ unsigned Size = (getContext().getTypeSize(Ty) + 31) / 32;
+
+ if (Align > 1) {
+ ARIndex = Builder.CreateAdd(ARIndex, Builder.getInt32(Align - 1));
+ ARIndex =
+ Builder.CreateAnd(ARIndex, Builder.getInt32((uint32_t) ~(Align - 1)));
+ }
+
+ llvm::Value *ARIndexNext = Builder.CreateAdd(ARIndex, Builder.getInt32(Size));
+ Builder.CreateStore(Builder.CreateShl(ARIndexNext, Builder.getInt32(2)),
+ ARAreaPtr);
+
+ const unsigned OverflowLimit = 6;
+ llvm::Value *CC = Builder.CreateICmpULE(
+ ARIndexNext, Builder.getInt32(OverflowLimit), "cond");
+
+ llvm::BasicBlock *UsingRegSaveArea =
+ CGF.createBasicBlock("using_regsavearea");
+ llvm::BasicBlock *UsingOverflow = CGF.createBasicBlock("using_overflow");
+ llvm::BasicBlock *Cont = CGF.createBasicBlock("cont");
+
+ Builder.CreateCondBr(CC, UsingRegSaveArea, UsingOverflow);
+
+ llvm::Type *DirectTy = CGF.ConvertType(Ty);
+
+ // Case 1: consume registers.
+ Address RegAddr = Address::invalid();
+ {
+ CGF.EmitBlock(UsingRegSaveArea);
+
+ CharUnits RegSize = CharUnits::fromQuantity(4);
+ RegSaveArea =
+ Address(Builder.CreateInBoundsGEP(
+ CGF.Int32Ty, RegSaveArea.emitRawPointer(CGF), ARIndex),
+ CGF.Int32Ty,
+ RegSaveArea.getAlignment().alignmentOfArrayElement(RegSize));
+ RegAddr = RegSaveArea.withElementType(DirectTy);
+ CGF.EmitBranch(Cont);
+ }
+
+ // Case 2: consume space in the overflow area.
+ Address MemAddr = Address::invalid();
+ {
+ CGF.EmitBlock(UsingOverflow);
+ llvm::Value *CC1 = Builder.CreateICmpULE(
+ ARIndex, Builder.getInt32(OverflowLimit), "cond_overflow");
+
+ llvm::Value *ARIndexOff = Builder.CreateSelect(
+ CC1, Builder.CreateSub(Builder.getInt32(8), ARIndex),
+ Builder.getInt32(0));
+
+ llvm::Value *ARIndexCorr = Builder.CreateAdd(ARIndex, ARIndexOff);
+ llvm::Value *ARIndexNextCorr = Builder.CreateAdd(ARIndexNext, ARIndexOff);
+ Builder.CreateStore(Builder.CreateShl(ARIndexNextCorr, Builder.getInt32(2)),
+ ARAreaPtr);
+
+ CharUnits RegSize = CharUnits::fromQuantity(4);
+ OverflowArea =
+ Address(Builder.CreateInBoundsGEP(
+ CGF.Int32Ty, OverflowArea.emitRawPointer(CGF), ARIndexCorr),
+ CGF.Int32Ty,
+ OverflowArea.getAlignment().alignmentOfArrayElement(RegSize));
+ MemAddr = OverflowArea.withElementType(DirectTy);
+ CGF.EmitBranch(Cont);
+ }
+
+ CGF.EmitBlock(Cont);
+
+ // Merge the cases with a phi.
+ Address Result =
+ emitMergePHI(CGF, RegAddr, UsingRegSaveArea, MemAddr, UsingOverflow, "");
+
+ return CGF.EmitLoadOfAnyValue(CGF.MakeAddrLValue(Result, Ty), Slot);
+}
+
+ABIArgInfo XtensaABIInfo::extendType(QualType Ty) const {
+ return ABIArgInfo::getExtend(Ty);
+}
+
+namespace {
+class XtensaTargetCodeGenInfo : public TargetCodeGenInfo {
+public:
+ XtensaTargetCodeGenInfo(CodeGen::CodeGenTypes &CGT)
+ : TargetCodeGenInfo(std::make_unique<XtensaABIInfo>(CGT)) {}
+};
+} // namespace
+
+std::unique_ptr<TargetCodeGenInfo>
+CodeGen::createXtensaTargetCodeGenInfo(CodeGenModule &CGM) {
+ return std::make_unique<XtensaTargetCodeGenInfo>(CGM.getTypes());
+}
diff --git a/clang/test/CodeGen/Xtensa/xtensa-abi.c b/clang/test/CodeGen/Xtensa/xtensa-abi.c
new file mode 100644
index 00000000000000..947ea96a9ed6ba
--- /dev/null
+++ b/clang/test/CodeGen/Xtensa/xtensa-abi.c
@@ -0,0 +1,413 @@
+// RUN: %clang_cc1 -triple xtensa -O0 -emit-llvm %s -o - | FileCheck %s
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Test scalar arguments
+
+// CHECK-LABEL: define dso_local zeroext i1 @f_scalar_i1(
+// CHECK-SAME: i1 noundef zeroext [[X:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: [[STOREDV:%.*]] = zext i1 [[X]] to i8
+// CHECK-NEXT: store i8 [[STOREDV]], ptr [[X_ADDR]], align 1
+// CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[X_ADDR]], align 1
+// CHECK-NEXT: [[LOADEDV:%.*]] = icmp ne i8 [[TMP0]], 0
+// CHECK-NEXT: ret i1 [[LOADEDV]]
+//
+_Bool f_scalar_i1(_Bool x) { return x; }
+
+// CHECK-LABEL: define dso_local signext i8 @f_scalar_i8(
+// CHECK-SAME: i8 noundef signext [[X:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i8, align 1
+// CHECK-NEXT: store i8 [[X]], ptr [[X_ADDR]], align 1
+// CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[X_ADDR]], align 1
+// CHECK-NEXT: ret i8 [[TMP0]]
+//
+int8_t f_scalar_i8(int8_t x) { return x; }
+
+// CHECK-LABEL: define dso_local zeroext i8 @f_scalar_i16(
+// CHECK-SAME: i16 noundef signext [[X:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i16, align 2
+// CHECK-NEXT: store i16 [[X]], ptr [[X_ADDR]], align 2
+// CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[X_ADDR]], align 2
+// CHECK-NEXT: [[CONV:%.*]] = trunc i16 [[TMP0]] to i8
+// CHECK-NEXT: ret i8 [[CONV]]
+//
+uint8_t f_scalar_i16(int16_t x) { return x; }
+
+// CHECK-LABEL: define dso_local i32 @f_scalar_i32(
+// CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: ret i32 [[TMP0]]
+//
+int32_t f_scalar_i32(int32_t x) { return x; }
+
+// CHECK-LABEL: define dso_local i64 @f_scalar_i64(
+// CHECK-SAME: i64 noundef [[X:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT: store i64 [[X]], ptr [[X_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[X_ADDR]], align 8
+// CHECK-NEXT: ret i64 [[TMP0]]
+//
+int64_t f_scalar_i64(int64_t x) { return x; }
+
+// CHECK-LABEL: define dso_local float @f_scalar_float(
+// CHECK-SAME: i32 noundef [[X_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca float, align 4
+// CHECK-NEXT: store i32 [[X_COERCE]], ptr [[X]], align 4
+// CHECK-NEXT: [[X1:%.*]] = load float, ptr [[X]], align 4
+// CHECK-NEXT: store float [[X1]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load float, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: ret float [[TMP0]]
+//
+float f_scalar_float(float x) { return x; }
+
+// CHECK-LABEL: define dso_local i64 @f_scalar_double(
+// CHECK-SAME: i64 noundef [[X_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[X:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca double, align 8
+// CHECK-NEXT: store i64 [[X_COERCE]], ptr [[X]], align 8
+// CHECK-NEXT: [[X1:%.*]] = load double, ptr [[X]], align 8
+// CHECK-NEXT: store double [[X1]], ptr [[X_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load double, ptr [[X_ADDR]], align 8
+// CHECK-NEXT: store double [[TMP0]], ptr [[RETVAL]], align 8
+// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[RETVAL]], align 8
+// CHECK-NEXT: ret i64 [[TMP1]]
+//
+double f_scalar_double(double x) { return x; }
+
+// Test aggregate arguments
+
+struct S16 { int a[4]; } __attribute__ ((aligned (16)));
+
+// CHECK-LABEL: define dso_local void @callee_struct_a16b_1(
+// CHECK-SAME: i128 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[A:%.*]] = alloca [[STRUCT_S16:%.*]], align 16
+// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_S16]], ptr [[A]], i32 0, i32 0
+// CHECK-NEXT: store i128 [[A_COERCE]], ptr [[COERCE_DIVE]], align 16
+// CHECK-NEXT: ret void
+//
+void callee_struct_a16b_1(struct S16 a) {}
+
+
+// CHECK-LABEL: define dso_local void @callee_struct_a16b_2(
+// CHECK-SAME: i128 [[A_COERCE:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[A:%.*]] = alloca [[STRUCT_S16:%.*]], align 16
+// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_S16]], ptr [[A]], i32 0, i32 0
+// CHECK-NEXT: store i128 [[A_COERCE]], ptr [[COERCE_DIVE]], align 16
+// CHECK-NEXT: store i32 [[B]], ptr [[B_ADDR]], align 4
+// CHECK-NEXT: ret void
+//
+void callee_struct_a16b_2(struct S16 a, int b) {}
+
+
+// CHECK-LABEL: define dso_local void @callee_struct_a16b_3(
+// CHECK-SAME: i32 noundef [[A:%.*]], ptr noundef byval([[STRUCT_S16:%.*]]) align 16 [[B:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i32 [[A]], ptr [[A_ADDR]], align 4
+// CHECK-NEXT: ret void
+//
+void callee_struct_a16b_3(int a, struct S16 b) {}
+
+// Test variable arguments
+int f_va_callee(int, ...);
+
+// CHECK-LABEL: define dso_local void @f_va_caller(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[DOTCOMPOUNDLITERAL:%.*]] = alloca [[STRUCT_S16:%.*]], align 16
+// CHECK-NEXT: [[COERCE:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[BYVAL_TEMP:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_S16]], ptr [[DOTCOMPOUNDLITERAL]], i32 0, i32 0
+// CHECK-NEXT: store i32 6, ptr [[A]], align 4
+// CHECK-NEXT: [[ARRAYINIT_ELEMENT:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 1
+// CHECK-NEXT: store i32 7, ptr [[ARRAYINIT_ELEMENT]], align 4
+// CHECK-NEXT: [[ARRAYINIT_ELEMENT1:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 2
+// CHECK-NEXT: store i32 8, ptr [[ARRAYINIT_ELEMENT1]], align 4
+// CHECK-NEXT: [[ARRAYINIT_ELEMENT2:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 3
+// CHECK-NEXT: store i32 9, ptr [[ARRAYINIT_ELEMENT2]], align 4
+// CHECK-NEXT: store double 4.000000e+00, ptr [[COERCE]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load i64, ptr [[COERCE]], align 8
+// CHECK-NEXT: store double 5.000000e+00, ptr [[BYVAL_TEMP]], align 8
+// CHECK-NEXT: [[CALL:%.*]] = call i32 (i32, ...) @f_va_callee(i32 noundef 1, i32 noundef 2, i64 noundef 3, i64 noundef [[TMP0]], ptr noundef byval(double) align 8 [[BYVAL_TEMP]], ptr noundef byval([[STRUCT_S16]]) align 16 [[DOTCOMPOUNDLITERAL]])
+// CHECK-NEXT: ret void
+//
+void f_va_caller(void) {
+ f_va_callee(1, 2, 3LL, 4.0f, 5.0, (struct S16){6, 7, 8, 9});
+}
+
+// CHECK-LABEL: define dso_local i32 @f_va_1(
+// CHECK-SAME: i32 noundef [[FMT_COERCE:%.*]], ...) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[FMT:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[FMT_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[VA:%.*]] = alloca [[STRUCT___VA_LIST_TAG:%.*]], align 4
+// CHECK-NEXT: [[V:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[COERCE_VAL_IP:%.*]] = inttoptr i32 [[FMT_COERCE]] to ptr
+// CHECK-NEXT: store ptr [[COERCE_VAL_IP]], ptr [[FMT]], align 4
+// CHECK-NEXT: [[FMT1:%.*]] = load ptr, ptr [[FMT]], align 4
+// CHECK-NEXT: store ptr [[FMT1]], ptr [[FMT_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[VA]])
+// CHECK-NEXT: [[__VA_STK:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 0
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[__VA_STK]], align 4
+// CHECK-NEXT: [[__VA_REG:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[__VA_REG]], align 4
+// CHECK-NEXT: [[__VA_NDX:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 2
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = lshr i32 [[TMP2]], 2
+// CHECK-NEXT: [[TMP4:%.*]] = add i32 [[TMP3]], 1
+// CHECK-NEXT: [[TMP5:%.*]] = shl i32 [[TMP4]], 2
+// CHECK-NEXT: store i32 [[TMP5]], ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[COND:%.*]] = icmp ule i32 [[TMP4]], 6
+// CHECK-NEXT: br i1 [[COND]], label %[[USING_REGSAVEAREA:.*]], label %[[USING_OVERFLOW:.*]]
+// CHECK: [[USING_REGSAVEAREA]]:
+// CHECK-NEXT: [[TMP6:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i32 [[TMP3]]
+// CHECK-NEXT: br label %[[CONT:.*]]
+// CHECK: [[USING_OVERFLOW]]:
+// CHECK-NEXT: [[COND_OVERFLOW:%.*]] = icmp ule i32 [[TMP3]], 6
+// CHECK-NEXT: [[TMP7:%.*]] = sub i32 8, [[TMP3]]
+// CHECK-NEXT: [[TMP8:%.*]] = select i1 [[COND_OVERFLOW]], i32 [[TMP7]], i32 0
+// CHECK-NEXT: [[TMP9:%.*]] = add i32 [[TMP3]], [[TMP8]]
+// CHECK-NEXT: [[TMP10:%.*]] = add i32 [[TMP4]], [[TMP8]]
+// CHECK-NEXT: [[TMP11:%.*]] = shl i32 [[TMP10]], 2
+// CHECK-NEXT: store i32 [[TMP11]], ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[TMP12:%.*]] = getelementptr inbounds i32, ptr [[TMP0]], i32 [[TMP9]]
+// CHECK-NEXT: br label %[[CONT]]
+// CHECK: [[CONT]]:
+// CHECK-NEXT: [[TMP13:%.*]] = phi ptr [ [[TMP6]], %[[USING_REGSAVEAREA]] ], [ [[TMP12]], %[[USING_OVERFLOW]] ]
+// CHECK-NEXT: [[TMP14:%.*]] = load i32, ptr [[TMP13]], align 4
+// CHECK-NEXT: store i32 [[TMP14]], ptr [[V]], align 4
+// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[VA]])
+// CHECK-NEXT: [[TMP15:%.*]] = load i32, ptr [[V]], align 4
+// CHECK-NEXT: ret i32 [[TMP15]]
+//
+int f_va_1(char *fmt, ...) {
+ __builtin_va_list va;
+
+ __builtin_va_start(va, fmt);
+ int v = __builtin_va_arg(va, int);
+ __builtin_va_end(va);
+
+ return v;
+}
+
+// CHECK-LABEL: define dso_local i64 @f_va_2(
+// CHECK-SAME: i32 noundef [[FMT_COERCE:%.*]], ...) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[FMT:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[FMT_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[VA:%.*]] = alloca [[STRUCT___VA_LIST_TAG:%.*]], align 4
+// CHECK-NEXT: [[V:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[COERCE_VAL_IP:%.*]] = inttoptr i32 [[FMT_COERCE]] to ptr
+// CHECK-NEXT: store ptr [[COERCE_VAL_IP]], ptr [[FMT]], align 4
+// CHECK-NEXT: [[FMT1:%.*]] = load ptr, ptr [[FMT]], align 4
+// CHECK-NEXT: store ptr [[FMT1]], ptr [[FMT_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[VA]])
+// CHECK-NEXT: [[__VA_STK:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 0
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[__VA_STK]], align 4
+// CHECK-NEXT: [[__VA_REG:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[__VA_REG]], align 4
+// CHECK-NEXT: [[__VA_NDX:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 2
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = lshr i32 [[TMP2]], 2
+// CHECK-NEXT: [[TMP4:%.*]] = add i32 [[TMP3]], 1
+// CHECK-NEXT: [[TMP5:%.*]] = and i32 [[TMP4]], -2
+// CHECK-NEXT: [[TMP6:%.*]] = add i32 [[TMP5]], 2
+// CHECK-NEXT: [[TMP7:%.*]] = shl i32 [[TMP6]], 2
+// CHECK-NEXT: store i32 [[TMP7]], ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[COND:%.*]] = icmp ule i32 [[TMP6]], 6
+// CHECK-NEXT: br i1 [[COND]], label %[[USING_REGSAVEAREA:.*]], label %[[USING_OVERFLOW:.*]]
+// CHECK: [[USING_REGSAVEAREA]]:
+// CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i32 [[TMP5]]
+// CHECK-NEXT: br label %[[CONT:.*]]
+// CHECK: [[USING_OVERFLOW]]:
+// CHECK-NEXT: [[COND_OVERFLOW:%.*]] = icmp ule i32 [[TMP5]], 6
+// CHECK-NEXT: [[TMP9:%.*]] = sub i32 8, [[TMP5]]
+// CHECK-NEXT: [[TMP10:%.*]] = select i1 [[COND_OVERFLOW]], i32 [[TMP9]], i32 0
+// CHECK-NEXT: [[TMP11:%.*]] = add i32 [[TMP5]], [[TMP10]]
+// CHECK-NEXT: [[TMP12:%.*]] = add i32 [[TMP6]], [[TMP10]]
+// CHECK-NEXT: [[TMP13:%.*]] = shl i32 [[TMP12]], 2
+// CHECK-NEXT: store i32 [[TMP13]], ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[TMP14:%.*]] = getelementptr inbounds i32, ptr [[TMP0]], i32 [[TMP11]]
+// CHECK-NEXT: br label %[[CONT]]
+// CHECK: [[CONT]]:
+// CHECK-NEXT: [[TMP15:%.*]] = phi ptr [ [[TMP8]], %[[USING_REGSAVEAREA]] ], [ [[TMP14]], %[[USING_OVERFLOW]] ]
+// CHECK-NEXT: [[TMP16:%.*]] = load double, ptr [[TMP15]], align 4
+// CHECK-NEXT: store double [[TMP16]], ptr [[V]], align 8
+// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[VA]])
+// CHECK-NEXT: [[TMP17:%.*]] = load double, ptr [[V]], align 8
+// CHECK-NEXT: store double [[TMP17]], ptr [[RETVAL]], align 8
+// CHECK-NEXT: [[TMP18:%.*]] = load i64, ptr [[RETVAL]], align 8
+// CHECK-NEXT: ret i64 [[TMP18]]
+//
+double f_va_2(char *fmt, ...) {
+ __builtin_va_list va;
+
+ __builtin_va_start(va, fmt);
+ double v = __builtin_va_arg(va, double);
+ __builtin_va_end(va);
+
+ return v;
+}
+
+// CHECK-LABEL: define dso_local i64 @f_va_3(
+// CHECK-SAME: i32 noundef [[FMT_COERCE:%.*]], ...) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[FMT:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[FMT_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[VA:%.*]] = alloca [[STRUCT___VA_LIST_TAG:%.*]], align 4
+// CHECK-NEXT: [[V:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[W:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[X:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[COERCE_VAL_IP:%.*]] = inttoptr i32 [[FMT_COERCE]] to ptr
+// CHECK-NEXT: store ptr [[COERCE_VAL_IP]], ptr [[FMT]], align 4
+// CHECK-NEXT: [[FMT1:%.*]] = load ptr, ptr [[FMT]], align 4
+// CHECK-NEXT: store ptr [[FMT1]], ptr [[FMT_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[VA]])
+// CHECK-NEXT: [[__VA_STK:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 0
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[__VA_STK]], align 4
+// CHECK-NEXT: [[__VA_REG:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[__VA_REG]], align 4
+// CHECK-NEXT: [[__VA_NDX:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 2
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[TMP3:%.*]] = lshr i32 [[TMP2]], 2
+// CHECK-NEXT: [[TMP4:%.*]] = add i32 [[TMP3]], 1
+// CHECK-NEXT: [[TMP5:%.*]] = and i32 [[TMP4]], -2
+// CHECK-NEXT: [[TMP6:%.*]] = add i32 [[TMP5]], 2
+// CHECK-NEXT: [[TMP7:%.*]] = shl i32 [[TMP6]], 2
+// CHECK-NEXT: store i32 [[TMP7]], ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[COND:%.*]] = icmp ule i32 [[TMP6]], 6
+// CHECK-NEXT: br i1 [[COND]], label %[[USING_REGSAVEAREA:.*]], label %[[USING_OVERFLOW:.*]]
+// CHECK: [[USING_REGSAVEAREA]]:
+// CHECK-NEXT: [[TMP8:%.*]] = getelementptr inbounds i32, ptr [[TMP1]], i32 [[TMP5]]
+// CHECK-NEXT: br label %[[CONT:.*]]
+// CHECK: [[USING_OVERFLOW]]:
+// CHECK-NEXT: [[COND_OVERFLOW:%.*]] = icmp ule i32 [[TMP5]], 6
+// CHECK-NEXT: [[TMP9:%.*]] = sub i32 8, [[TMP5]]
+// CHECK-NEXT: [[TMP10:%.*]] = select i1 [[COND_OVERFLOW]], i32 [[TMP9]], i32 0
+// CHECK-NEXT: [[TMP11:%.*]] = add i32 [[TMP5]], [[TMP10]]
+// CHECK-NEXT: [[TMP12:%.*]] = add i32 [[TMP6]], [[TMP10]]
+// CHECK-NEXT: [[TMP13:%.*]] = shl i32 [[TMP12]], 2
+// CHECK-NEXT: store i32 [[TMP13]], ptr [[__VA_NDX]], align 4
+// CHECK-NEXT: [[TMP14:%.*]] = getelementptr inbounds i32, ptr [[TMP0]], i32 [[TMP11]]
+// CHECK-NEXT: br label %[[CONT]]
+// CHECK: [[CONT]]:
+// CHECK-NEXT: [[TMP15:%.*]] = phi ptr [ [[TMP8]], %[[USING_REGSAVEAREA]] ], [ [[TMP14]], %[[USING_OVERFLOW]] ]
+// CHECK-NEXT: [[TMP16:%.*]] = load double, ptr [[TMP15]], align 4
+// CHECK-NEXT: store double [[TMP16]], ptr [[V]], align 8
+// CHECK-NEXT: [[__VA_STK2:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 0
+// CHECK-NEXT: [[TMP17:%.*]] = load ptr, ptr [[__VA_STK2]], align 4
+// CHECK-NEXT: [[__VA_REG3:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 1
+// CHECK-NEXT: [[TMP18:%.*]] = load ptr, ptr [[__VA_REG3]], align 4
+// CHECK-NEXT: [[__VA_NDX4:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 2
+// CHECK-NEXT: [[TMP19:%.*]] = load i32, ptr [[__VA_NDX4]], align 4
+// CHECK-NEXT: [[TMP20:%.*]] = lshr i32 [[TMP19]], 2
+// CHECK-NEXT: [[TMP21:%.*]] = add i32 [[TMP20]], 1
+// CHECK-NEXT: [[TMP22:%.*]] = shl i32 [[TMP21]], 2
+// CHECK-NEXT: store i32 [[TMP22]], ptr [[__VA_NDX4]], align 4
+// CHECK-NEXT: [[COND5:%.*]] = icmp ule i32 [[TMP21]], 6
+// CHECK-NEXT: br i1 [[COND5]], label %[[USING_REGSAVEAREA6:.*]], label %[[USING_OVERFLOW7:.*]]
+// CHECK: [[USING_REGSAVEAREA6]]:
+// CHECK-NEXT: [[TMP23:%.*]] = getelementptr inbounds i32, ptr [[TMP18]], i32 [[TMP20]]
+// CHECK-NEXT: br label %[[CONT9:.*]]
+// CHECK: [[USING_OVERFLOW7]]:
+// CHECK-NEXT: [[COND_OVERFLOW8:%.*]] = icmp ule i32 [[TMP20]], 6
+// CHECK-NEXT: [[TMP24:%.*]] = sub i32 8, [[TMP20]]
+// CHECK-NEXT: [[TMP25:%.*]] = select i1 [[COND_OVERFLOW8]], i32 [[TMP24]], i32 0
+// CHECK-NEXT: [[TMP26:%.*]] = add i32 [[TMP20]], [[TMP25]]
+// CHECK-NEXT: [[TMP27:%.*]] = add i32 [[TMP21]], [[TMP25]]
+// CHECK-NEXT: [[TMP28:%.*]] = shl i32 [[TMP27]], 2
+// CHECK-NEXT: store i32 [[TMP28]], ptr [[__VA_NDX4]], align 4
+// CHECK-NEXT: [[TMP29:%.*]] = getelementptr inbounds i32, ptr [[TMP17]], i32 [[TMP26]]
+// CHECK-NEXT: br label %[[CONT9]]
+// CHECK: [[CONT9]]:
+// CHECK-NEXT: [[TMP30:%.*]] = phi ptr [ [[TMP23]], %[[USING_REGSAVEAREA6]] ], [ [[TMP29]], %[[USING_OVERFLOW7]] ]
+// CHECK-NEXT: [[TMP31:%.*]] = load i32, ptr [[TMP30]], align 4
+// CHECK-NEXT: store i32 [[TMP31]], ptr [[W]], align 4
+// CHECK-NEXT: [[__VA_STK10:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 0
+// CHECK-NEXT: [[TMP32:%.*]] = load ptr, ptr [[__VA_STK10]], align 4
+// CHECK-NEXT: [[__VA_REG11:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 1
+// CHECK-NEXT: [[TMP33:%.*]] = load ptr, ptr [[__VA_REG11]], align 4
+// CHECK-NEXT: [[__VA_NDX12:%.*]] = getelementptr inbounds nuw [[STRUCT___VA_LIST_TAG]], ptr [[VA]], i32 0, i32 2
+// CHECK-NEXT: [[TMP34:%.*]] = load i32, ptr [[__VA_NDX12]], align 4
+// CHECK-NEXT: [[TMP35:%.*]] = lshr i32 [[TMP34]], 2
+// CHECK-NEXT: [[TMP36:%.*]] = add i32 [[TMP35]], 1
+// CHECK-NEXT: [[TMP37:%.*]] = and i32 [[TMP36]], -2
+// CHECK-NEXT: [[TMP38:%.*]] = add i32 [[TMP37]], 2
+// CHECK-NEXT: [[TMP39:%.*]] = shl i32 [[TMP38]], 2
+// CHECK-NEXT: store i32 [[TMP39]], ptr [[__VA_NDX12]], align 4
+// CHECK-NEXT: [[COND13:%.*]] = icmp ule i32 [[TMP38]], 6
+// CHECK-NEXT: br i1 [[COND13]], label %[[USING_REGSAVEAREA14:.*]], label %[[USING_OVERFLOW15:.*]]
+// CHECK: [[USING_REGSAVEAREA14]]:
+// CHECK-NEXT: [[TMP40:%.*]] = getelementptr inbounds i32, ptr [[TMP33]], i32 [[TMP37]]
+// CHECK-NEXT: br label %[[CONT17:.*]]
+// CHECK: [[USING_OVERFLOW15]]:
+// CHECK-NEXT: [[COND_OVERFLOW16:%.*]] = icmp ule i32 [[TMP37]], 6
+// CHECK-NEXT: [[TMP41:%.*]] = sub i32 8, [[TMP37]]
+// CHECK-NEXT: [[TMP42:%.*]] = select i1 [[COND_OVERFLOW16]], i32 [[TMP41]], i32 0
+// CHECK-NEXT: [[TMP43:%.*]] = add i32 [[TMP37]], [[TMP42]]
+// CHECK-NEXT: [[TMP44:%.*]] = add i32 [[TMP38]], [[TMP42]]
+// CHECK-NEXT: [[TMP45:%.*]] = shl i32 [[TMP44]], 2
+// CHECK-NEXT: store i32 [[TMP45]], ptr [[__VA_NDX12]], align 4
+// CHECK-NEXT: [[TMP46:%.*]] = getelementptr inbounds i32, ptr [[TMP32]], i32 [[TMP43]]
+// CHECK-NEXT: br label %[[CONT17]]
+// CHECK: [[CONT17]]:
+// CHECK-NEXT: [[TMP47:%.*]] = phi ptr [ [[TMP40]], %[[USING_REGSAVEAREA14]] ], [ [[TMP46]], %[[USING_OVERFLOW15]] ]
+// CHECK-NEXT: [[TMP48:%.*]] = load double, ptr [[TMP47]], align 4
+// CHECK-NEXT: store double [[TMP48]], ptr [[X]], align 8
+// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[VA]])
+// CHECK-NEXT: [[TMP49:%.*]] = load double, ptr [[V]], align 8
+// CHECK-NEXT: [[TMP50:%.*]] = load double, ptr [[X]], align 8
+// CHECK-NEXT: [[ADD:%.*]] = fadd double [[TMP49]], [[TMP50]]
+// CHECK-NEXT: store double [[ADD]], ptr [[RETVAL]], align 8
+// CHECK-NEXT: [[TMP51:%.*]] = load i64, ptr [[RETVAL]], align 8
+// CHECK-NEXT: ret i64 [[TMP51]]
+//
+double f_va_3(char *fmt, ...) {
+ __builtin_va_list va;
+
+ __builtin_va_start(va, fmt);
+ double v = __builtin_va_arg(va, double);
+ int w = __builtin_va_arg(va, int);
+ double x = __builtin_va_arg(va, double);
+ __builtin_va_end(va);
+
+ return v + x;
+}
+
+#define __malloc_like __attribute__((__malloc__))
+
+char *bufalloc () __malloc_like ;//__result_use_check;
+extern void* malloc (unsigned size);
+
+// CHECK: define dso_local noalias ptr @bufalloc() #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[BUF:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i32 noundef 1024)
+// CHECK-NEXT: store ptr [[CALL]], ptr [[BUF]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[BUF]], align 4
+// CHECK-NEXT: ret ptr [[TMP0]]
+//
+char *bufalloc ()
+{
+ char* buf = malloc(1024);
+
+ return buf;
+}
>From 3b36a95ea112275ae9fab6ec6d6b564974e54e6a Mon Sep 17 00:00:00 2001
From: Andrei Safronov <safronov at espressif.com>
Date: Tue, 22 Sep 2026 21:45:40 +0300
Subject: [PATCH 2/2] [Clang][CodeGen][Xtensa] Minor fixes in Xtensa ABI
implementation.
---
clang/lib/CodeGen/Targets/Xtensa.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/Xtensa.cpp b/clang/lib/CodeGen/Targets/Xtensa.cpp
index 13417f33381d79..b9e7520dc9b910 100644
--- a/clang/lib/CodeGen/Targets/Xtensa.cpp
+++ b/clang/lib/CodeGen/Targets/Xtensa.cpp
@@ -45,8 +45,8 @@ void XtensaABIInfo::computeInfo(CGFunctionInfo &FI) const {
FI.getReturnInfo() = classifyReturnType(RetTy);
int ArgGPRsLeft = MaxNumArgGPRs;
- for (auto &ArgInfo : FI.arguments()) {
- ArgInfo.info = classifyArgumentType(ArgInfo.type, ArgGPRsLeft);
+ for (auto &[Type, Info] : FI.arguments()) {
+ Info = classifyArgumentType(Type, ArgGPRsLeft);
}
}
@@ -161,7 +161,7 @@ RValue XtensaABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
if (Align > 1) {
ARIndex = Builder.CreateAdd(ARIndex, Builder.getInt32(Align - 1));
ARIndex =
- Builder.CreateAnd(ARIndex, Builder.getInt32((uint32_t) ~(Align - 1)));
+ Builder.CreateAnd(ARIndex, Builder.getInt32((uint32_t)~(Align - 1)));
}
llvm::Value *ARIndexNext = Builder.CreateAdd(ARIndex, Builder.getInt32(Size));
More information about the cfe-commits
mailing list