Skip to content

ast_canopy: skip Itanium mangling for dependent-signature functions#324

Open
isVoid wants to merge 2 commits intoNVIDIA:mainfrom
isVoid:fea/ast-canopy-function-mangle-guard
Open

ast_canopy: skip Itanium mangling for dependent-signature functions#324
isVoid wants to merge 2 commits intoNVIDIA:mainfrom
isVoid:fea/ast-canopy-function-mangle-guard

Conversation

@isVoid
Copy link
Copy Markdown
Collaborator

@isVoid isVoid commented Apr 21, 2026

Summary

  • clang::ItaniumMangleContext::mangleName segfaults when handed a FunctionDecl whose parameter or return types are still template-dependent. Because the Function constructor unconditionally mangled every FunctionDecl it visited, any header containing a CRTP base class, a function template with a dependent return type, or Eigen-style expression templates would segfault the whole Python process.
  • Introduces has_dependent_signature(FD), which returns true when the return type is dependent, any parameter type is dependent, or the parent (for methods) is a dependent context. When it returns true, mangling is skipped and the qualified name is used as a best-effort fallback.
  • Also plugs a leak: wraps the ItaniumMangleContext handle in std::unique_ptr (create() allocates with raw new but there was no matching delete).

Test plan

  • New sample ast_canopy/tests/data/sample_crtp_dependent.cu:
    • CRTP base with a method whose return type depends on the derived type.
    • Function template whose return type depends on T::value_type.
    • A plain device function (vec3_dot) that must still be mangled normally.
  • New tests in ast_canopy/tests/test_crtp_dependent_mangle.py:
    • Parsing the CRTP header does not segfault.
    • Both CRTPBase and Vec3 class templates are captured.
    • extract_value appears as a function template (dependent-signature path works).
    • vec3_dot still has a valid _Z-prefixed Itanium mangled name (regression guard).
  • Rebuilt libastcanopy.so with only this branch applied and ran the test to confirm the fix is sufficient in isolation.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed a crash when parsing C++/CUDA code with template-dependent function signatures (e.g., CRTP patterns) and improved handling so dependent signatures no longer break parsing; non-dependent functions retain normal mangled names.
  • Tests

    • Added tests and sample code exercising dependent-signature parsing and mangling to prevent regressions.

clang::ItaniumMangleContext::mangleName segfaults when handed a
FunctionDecl whose parameter or return types are still
template-dependent. Because the Function constructor unconditionally
mangled every FunctionDecl it visited, any header containing a CRTP
base class, a function template with a dependent return type, or
Eigen-style expression templates would segfault the whole Python
process.

Introduce has_dependent_signature(FD), which returns true when:
  - the return type is dependent, or
  - any parameter type is dependent, or
  - the parent (for methods) is a dependent context.

When it returns true, skip mangling and fall back to the qualified
name. Otherwise proceed as before.

Also wrap the ItaniumMangleContext handle in std::unique_ptr to plug a
leak -- ItaniumMangleContext::create allocates with raw new but there
was no delete.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

Added a helper to detect template-dependent function signatures and updated Function construction to avoid Itanium mangling for dependent signatures (falling back to the qualified name). Switched the Itanium mangle context to a std::unique_ptr. Added test data and tests exercising CRTP- and nested-dependent return types.

Changes

Cohort / File(s) Summary
Core Function Mangling Logic
ast_canopy/cpp/src/function.cpp
Added has_dependent_signature() to detect dependent return/parameter types and dependent parent contexts. Function::Function now skips Itanium mangling for dependent signatures and uses the qualified name as a fallback. Replaced raw Itanium mangle context pointer with std::unique_ptr.
Test Data and Test Suite
ast_canopy/tests/data/sample_crtp_dependent.cu, ast_canopy/tests/test_crtp_dependent_mangle.py
Added a CUDA test source with CRTP and nested-dependent return-type cases and a pytest module that ensures parsing/mangling of dependent signatures does not crash and that non-dependent functions still produce Itanium mangled names starting with _Z.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 In templated fields I bound and hop,
Detecting dependents before they drop.
No mangled snares, just qual names bloom,
Tests cheer softly in the parsing room. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: skipping Itanium mangling for functions with dependent signatures, which directly addresses the core fix in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ast_canopy/cpp/src/function.cpp`:
- Around line 79-80: The current fallback sets mangled_name = qual_name when
has_dependent_signature(FD) is true, which loses parameter/qualifier info and
conflates overloads; instead, construct an overload-stable fallback by appending
a printed signature (parameter type list and any cv/ref qualifiers) to qual_name
or by tagging the name as non-unique (e.g., qual_name + "<dependent-signature:
...>") so callers won't treat it as a unique mangled symbol. Locate the branch
where has_dependent_signature(FD) is checked and build the signature from FD’s
parameter types and function qualifiers (use the FunctionDecl/FunctionProtoType
accessors to print each parameter type and cv/ref qualifiers), then set
mangled_name to qual_name + separator + that printed signature (or qual_name +
explicit dependent tag) so overloads remain distinguishable.

In `@ast_canopy/tests/test_crtp_dependent_mangle.py`:
- Around line 49-54: The test currently only checks that "extract_value"
appears; add an assertion that verifies the dependent function template's
mangled name uses the fallback instead of an empty/Itanium mangled form: after
obtaining decls.function_templates and selecting the template whose
ft.function.name == "extract_value", assert that ft.function.mangled_name is
non-empty and not starting with "_Z" (or assert equality to the qualified-name
fallback via ft.function.qualified_name) to ensure Function::Function assigned
the dependent-signature fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 556a84e8-ef3e-4292-8e04-5bb82e3f7fd4

📥 Commits

Reviewing files that changed from the base of the PR and between f29a975 and 493d6d1.

📒 Files selected for processing (3)
  • ast_canopy/cpp/src/function.cpp
  • ast_canopy/tests/data/sample_crtp_dependent.cu
  • ast_canopy/tests/test_crtp_dependent_mangle.py

Comment on lines +79 to +80
if (has_dependent_signature(FD)) {
mangled_name = qual_name; // best-effort fallback
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use an overload-stable fallback instead of qual_name alone.

qual_name drops the parameter list, so dependent overloads in the same scope will now get the same mangled_name. That makes overloads indistinguishable downstream even though the crash is avoided. Please build the fallback from a printed signature (qual_name + parameter types + cv/ref qualifiers) or mark it separately so callers do not treat it as a unique mangled symbol.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ast_canopy/cpp/src/function.cpp` around lines 79 - 80, The current fallback
sets mangled_name = qual_name when has_dependent_signature(FD) is true, which
loses parameter/qualifier info and conflates overloads; instead, construct an
overload-stable fallback by appending a printed signature (parameter type list
and any cv/ref qualifiers) to qual_name or by tagging the name as non-unique
(e.g., qual_name + "<dependent-signature: ...>") so callers won't treat it as a
unique mangled symbol. Locate the branch where has_dependent_signature(FD) is
checked and build the signature from FD’s parameter types and function
qualifiers (use the FunctionDecl/FunctionProtoType accessors to print each
parameter type and cv/ref qualifiers), then set mangled_name to qual_name +
separator + that printed signature (or qual_name + explicit dependent tag) so
overloads remain distinguishable.

Comment on lines +49 to +54
def test_dependent_function_template_captured(source_path):
"""extract_value has a dependent return type. It should appear as a
function template without having segfaulted the mangler."""
decls = parse_declarations_from_source(source_path, [source_path], "sm_80")
ft_names = [ft.function.name for ft in decls.function_templates]
assert "extract_value" in ft_names, ft_names
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert the dependent-signature fallback explicitly.

This only proves that extract_value was collected. It does not verify the new behavior at Function::Function, so a regression in the fallback assignment could still pass. Please also assert that the dependent function template's mangled_name is the qualified-name fallback (or at least non-empty and not _Z-prefixed).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ast_canopy/tests/test_crtp_dependent_mangle.py` around lines 49 - 54, The
test currently only checks that "extract_value" appears; add an assertion that
verifies the dependent function template's mangled name uses the fallback
instead of an empty/Itanium mangled form: after obtaining
decls.function_templates and selecting the template whose ft.function.name ==
"extract_value", assert that ft.function.mangled_name is non-empty and not
starting with "_Z" (or assert equality to the qualified-name fallback via
ft.function.qualified_name) to ensure Function::Function assigned the
dependent-signature fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ast_canopy/tests/data/sample_crtp_dependent.cu`:
- Around line 23-24: CRTPBase's const member add() calls the non-const helper
derived(), breaking const-correctness; add a const overload of derived() (i.e.,
a __device__ const Derived& derived() const that static_casts *this to const
Derived&) so const member functions can call it without casting, leaving the
existing non-const __device__ Derived& derived() for mutable use.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d9985115-e372-4178-a329-3c18baba4df1

📥 Commits

Reviewing files that changed from the base of the PR and between 493d6d1 and 8fd7ea5.

📒 Files selected for processing (1)
  • ast_canopy/tests/data/sample_crtp_dependent.cu

Comment on lines +23 to +24
__device__ Derived &derived() { return static_cast<Derived &>(*this); }
__device__ Derived add(const Derived &other) const { return derived(); }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In C++, can a const member function call a non-const member function on the same object? Please cite the const-correctness rule and a minimal example.

💡 Result:

No, a const member function cannot call a non-const member function on the same object. The const-correctness rule is specified in the C++ standard via [class.mfct.non-static]: In the body of a const member function, the implicit object parameter (this) has type cv-qualified (const-qualified), so only other const member functions may be called normally on *this. A non-const member function requires a non-const this pointer, causing a compilation error. Minimal example: struct Example { void nonConst {} // non-const member function void constFunc const { // const member function nonConst; // error: passing 'const Example' as 'this' argument discards qualifiers } }; This fails to compile, as confirmed across sources including cppreference.com and Stack Overflow discussions.

Citations:


🏁 Script executed:

# Check if the file exists and read the relevant lines
if [ -f "ast_canopy/tests/data/sample_crtp_dependent.cu" ]; then
  echo "=== File exists, showing lines 23-24 ==="
  sed -n '20,28p' ast_canopy/tests/data/sample_crtp_dependent.cu
else
  echo "File not found at expected path"
fi

Repository: NVIDIA/numbast

Length of output: 529


🏁 Script executed:

# Check if the Python test file exists and references this data file
if [ -f "ast_canopy/tests/test_crtp_dependent_mangle.py" ]; then
  echo "=== Python test file found, searching for reference to the data file ==="
  grep -n "sample_crtp_dependent" ast_canopy/tests/test_crtp_dependent_mangle.py
else
  echo "Python test file not found"
fi

Repository: NVIDIA/numbast

Length of output: 199


Add const overload for derived() to fix const-correctness violation

CRTPBase<Derived>::add is const (line 24) but calls non-const derived() (line 23). Per C++ const-correctness rules, a const member function cannot call a non-const member function on *this without explicit casting. This code cannot compile as written and breaks the test fixture.

Suggested fix
 template <typename Derived> struct CRTPBase {
   __device__ Derived &derived() { return static_cast<Derived &>(*this); }
+  __device__ const Derived &derived() const {
+    return static_cast<const Derived &>(*this);
+  }
   __device__ Derived add(const Derived &other) const { return derived(); }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
__device__ Derived &derived() { return static_cast<Derived &>(*this); }
__device__ Derived add(const Derived &other) const { return derived(); }
template <typename Derived> struct CRTPBase {
__device__ Derived &derived() { return static_cast<Derived &>(*this); }
__device__ const Derived &derived() const {
return static_cast<const Derived &>(*this);
}
__device__ Derived add(const Derived &other) const { return derived(); }
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ast_canopy/tests/data/sample_crtp_dependent.cu` around lines 23 - 24,
CRTPBase's const member add() calls the non-const helper derived(), breaking
const-correctness; add a const overload of derived() (i.e., a __device__ const
Derived& derived() const that static_casts *this to const Derived&) so const
member functions can call it without casting, leaving the existing non-const
__device__ Derived& derived() for mutable use.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant