Skip to content

[X86_64] fix empty structure vaarg in c++ #77907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 21, 2024
Merged

Conversation

hstk30-hw
Copy link
Contributor

SizeInBytes of empty structure is 0 in C, while 1 in C++. And empty structure argument of the function is ignored in X86_64 backend.As a result, the value of variable arguments in C++ is incorrect. fix #77036

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jan 12, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 12, 2024

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-backend-x86

Author: None (hstk30-hw)

Changes

SizeInBytes of empty structure is 0 in C, while 1 in C++. And empty structure argument of the function is ignored in X86_64 backend.As a result, the value of variable arguments in C++ is incorrect. fix #77036


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

2 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/X86.cpp (+5)
  • (added) clang/test/CodeGen/X86/x86_64-vaarg.c (+15)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index d053f41ab168f5..c2c11280838b12 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -2989,6 +2989,11 @@ static Address EmitX86_64VAArgFromMemory(CodeGenFunction &CGF,
   // an 8 byte boundary.
 
   uint64_t SizeInBytes = (CGF.getContext().getTypeSize(Ty) + 7) / 8;
+
+  if (isEmptyRecord(CGF.getContext(), Ty, true)) {
+    SizeInBytes = 0;  
+  }
+
   llvm::Value *Offset =
       llvm::ConstantInt::get(CGF.Int32Ty, (SizeInBytes + 7)  & ~7);
   overflow_arg_area = CGF.Builder.CreateGEP(CGF.Int8Ty, overflow_arg_area,
diff --git a/clang/test/CodeGen/X86/x86_64-vaarg.c b/clang/test/CodeGen/X86/x86_64-vaarg.c
new file mode 100644
index 00000000000000..ae201a2d3f3017
--- /dev/null
+++ b/clang/test/CodeGen/X86/x86_64-vaarg.c
@@ -0,0 +1,15 @@
+// RUN: %clang -xc++ -target x86_64-linux-gnu -emit-llvm -S -o - %s | FileCheck %s
+
+struct Empty {};
+
+struct Empty emptyvar;
+
+void take_args(int a, ...) {
+    // CHECK:      %overflow_arg_area = load ptr, ptr %overflow_arg_area_p, align 8
+    // CHECK-NEXT: %overflow_arg_area.next = getelementptr i8, ptr %overflow_arg_area, i32 0
+    // CHECK-NEXT: store ptr %overflow_arg_area.next, ptr %overflow_arg_area_p, align 8
+    __builtin_va_list l;
+    __builtin_va_start(l, a);
+    emptyvar = __builtin_va_arg(l, struct Empty);
+    __builtin_va_end(l);
+}

@llvmbot
Copy link
Member

llvmbot commented Jan 12, 2024

@llvm/pr-subscribers-clang

Author: None (hstk30-hw)

Changes

SizeInBytes of empty structure is 0 in C, while 1 in C++. And empty structure argument of the function is ignored in X86_64 backend.As a result, the value of variable arguments in C++ is incorrect. fix #77036


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

2 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/X86.cpp (+5)
  • (added) clang/test/CodeGen/X86/x86_64-vaarg.c (+15)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index d053f41ab168f5..c2c11280838b12 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -2989,6 +2989,11 @@ static Address EmitX86_64VAArgFromMemory(CodeGenFunction &CGF,
   // an 8 byte boundary.
 
   uint64_t SizeInBytes = (CGF.getContext().getTypeSize(Ty) + 7) / 8;
+
+  if (isEmptyRecord(CGF.getContext(), Ty, true)) {
+    SizeInBytes = 0;  
+  }
+
   llvm::Value *Offset =
       llvm::ConstantInt::get(CGF.Int32Ty, (SizeInBytes + 7)  & ~7);
   overflow_arg_area = CGF.Builder.CreateGEP(CGF.Int8Ty, overflow_arg_area,
diff --git a/clang/test/CodeGen/X86/x86_64-vaarg.c b/clang/test/CodeGen/X86/x86_64-vaarg.c
new file mode 100644
index 00000000000000..ae201a2d3f3017
--- /dev/null
+++ b/clang/test/CodeGen/X86/x86_64-vaarg.c
@@ -0,0 +1,15 @@
+// RUN: %clang -xc++ -target x86_64-linux-gnu -emit-llvm -S -o - %s | FileCheck %s
+
+struct Empty {};
+
+struct Empty emptyvar;
+
+void take_args(int a, ...) {
+    // CHECK:      %overflow_arg_area = load ptr, ptr %overflow_arg_area_p, align 8
+    // CHECK-NEXT: %overflow_arg_area.next = getelementptr i8, ptr %overflow_arg_area, i32 0
+    // CHECK-NEXT: store ptr %overflow_arg_area.next, ptr %overflow_arg_area_p, align 8
+    __builtin_va_list l;
+    __builtin_va_start(l, a);
+    emptyvar = __builtin_va_arg(l, struct Empty);
+    __builtin_va_end(l);
+}

Copy link

github-actions bot commented Jan 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@hstk30-hw hstk30-hw force-pushed the va_arg branch 2 times, most recently from fa84978 to 63ed15b Compare January 12, 2024 11:49
@hstk30-hw hstk30-hw added the ABI Application Binary Interface label Jan 12, 2024
@hstk30-hw hstk30-hw force-pushed the va_arg branch 2 times, most recently from 0cb5f9a to 8e1d61d Compare January 13, 2024 03:36
Comment on lines 2991 to 2994
uint64_t SizeInBytes = (CGF.getContext().getTypeSize(Ty) + 7) / 8;

if (isEmptyRecord(CGF.getContext(), Ty, true)) {
SizeInBytes = 0;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion:

uint64_t SizeInBytes = 0;
if (!isEmptyRecord(CGF.getContext(), Ty, true))
  SizeInBytes = (CGF.getContext().getTypeSize(Ty) + 7) / 8;

Copy link
Contributor

Choose a reason for hiding this comment

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

done

@phoebewang
Copy link
Contributor

I checked it locally, the patch doesn't fix the reported problem:

$ clang pr77036.cpp && ./a.out
-nan
Fail

@CoTinker
Copy link
Contributor

I checked it locally, the patch doesn't fix the reported problem:

$ clang pr77036.cpp && ./a.out
-nan
Fail

It seems that struct S14{} is empty record, but struct S14 { union{}a;} is not.

@CoTinker
Copy link
Contributor

I've revised it. Can you check it again? @phoebewang

@phoebewang
Copy link
Contributor

I've revised it. Can you check it again? @phoebewang

I'm not sure the usage of the isEmptyRecord. Tagging @asb who modified the interface recently.

@phoebewang phoebewang requested a review from asb January 17, 2024 05:51
uint64_t SizeInBytes = (CGF.getContext().getTypeSize(Ty) + 7) / 8;
uint64_t SizeInBytes = 0;
if (!isEmptyRecord(CGF.getContext(), Ty, true, true))
SizeInBytes = (CGF.getContext().getTypeSize(Ty) + 7) / 8;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Using isEmptyRecord() like this doesn't seem right. I mean, it's probably pretty close to being correct in most cases, but I suspect there are edge cases where it does something slightly different.

Also, as far as I can tell, we aren't supposed to realign the va_list if an argument is empty (which could matter for an empty struct with an alignment attribute).

I think we should be using the result of classifyArgumentType() here to check what to do here. If the ABIArgInfo is "ignore", the argument isn't passed at all, so we shouldn't access the va_list. We should just make a temporary and return its address.

@CoTinker
Copy link
Contributor

CoTinker commented Mar 5, 2024

ping~

llvm::Value *Load = CGF.Builder.CreateLoad(VAListAddr);
llvm::Value *Offset = llvm::ConstantInt::get(CGF.Int32Ty, 0);
Load = CGF.Builder.CreateGEP(CGF.Int8Ty, Load, Offset);
return Address(Load, CGF.ConvertTypeForMem(Ty), Align);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think the address here is guaranteed to be appropriately aligned. It's probably better to create a temporary like I suggested before.

Also, a GEP with offset 0 does nothing.

Copy link
Contributor

Choose a reason for hiding this comment

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

how to create a temporary?

Copy link
Collaborator

Choose a reason for hiding this comment

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

CGF.CreateMemTemp()

@CoTinker
Copy link
Contributor

ping~

Comment on lines 3018 to 3020
if (AI.isIgnore()) {
return CGF.CreateMemTemp(Ty);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it behave differently between C and C++? Maybe adding a C test for it?

Besides, do not use parentheses for single line code.

@phoebewang
Copy link
Contributor

Checked both pr77036.cpp and pr77036.c get the same result, so looks like a right fix. But I want to wait @efriedma-quic to sign off.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

Code change looks fine; the regression test needs a few tweaks.


// CHECK-LABEL: define{{.*}} void @{{.*}}empty_record_test{{.*}}()
empty empty_record_test(void) {
// CHECK: [[RET:%[a-z]+]] = alloca %struct.empty, align 1
Copy link
Collaborator

Choose a reason for hiding this comment

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

This test doesn't really make it obvious that this is working the way you want; I mean, the existence of TMP sort of hints at it, but it's not comprehensive.

I'd suggest:

  • Make the va_list an argument to the function, instead of declaring it as a local.
  • Consider autogenerating the CHECK lines using update_cc_test_checks.py

SizeInBytes of empty structure is 0 in C, while 1 in C++.
And empty structure argument of the function is ignored
in X86_64 backend.As a result, the value of variable
arguments in C++ is incorrect.So we should just make a
temporary and return its address.
Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

LGTM

@hstk30-hw hstk30-hw merged commit 631248d into llvm:main Mar 21, 2024
chencha3 pushed a commit to chencha3/llvm-project that referenced this pull request Mar 23, 2024
SizeInBytes of empty structure is 0 in C, while 1 in C++. And empty
structure argument of the function is ignored in X86_64 backend.As a
result, the value of variable arguments in C++ is incorrect. fix llvm#77036

Co-authored-by: Longsheng Mou <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ABI Application Binary Interface backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[x86_64][clang] Empty structure argument are ignored in function variable arguments.
5 participants