Skip to content

Conversation

@HendrikHuebner
Copy link
Contributor

This PR upstreams __builtin_va_copy, and extends the existing tests.

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Nov 24, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 24, 2025

@llvm/pr-subscribers-clangir

Author: Hendrik Hübner (HendrikHuebner)

Changes

This PR upstreams __builtin_va_copy, and extends the existing tests.


Full diff: https://github.com/llvm/llvm-project/pull/169415.diff

4 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+34)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+6-1)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+12)
  • (modified) clang/test/CIR/CodeGen/var_arg.c (+102-1)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a19c4f951fff9..78143ab44fa7e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4704,6 +4704,40 @@ def CIR_VAEndOp : CIR_Op<"va_end"> {
   }];
 }
 
+def CIR_VACopyOp : CIR_Op<"va.copy"> {
+  let summary = "Copied a variable argument list";
+  let description = [{
+    The `cir.copy` operation models the C/C++ va_copy macro.
+    The variable argument list passed as the `$src_list` is copied to an
+    unitialized `va_list` in the destination operand. The next argument that
+    can be extracted from the copied list is the same as the next argument in
+    the source list. The copied list must be destroyed with `va_end`.
+
+    Example:
+
+    ```mlir
+    ```mlir
+    // %args : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
+    %p = cir.cast array_to_ptrdecay %args
+          : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
+          -> !cir.ptr<!rec___va_list_tag>
+    cir.va.copy %p to %dst
+          : (!cir.ptr<!rec___va_list_tag>, !cir.ptr<!rec___va_list_tag>)
+    ```
+
+    ```
+  }];
+
+  let arguments = (ins
+    CIR_PointerType:$dst_list,
+    CIR_PointerType:$src_list
+  );
+
+  let assemblyFormat = [{
+    $src_list `to` $dst_list attr-dict `:` type(operands)
+  }];
+}
+
 def CIR_VAArgOp : CIR_Op<"va_arg"> {
   let summary = "Fetches next variadic element as a given type";
   let description = [{
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index d220fdf4dc8a7..95ddfdfc7358a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -266,7 +266,12 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
   case Builtin::BI__builtin_va_end:
     emitVAEnd(emitVAListRef(e->getArg(0)).getPointer());
     return {};
-
+  case Builtin::BI__builtin_va_copy: {
+    mlir::Value dstPtr = emitVAListRef(e->getArg(0)).getPointer();
+    mlir::Value srcPtr = emitVAListRef(e->getArg(1)).getPointer();
+    cir::VACopyOp::create(builder, dstPtr.getLoc(), dstPtr, srcPtr);
+    return {};
+  }
   case Builtin::BIcos:
   case Builtin::BIcosf:
   case Builtin::BIcosl:
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 6136d48204e0c..f3c1b92fafcd3 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4034,6 +4034,18 @@ mlir::LogicalResult CIRToLLVMVAEndOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+mlir::LogicalResult CIRToLLVMVACopyOpLowering::matchAndRewrite(
+    cir::VACopyOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
+  auto dstList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
+                                               adaptor.getDstList());
+  auto srcList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
+                                               adaptor.getSrcList());
+  rewriter.replaceOpWithNewOp<mlir::LLVM::VaCopyOp>(op, dstList, srcList);
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMVAArgOpLowering::matchAndRewrite(
     cir::VAArgOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/CodeGen/var_arg.c b/clang/test/CIR/CodeGen/var_arg.c
index f5b92c61e11ad..82de39a3db58a 100644
--- a/clang/test/CIR/CodeGen/var_arg.c
+++ b/clang/test/CIR/CodeGen/var_arg.c
@@ -141,7 +141,7 @@ int stdarg_start(int count, ...) {
 // OGCG:   %[[COND:.+]] = icmp ule i32 %[[GPOFFSET]], 40
 // OGCG:   br i1 %[[COND]], label %vaarg.in_reg, label %vaarg.in_mem
 //
-// OGCG: vaarg.in_reg:                                   
+// OGCG: vaarg.in_reg:
 // OGCG:   %[[REGSAVE_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY1]], i32 0, i32 3
 // OGCG:   %[[REGSAVE:.+]] = load ptr, ptr %[[REGSAVE_PTR]]
 // OGCG:   %[[VAADDR1:.+]] = getelementptr i8, ptr %[[REGSAVE]], i32 %[[GPOFFSET]]
@@ -164,3 +164,104 @@ int stdarg_start(int count, ...) {
 // OGCG:   call void @llvm.va_end.p0(ptr %[[DECAY2]])
 // OGCG:   %[[VAL:.+]] = load i32, ptr %[[RES_ADDR]]
 // OGCG:   ret i32 %[[VAL]]
+
+int stdarg_copy(int count, ...) {
+    __builtin_va_list args;
+    __builtin_stdarg_start(args, 12345);
+    __builtin_va_list dup;
+    __builtin_va_copy(args, dup);
+    int res = __builtin_va_arg(dup, int);
+    __builtin_va_end(args);
+    __builtin_va_end(dup);
+    return res;
+}
+
+// CIR-LABEL: cir.func dso_local @stdarg_copy(
+// CIR:   %[[COUNT_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["count", init]
+// CIR:   %[[RET_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"]
+// CIR:   %[[VAAREA:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["args"]
+// CIR:   %[[DUP_AREA:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["dup"]
+// CIR:   %[[RES_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["res", init]
+// CIR:   cir.store %arg0, %[[COUNT_ADDR]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[VA_PTR0:.+]] = cir.cast array_to_ptrdecay %[[VAAREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[C12345:.+]] = cir.const #cir.int<12345> : !s32i
+// CIR:   cir.va_start %[[VA_PTR0]] %[[C12345]] : !cir.ptr<!rec___va_list_tag>, !s32i
+// CIR:   %[[VA_PTR_SRC:.+]] = cir.cast array_to_ptrdecay %[[VAAREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_PTR_DEST:.+]] = cir.cast array_to_ptrdecay %[[DUP_AREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   cir.va.copy %[[VA_PTR_DEST]] to %[[VA_PTR_SRC]] : !cir.ptr<!rec___va_list_tag>, !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_PTR_COPY:.+]] = cir.cast array_to_ptrdecay %[[DUP_AREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_ARG:.+]] = cir.va_arg %[[VA_PTR_COPY]] : (!cir.ptr<!rec___va_list_tag>) -> !s32i
+// CIR:   cir.store{{.*}} %[[VA_ARG]], %[[RES_ADDR]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[VA_PTR2:.+]] = cir.cast array_to_ptrdecay %[[VAAREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   cir.va_end %[[VA_PTR2]] : !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_PTR3:.+]] = cir.cast array_to_ptrdecay %[[DUP_AREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   cir.va_end %[[VA_PTR3]] : !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[RESULT:.+]] = cir.load{{.*}} %[[RES_ADDR]] : !cir.ptr<!s32i>, !s32i
+// CIR:   cir.store %[[RESULT]], %[[RET_ADDR]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[RETVAL:.+]] = cir.load{{.*}} %[[RET_ADDR]] : !cir.ptr<!s32i>, !s32i
+// CIR:   cir.return %[[RETVAL]] : !s32i
+
+// LLVM-LABEL: define dso_local i32 @stdarg_copy(
+// LLVM:   %[[COUNT_ADDR:.+]] = alloca i32{{.*}}
+// LLVM:   %[[RET_ADDR:.+]] = alloca i32{{.*}}
+// LLVM:   %[[VAAREA:.+]] = alloca [1 x %struct.__va_list_tag]{{.*}}
+// LLVM:   %[[DUP_AREA:.+]] = alloca [1 x %struct.__va_list_tag]{{.*}}
+// LLVM:   %[[RES_ADDR:.+]] = alloca i32{{.*}}
+// LLVM:   %[[VA_PTR0:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
+// LLVM:   call void @llvm.va_start.p0(ptr %[[VA_PTR0]])
+// LLVM:   %[[VA_PTR1:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
+// LLVM:   %[[VA_PTR_DUP:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
+// LLVM:   call void @llvm.va_copy.p0(ptr %[[VA_PTR1]], ptr %[[VA_PTR_DUP]])
+// LLVM:   %[[VA_PTR_DUP2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
+// LLVM:   %[[VA_ARG:.+]] = va_arg ptr %[[VA_PTR_DUP2]], i32
+// LLVM:   store i32 %[[VA_ARG]], ptr %[[RES_ADDR]], {{.*}}
+// LLVM:   %[[VA_PTR2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
+// LLVM:   call void @llvm.va_end.p0(ptr %[[VA_PTR2]])
+// LLVM:   %[[VA_PTR3:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
+// LLVM:   call void @llvm.va_end.p0(ptr %[[VA_PTR3]])
+// LLVM:   %[[TMP_LOAD:.+]] = load i32, ptr %[[RES_ADDR]], {{.*}}
+// LLVM:   store i32 %[[TMP_LOAD]], ptr %[[RET_ADDR]], {{.*}}
+// LLVM:   %[[RETVAL:.+]] = load i32, ptr %[[RET_ADDR]], {{.*}}
+// LLVM:   ret i32 %[[RETVAL]]
+
+// OGCG-LABEL: define dso_local i32 @stdarg_copy
+// OGCG:   %[[COUNT_ADDR:.+]] = alloca i32
+// OGCG:   %[[VAAREA:.+]] = alloca [1 x %struct.__va_list_tag]
+// OGCG:   %[[DUP_AREA:.+]] = alloca [1 x %struct.__va_list_tag]
+// OGCG:   %[[RES_ADDR:.+]] = alloca i32
+// OGCG:   %[[DECAY:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[VAAREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_start.p0(ptr %[[DECAY]])
+// OGCG:   %[[DECAY1:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[VAAREA]], i64 0, i64 0
+// OGCG:   %[[DECAY_DUP:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[DUP_AREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_copy.p0(ptr %[[DECAY1]], ptr %[[DECAY_DUP]])
+// OGCG:   %[[DECAY_DUP2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[DUP_AREA]], i64 0, i64 0
+// OGCG:   %[[GPOFFSET_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY_DUP2]], i32 0, i32 0
+// OGCG:   %[[GPOFFSET:.+]] = load i32, ptr %[[GPOFFSET_PTR]]
+// OGCG:   %[[COND:.+]] = icmp ule i32 %[[GPOFFSET]], 40
+// OGCG:   br i1 %[[COND]], label %vaarg.in_reg, label %vaarg.in_mem
+//
+// OGCG: vaarg.in_reg:
+// OGCG:   %[[REGSAVE_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY_DUP2]], i32 0, i32 3
+// OGCG:   %[[REGSAVE:.+]] = load ptr, ptr %[[REGSAVE_PTR]]
+// OGCG:   %[[VAADDR1:.+]] = getelementptr i8, ptr %[[REGSAVE]], i32 %[[GPOFFSET]]
+// OGCG:   %[[NEXT_GPOFFSET:.+]] = add i32 %[[GPOFFSET]], 8
+// OGCG:   store i32 %[[NEXT_GPOFFSET]], ptr %[[GPOFFSET_PTR]]
+// OGCG:   br label %vaarg.end
+//
+// OGCG: vaarg.in_mem:
+// OGCG:   %[[OVERFLOW_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY_DUP2]], i32 0, i32 2
+// OGCG:   %[[OVERFLOW:.+]] = load ptr, ptr %[[OVERFLOW_PTR]]
+// OGCG:   %[[OVERFLOW_NEXT:.+]] = getelementptr i8, ptr %[[OVERFLOW]], i32 8
+// OGCG:   store ptr %[[OVERFLOW_NEXT]], ptr %[[OVERFLOW_PTR]]
+// OGCG:   br label %vaarg.end
+//
+// OGCG: vaarg.end:
+// OGCG:   %[[PHI:.+]] = phi ptr [ %[[VAADDR1]], %vaarg.in_reg ], [ %[[OVERFLOW]], %vaarg.in_mem ]
+// OGCG:   %[[LOADED:.+]] = load i32, ptr %[[PHI]]
+// OGCG:   store i32 %[[LOADED]], ptr %[[RES_ADDR]]
+// OGCG:   %[[DECAY2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[VAAREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_end.p0(ptr %[[DECAY2]])
+// OGCG:   %[[DECAY_DUP3:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[DUP_AREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_end.p0(ptr %[[DECAY_DUP3]])
+// OGCG:   %[[VAL:.+]] = load i32, ptr %[[RES_ADDR]]
+// OGCG:   ret i32 %[[VAL]]

@llvmbot
Copy link
Member

llvmbot commented Nov 24, 2025

@llvm/pr-subscribers-clang

Author: Hendrik Hübner (HendrikHuebner)

Changes

This PR upstreams __builtin_va_copy, and extends the existing tests.


Full diff: https://github.com/llvm/llvm-project/pull/169415.diff

4 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+34)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+6-1)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+12)
  • (modified) clang/test/CIR/CodeGen/var_arg.c (+102-1)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a19c4f951fff9..78143ab44fa7e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4704,6 +4704,40 @@ def CIR_VAEndOp : CIR_Op<"va_end"> {
   }];
 }
 
+def CIR_VACopyOp : CIR_Op<"va.copy"> {
+  let summary = "Copied a variable argument list";
+  let description = [{
+    The `cir.copy` operation models the C/C++ va_copy macro.
+    The variable argument list passed as the `$src_list` is copied to an
+    unitialized `va_list` in the destination operand. The next argument that
+    can be extracted from the copied list is the same as the next argument in
+    the source list. The copied list must be destroyed with `va_end`.
+
+    Example:
+
+    ```mlir
+    ```mlir
+    // %args : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
+    %p = cir.cast array_to_ptrdecay %args
+          : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
+          -> !cir.ptr<!rec___va_list_tag>
+    cir.va.copy %p to %dst
+          : (!cir.ptr<!rec___va_list_tag>, !cir.ptr<!rec___va_list_tag>)
+    ```
+
+    ```
+  }];
+
+  let arguments = (ins
+    CIR_PointerType:$dst_list,
+    CIR_PointerType:$src_list
+  );
+
+  let assemblyFormat = [{
+    $src_list `to` $dst_list attr-dict `:` type(operands)
+  }];
+}
+
 def CIR_VAArgOp : CIR_Op<"va_arg"> {
   let summary = "Fetches next variadic element as a given type";
   let description = [{
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index d220fdf4dc8a7..95ddfdfc7358a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -266,7 +266,12 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
   case Builtin::BI__builtin_va_end:
     emitVAEnd(emitVAListRef(e->getArg(0)).getPointer());
     return {};
-
+  case Builtin::BI__builtin_va_copy: {
+    mlir::Value dstPtr = emitVAListRef(e->getArg(0)).getPointer();
+    mlir::Value srcPtr = emitVAListRef(e->getArg(1)).getPointer();
+    cir::VACopyOp::create(builder, dstPtr.getLoc(), dstPtr, srcPtr);
+    return {};
+  }
   case Builtin::BIcos:
   case Builtin::BIcosf:
   case Builtin::BIcosl:
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 6136d48204e0c..f3c1b92fafcd3 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4034,6 +4034,18 @@ mlir::LogicalResult CIRToLLVMVAEndOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+mlir::LogicalResult CIRToLLVMVACopyOpLowering::matchAndRewrite(
+    cir::VACopyOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
+  auto dstList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
+                                               adaptor.getDstList());
+  auto srcList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
+                                               adaptor.getSrcList());
+  rewriter.replaceOpWithNewOp<mlir::LLVM::VaCopyOp>(op, dstList, srcList);
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMVAArgOpLowering::matchAndRewrite(
     cir::VAArgOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/CodeGen/var_arg.c b/clang/test/CIR/CodeGen/var_arg.c
index f5b92c61e11ad..82de39a3db58a 100644
--- a/clang/test/CIR/CodeGen/var_arg.c
+++ b/clang/test/CIR/CodeGen/var_arg.c
@@ -141,7 +141,7 @@ int stdarg_start(int count, ...) {
 // OGCG:   %[[COND:.+]] = icmp ule i32 %[[GPOFFSET]], 40
 // OGCG:   br i1 %[[COND]], label %vaarg.in_reg, label %vaarg.in_mem
 //
-// OGCG: vaarg.in_reg:                                   
+// OGCG: vaarg.in_reg:
 // OGCG:   %[[REGSAVE_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY1]], i32 0, i32 3
 // OGCG:   %[[REGSAVE:.+]] = load ptr, ptr %[[REGSAVE_PTR]]
 // OGCG:   %[[VAADDR1:.+]] = getelementptr i8, ptr %[[REGSAVE]], i32 %[[GPOFFSET]]
@@ -164,3 +164,104 @@ int stdarg_start(int count, ...) {
 // OGCG:   call void @llvm.va_end.p0(ptr %[[DECAY2]])
 // OGCG:   %[[VAL:.+]] = load i32, ptr %[[RES_ADDR]]
 // OGCG:   ret i32 %[[VAL]]
+
+int stdarg_copy(int count, ...) {
+    __builtin_va_list args;
+    __builtin_stdarg_start(args, 12345);
+    __builtin_va_list dup;
+    __builtin_va_copy(args, dup);
+    int res = __builtin_va_arg(dup, int);
+    __builtin_va_end(args);
+    __builtin_va_end(dup);
+    return res;
+}
+
+// CIR-LABEL: cir.func dso_local @stdarg_copy(
+// CIR:   %[[COUNT_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["count", init]
+// CIR:   %[[RET_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"]
+// CIR:   %[[VAAREA:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["args"]
+// CIR:   %[[DUP_AREA:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["dup"]
+// CIR:   %[[RES_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["res", init]
+// CIR:   cir.store %arg0, %[[COUNT_ADDR]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[VA_PTR0:.+]] = cir.cast array_to_ptrdecay %[[VAAREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[C12345:.+]] = cir.const #cir.int<12345> : !s32i
+// CIR:   cir.va_start %[[VA_PTR0]] %[[C12345]] : !cir.ptr<!rec___va_list_tag>, !s32i
+// CIR:   %[[VA_PTR_SRC:.+]] = cir.cast array_to_ptrdecay %[[VAAREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_PTR_DEST:.+]] = cir.cast array_to_ptrdecay %[[DUP_AREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   cir.va.copy %[[VA_PTR_DEST]] to %[[VA_PTR_SRC]] : !cir.ptr<!rec___va_list_tag>, !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_PTR_COPY:.+]] = cir.cast array_to_ptrdecay %[[DUP_AREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_ARG:.+]] = cir.va_arg %[[VA_PTR_COPY]] : (!cir.ptr<!rec___va_list_tag>) -> !s32i
+// CIR:   cir.store{{.*}} %[[VA_ARG]], %[[RES_ADDR]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[VA_PTR2:.+]] = cir.cast array_to_ptrdecay %[[VAAREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   cir.va_end %[[VA_PTR2]] : !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[VA_PTR3:.+]] = cir.cast array_to_ptrdecay %[[DUP_AREA]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>> -> !cir.ptr<!rec___va_list_tag>
+// CIR:   cir.va_end %[[VA_PTR3]] : !cir.ptr<!rec___va_list_tag>
+// CIR:   %[[RESULT:.+]] = cir.load{{.*}} %[[RES_ADDR]] : !cir.ptr<!s32i>, !s32i
+// CIR:   cir.store %[[RESULT]], %[[RET_ADDR]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[RETVAL:.+]] = cir.load{{.*}} %[[RET_ADDR]] : !cir.ptr<!s32i>, !s32i
+// CIR:   cir.return %[[RETVAL]] : !s32i
+
+// LLVM-LABEL: define dso_local i32 @stdarg_copy(
+// LLVM:   %[[COUNT_ADDR:.+]] = alloca i32{{.*}}
+// LLVM:   %[[RET_ADDR:.+]] = alloca i32{{.*}}
+// LLVM:   %[[VAAREA:.+]] = alloca [1 x %struct.__va_list_tag]{{.*}}
+// LLVM:   %[[DUP_AREA:.+]] = alloca [1 x %struct.__va_list_tag]{{.*}}
+// LLVM:   %[[RES_ADDR:.+]] = alloca i32{{.*}}
+// LLVM:   %[[VA_PTR0:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
+// LLVM:   call void @llvm.va_start.p0(ptr %[[VA_PTR0]])
+// LLVM:   %[[VA_PTR1:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
+// LLVM:   %[[VA_PTR_DUP:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
+// LLVM:   call void @llvm.va_copy.p0(ptr %[[VA_PTR1]], ptr %[[VA_PTR_DUP]])
+// LLVM:   %[[VA_PTR_DUP2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
+// LLVM:   %[[VA_ARG:.+]] = va_arg ptr %[[VA_PTR_DUP2]], i32
+// LLVM:   store i32 %[[VA_ARG]], ptr %[[RES_ADDR]], {{.*}}
+// LLVM:   %[[VA_PTR2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
+// LLVM:   call void @llvm.va_end.p0(ptr %[[VA_PTR2]])
+// LLVM:   %[[VA_PTR3:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
+// LLVM:   call void @llvm.va_end.p0(ptr %[[VA_PTR3]])
+// LLVM:   %[[TMP_LOAD:.+]] = load i32, ptr %[[RES_ADDR]], {{.*}}
+// LLVM:   store i32 %[[TMP_LOAD]], ptr %[[RET_ADDR]], {{.*}}
+// LLVM:   %[[RETVAL:.+]] = load i32, ptr %[[RET_ADDR]], {{.*}}
+// LLVM:   ret i32 %[[RETVAL]]
+
+// OGCG-LABEL: define dso_local i32 @stdarg_copy
+// OGCG:   %[[COUNT_ADDR:.+]] = alloca i32
+// OGCG:   %[[VAAREA:.+]] = alloca [1 x %struct.__va_list_tag]
+// OGCG:   %[[DUP_AREA:.+]] = alloca [1 x %struct.__va_list_tag]
+// OGCG:   %[[RES_ADDR:.+]] = alloca i32
+// OGCG:   %[[DECAY:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[VAAREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_start.p0(ptr %[[DECAY]])
+// OGCG:   %[[DECAY1:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[VAAREA]], i64 0, i64 0
+// OGCG:   %[[DECAY_DUP:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[DUP_AREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_copy.p0(ptr %[[DECAY1]], ptr %[[DECAY_DUP]])
+// OGCG:   %[[DECAY_DUP2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[DUP_AREA]], i64 0, i64 0
+// OGCG:   %[[GPOFFSET_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY_DUP2]], i32 0, i32 0
+// OGCG:   %[[GPOFFSET:.+]] = load i32, ptr %[[GPOFFSET_PTR]]
+// OGCG:   %[[COND:.+]] = icmp ule i32 %[[GPOFFSET]], 40
+// OGCG:   br i1 %[[COND]], label %vaarg.in_reg, label %vaarg.in_mem
+//
+// OGCG: vaarg.in_reg:
+// OGCG:   %[[REGSAVE_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY_DUP2]], i32 0, i32 3
+// OGCG:   %[[REGSAVE:.+]] = load ptr, ptr %[[REGSAVE_PTR]]
+// OGCG:   %[[VAADDR1:.+]] = getelementptr i8, ptr %[[REGSAVE]], i32 %[[GPOFFSET]]
+// OGCG:   %[[NEXT_GPOFFSET:.+]] = add i32 %[[GPOFFSET]], 8
+// OGCG:   store i32 %[[NEXT_GPOFFSET]], ptr %[[GPOFFSET_PTR]]
+// OGCG:   br label %vaarg.end
+//
+// OGCG: vaarg.in_mem:
+// OGCG:   %[[OVERFLOW_PTR:.+]] = getelementptr inbounds nuw %struct.__va_list_tag, ptr %[[DECAY_DUP2]], i32 0, i32 2
+// OGCG:   %[[OVERFLOW:.+]] = load ptr, ptr %[[OVERFLOW_PTR]]
+// OGCG:   %[[OVERFLOW_NEXT:.+]] = getelementptr i8, ptr %[[OVERFLOW]], i32 8
+// OGCG:   store ptr %[[OVERFLOW_NEXT]], ptr %[[OVERFLOW_PTR]]
+// OGCG:   br label %vaarg.end
+//
+// OGCG: vaarg.end:
+// OGCG:   %[[PHI:.+]] = phi ptr [ %[[VAADDR1]], %vaarg.in_reg ], [ %[[OVERFLOW]], %vaarg.in_mem ]
+// OGCG:   %[[LOADED:.+]] = load i32, ptr %[[PHI]]
+// OGCG:   store i32 %[[LOADED]], ptr %[[RES_ADDR]]
+// OGCG:   %[[DECAY2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[VAAREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_end.p0(ptr %[[DECAY2]])
+// OGCG:   %[[DECAY_DUP3:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[DUP_AREA]], i64 0, i64 0
+// OGCG:   call void @llvm.va_end.p0(ptr %[[DECAY_DUP3]])
+// OGCG:   %[[VAL:.+]] = load i32, ptr %[[RES_ADDR]]
+// OGCG:   ret i32 %[[VAL]]

Copy link
Contributor

@andykaylor andykaylor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here looks good, but the test is showing a problem.

// LLVM: %[[VA_PTR1:.+]] = getelementptr %struct.__va_list_tag, ptr %[[VAAREA]], i32 0
// LLVM: %[[VA_PTR_DUP:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
// LLVM: call void @llvm.va_copy.p0(ptr %[[VA_PTR1]], ptr %[[VA_PTR_DUP]])
// LLVM: %[[VA_PTR_DUP2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[DUP_AREA]], i32 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we've got something missing here that has nothing to do with this PR. Can you use a test that does just the vaarg copy? Maybe something like this (in a different test file):

void test_vaarg_copy() {
  __builtin_va_list args, dup;
  __builtin_va_copy(args, dup);    
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah lets use a simpler test case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants