Skip to content

[clang] Fix clang debug info generation for unprtototyped function #150022

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 3 commits into from
Aug 1, 2025

Conversation

sga-sc
Copy link
Contributor

@sga-sc sga-sc commented Jul 22, 2025

Consider this declaration:

int foo();

This function is described in LLVM with clang::FunctionNoProtoType class. (See description)

Judging by this comment all such functions are treated like functions with variadic number of parameters.

When we want to emit debug info we have to know function that we calling.

In method getCalledFunction() we compare two types of function:

  1. Function that we deduce from calling operand, and
  2. Function that we store locally

If they differ we get nullptr and can't emit appropriate debug info.

The only thing they differ is: lhs function is variadic, but rhs function isn't

Reason of this difference is that under RISC-V there is no overridden function that tells us about treating functions with no parameters. Default function always return false.

This patch overrides this function for RISC-V

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:RISC-V clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jul 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 22, 2025

@llvm/pr-subscribers-debuginfo
@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-backend-risc-v

Author: Georgiy Samoylov (sga-sc)

Changes

Consider this declaration:

int foo();

This function is described in LLVM with clang::FunctionNoProtoType class. (See description)

Judging by this comment all such functions are treated like functions with variadic number of parameters.

When we want to emit debug info we have to know function that we calling.

In method getCalledFunction() we compare two types of function:

  1. Function that we deduce from calling operand, and
  2. Function that we store locally

If they differ we get nullptr and can't emit appropriate debug info.

The only thing they differ is: lhs function is variadic, but rhs function isn't

Reason of this difference is that under RISC-V there is no overridden function that tells us about treating functions with no parameters. Default function always return false.

This patch overrides this function for RISC-V


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

3 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/RISCV.cpp (+5)
  • (added) clang/test/CodeGen/RISCV/debug-info-emit-func-decl.c (+27)
  • (added) clang/test/CodeGen/RISCV/debug-info-emit-func-decl.cpp (+27)
diff --git a/clang/lib/CodeGen/Targets/RISCV.cpp b/clang/lib/CodeGen/Targets/RISCV.cpp
index e3232b61a693c..6c2ce5c96ce4a 100644
--- a/clang/lib/CodeGen/Targets/RISCV.cpp
+++ b/clang/lib/CodeGen/Targets/RISCV.cpp
@@ -842,6 +842,11 @@ class RISCVTargetCodeGenInfo : public TargetCodeGenInfo {
 
     Fn->addFnAttr("interrupt", Kind);
   }
+
+  bool isNoProtoCallVariadic(const CallArgList &,
+                             const FunctionNoProtoType *) const override {
+    return true;
+  }
 };
 } // namespace
 
diff --git a/clang/test/CodeGen/RISCV/debug-info-emit-func-decl.c b/clang/test/CodeGen/RISCV/debug-info-emit-func-decl.c
new file mode 100644
index 0000000000000..e580cb2702047
--- /dev/null
+++ b/clang/test/CodeGen/RISCV/debug-info-emit-func-decl.c
@@ -0,0 +1,27 @@
+// REQUIRES: riscv-registered-target
+// RUN: %clang_cc1 -triple riscv64 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=gdb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV64
+// RUN: %clang_cc1 -triple riscv64 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=lldb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV64
+// RUN: %clang_cc1 -triple riscv64 -debug-info-kind=limited -dwarf-version=5 -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV64
+
+// RUN: %clang_cc1 -triple riscv32 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=gdb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV32
+// RUN: %clang_cc1 -triple riscv32 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=lldb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV32
+// RUN: %clang_cc1 -triple riscv32 -debug-info-kind=limited -dwarf-version=5 -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV32
+
+// CHECK-RV64: declare !dbg ![[FOO:[0-9]+]] void @foo(...) local_unnamed_addr #[[ATTR0:[0-9]+]]
+// CHECK-RV32: declare !dbg ![[FOO:[0-9]+]] void @foo(...) local_unnamed_addr #[[ATTR0:[0-9]+]]
+void foo();
+
+// CHECK-RV64: declare !dbg ![[BAR:[0-9]+]] void @bar() local_unnamed_addr #[[ATTR0:[0-9]+]]
+// CHECK-RV32: declare !dbg ![[BAR:[0-9]+]] void @bar() local_unnamed_addr #[[ATTR0:[0-9]+]]
+void bar(void);
+
+// CHECK-RV64: declare !dbg ![[BAZ:[0-9]+]] void @baz(i32 noundef signext, ...) local_unnamed_addr #[[ATTR0:[0-9]+]]
+// CHECK-RV32: declare !dbg ![[BAZ:[0-9]+]] void @baz(i32 noundef, ...) local_unnamed_addr #[[ATTR0:[0-9]+]]
+void baz(int a, ...);
+
+int main() {
+  foo();
+  bar();
+  baz(1);
+  return 0;
+}
diff --git a/clang/test/CodeGen/RISCV/debug-info-emit-func-decl.cpp b/clang/test/CodeGen/RISCV/debug-info-emit-func-decl.cpp
new file mode 100644
index 0000000000000..48f5540b4be78
--- /dev/null
+++ b/clang/test/CodeGen/RISCV/debug-info-emit-func-decl.cpp
@@ -0,0 +1,27 @@
+// REQUIRES: riscv-registered-target
+// RUN: %clang_cc1 -triple riscv64 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=gdb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV64
+// RUN: %clang_cc1 -triple riscv64 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=lldb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV64
+// RUN: %clang_cc1 -triple riscv64 -debug-info-kind=limited -dwarf-version=5 -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV64
+
+// RUN: %clang_cc1 -triple riscv32 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=gdb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV32
+// RUN: %clang_cc1 -triple riscv32 -debug-info-kind=limited -dwarf-version=4 -debugger-tuning=lldb -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV32
+// RUN: %clang_cc1 -triple riscv32 -debug-info-kind=limited -dwarf-version=5 -O2 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-RV32
+
+// CHECK-RV64: declare !dbg ![[FOO:[0-9]+]] void @_Z3foov() local_unnamed_addr #[[ATTR0:[0-9]+]]
+// CHECK-RV32: declare !dbg ![[FOO:[0-9]+]] void @_Z3foov() local_unnamed_addr #[[ATTR0:[0-9]+]]
+void foo();
+
+// CHECK-RV64: declare !dbg ![[BAR:[0-9]+]] void @_Z3barv() local_unnamed_addr #[[ATTR0:[0-9]+]]
+// CHECK-RV32: declare !dbg ![[BAR:[0-9]+]] void @_Z3barv() local_unnamed_addr #[[ATTR0:[0-9]+]]
+void bar(void);
+
+// CHECK-RV64: declare !dbg ![[BAZ:[0-9]+]] void @_Z3baziz(i32 noundef signext, ...) local_unnamed_addr #[[ATTR0:[0-9]+]]
+// CHECK-RV32: declare !dbg ![[BAZ:[0-9]+]] void @_Z3baziz(i32 noundef, ...) local_unnamed_addr #[[ATTR0:[0-9]+]]
+void baz(int a, ...);
+
+int main() {
+  foo();
+  bar();
+  baz(1);
+  return 0;
+}

@sga-sc
Copy link
Contributor Author

sga-sc commented Jul 28, 2025

@kito-cheng @lenary @topperc guys, could you take a look, please?

@lenary
Copy link
Member

lenary commented Jul 28, 2025

I'm not sure a no-prototype call is the same as a variadic call on RISC-V, but I'm not sure. Note that the rules for passing variadic arguments are slightly different to passing non-variadic arguments:

In the base integer calling convention, variadic arguments are passed in the same manner as named arguments, with one exception. Variadic arguments with 2×XLEN-bit alignment and size at most 2×XLEN bits are passed in an aligned register pair (i.e., the first register in the pair is even-numbered), or on the stack by value if none is available. After a variadic argument has been passed on the stack, all future arguments will also be passed on the stack (i.e. the last argument register may be left unused due to the aligned register pair rule).

I'm not sure the impact treating no-prototype functions as variadic in debug info would have, presumably we might end up looking in the wrong registers for argument values?

@efriedma-quic
Copy link
Collaborator

So according to the standard, unprototyped function calls are not variadic: if you try to call a variadic function using an unprototyped function, the behavior is formally undefined. On some targets, for some function signatures, it might appear to work.

On x86 specifically, we have a minor compatibility hack: if you call an unprototyped function with certain arguments, we emit it as if it was variadic, so the backend knows to set "al". This makes calling a variadic function through an unprototyped signature work in some cases. But this is just a best-effort thing, to try to make broken code appear to work. And it isn't portable.

CGDebugInfo should probably be using something like dyn_cast<Function>(CallOrInvoke->getCalledOperand(), not getCalledFunction(), to avoid running into this.

@sga-sc sga-sc force-pushed the users/sga-sc/fix_clang_dwarf_info branch from 4822a7b to 765d836 Compare July 29, 2025 09:47
@sga-sc sga-sc force-pushed the users/sga-sc/fix_clang_dwarf_info branch from 765d836 to 238b68d Compare July 29, 2025 10:25
Copy link

github-actions bot commented Jul 29, 2025

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

@sga-sc sga-sc force-pushed the users/sga-sc/fix_clang_dwarf_info branch 2 times, most recently from 3807705 to 5d63792 Compare July 29, 2025 10:41
@sga-sc
Copy link
Contributor Author

sga-sc commented Jul 29, 2025

@efriedma-quic Addressed

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.

The code change looks fine.

Are the tests just checking that there's !dbg metadata attached to the function declarations? Do you really need 12 RUN lines to check that?

@sga-sc
Copy link
Contributor Author

sga-sc commented Jul 30, 2025

Declaration with !dbg is emitted in case when we want to compile with optimizations and in some other cases:

llvm::DINode::DIFlags CGDebugInfo::getCallSiteRelatedAttrs() const {
// Call site-related attributes are only useful in optimized programs, and
// when there's a possibility of debugging backtraces.
if (CGM.getCodeGenOpts().OptimizationLevel == 0 ||
DebugKind == llvm::codegenoptions::NoDebugInfo ||
DebugKind == llvm::codegenoptions::LocTrackingOnly)
return llvm::DINode::FlagZero;
// Call site-related attributes are available in DWARF v5. Some debuggers,
// while not fully DWARF v5-compliant, may accept these attributes as if they
// were part of DWARF v4.
bool SupportsDWARFv4Ext =
CGM.getCodeGenOpts().DwarfVersion == 4 &&
(CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::LLDB ||
CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::GDB);
if (!SupportsDWARFv4Ext && CGM.getCodeGenOpts().DwarfVersion < 5)
return llvm::DINode::FlagZero;
return llvm::DINode::FlagAllCallsDescribed;
}
We call getCallSiteRelatedAttrs() function inside EmitFuncDeclForCallSite()
getCallSiteRelatedAttrs() == llvm::DINode::FlagZero)

So in this test I sort through these options and check that necessary declarations exist. Also there is a difference between the declaration of foo for C and C++ and little a difference between RISCV64 and RISCV32. In that way we get 12 RUN commands.

@efriedma-quic
Copy link
Collaborator

Having checks for different optimization levels and DWARF versions is probably fine... but a lot of those checks overlap with clang/test/CodeGenCXX/dbg-info-all-calls-described.cpp . Explicitly checking RV32 vs RV64 seems unproductive; this code isn't target-specific.

@sga-sc sga-sc force-pushed the users/sga-sc/fix_clang_dwarf_info branch from eb24c47 to 1b80fdd Compare July 31, 2025 11:15
@sga-sc
Copy link
Contributor Author

sga-sc commented Jul 31, 2025

Ok, I see such test already exists. I added similar test for C

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

@@ -4805,7 +4805,8 @@ void CGDebugInfo::EmitFuncDeclForCallSite(llvm::CallBase *CallOrInvoke,
const FunctionDecl *CalleeDecl) {
if (!CallOrInvoke)
return;
auto *Func = CallOrInvoke->getCalledFunction();
auto *Func =
dyn_cast_or_null<llvm::Function>(CallOrInvoke->getCalledOperand());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, one minor adjustment: this can just be dyn_cast. CallOrInvoke->getCalledOperand() will never return null for well-formed IR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed. Could you merge it, please?

@sga-sc sga-sc force-pushed the users/sga-sc/fix_clang_dwarf_info branch from 1b80fdd to 50d14a4 Compare July 31, 2025 22:21
@sga-sc sga-sc changed the title [clang][RISCV] Fix clang dwarf info generation for unprtototyped function [clang] Fix clang dwarf info generation for unprtototyped function Jul 31, 2025
@sga-sc sga-sc changed the title [clang] Fix clang dwarf info generation for unprtototyped function [clang] Fix clang debug info generation for unprtototyped function Jul 31, 2025
@asi-sc asi-sc merged commit bcbbb2c into llvm:main Aug 1, 2025
9 checks passed
Michael137 added a commit that referenced this pull request Aug 1, 2025
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Aug 1, 2025
Michael137 added a commit that referenced this pull request Aug 2, 2025
These have been un-XFAILed in
`ebe6eba62580592af7065a36b22d929c15291e9a`, but require following Clang
fix: #150022. So skip older
compilers.
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Aug 2, 2025
…rsions

These have been un-XFAILed in
`ebe6eba62580592af7065a36b22d929c15291e9a`, but require following Clang
fix: llvm/llvm-project#150022. So skip older
compilers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:RISC-V clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category debuginfo
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants