Skip to content

Diagnose non-void functions in Jacobian mode - #2001

Open
Elvand-Lie wants to merge 2 commits into
vgvassilev:masterfrom
Elvand-Lie:fix/jacobian-void-return-scalar-fn
Open

Diagnose non-void functions in Jacobian mode#2001
Elvand-Lie wants to merge 2 commits into
vgvassilev:masterfrom
Elvand-Lie:fix/jacobian-void-return-scalar-fn

Conversation

@Elvand-Lie

@Elvand-Lie Elvand-Lie commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Description

Clad's Jacobian API generates a void derivative function and exposes outputs through pointer, reference, or array parameters.

Requesting clad::jacobian for a non-void primal function previously proceeded into derivative generation and emitted invalid C++, resulting in a cryptic downstream compiler error.

This PR adds a direct diagnostic in DerivativeBuilder::Derive that rejects non-void primal functions before invalid C++ is generated, explaining that Jacobian mode currently requires a void-returning primal function and that outputs must be provided through pointer, reference, or array parameters.

  • Current Contract: Strict void-returning primal requirement.
  • Coverage: Adds tests in test/Jacobian/NonVoidReturnDiagnostic.C covering scalar returns, output pointers with scalar returns (reproducing Jacobian Generates return in void Function #1306), and status-returning functions to document current behavior.
  • Follow-up Work: Selective status-return support, if desired by maintainers, involves separate return-control-flow and output-classification semantics and can be evaluated as a follow-up feature.

Related Issues

Fixes #1306

@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Elvand-Lie
Elvand-Lie force-pushed the fix/jacobian-void-return-scalar-fn branch from 81f3d63 to a6d8847 Compare August 20, 2026 17:17
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@Elvand-Lie
Elvand-Lie marked this pull request as draft August 20, 2026 17:37
@Elvand-Lie

Copy link
Copy Markdown
Contributor Author

Before I finalize the #1306 diagnostic, I would like to confirm the intended Jacobian return-value contract.

The current patch rejects every non-void primal. That handles differentiable scalar returns, for which the generated Jacobian ABI has no return-derivative output slot, but it also rejects status-returning C-style functions such as:

int status_output(double x, double* out) {
  *out = x * x;
  return 0;
}

I propose the following narrower rule:

  • keep the generated Jacobian function void-returning;
  • reject differentiable non-void returns with a clear diagnostic;
  • allow bool, integral, or enum status returns when Jacobian mode already recognizes a differentiable output parameter;
  • evaluate the status-return expression for side effects and lower it to a bare return, preserving early-return control flow.

This avoids inventing an ABI slot for scalar-return derivatives while preserving common status-code APIs.

Would you prefer this selective status-return policy, or should Jacobian primal functions be strictly void-returning?

@Elvand-Lie
Elvand-Lie force-pushed the fix/jacobian-void-return-scalar-fn branch from a6d8847 to 870f7ba Compare August 21, 2026 08:44
@Elvand-Lie

Copy link
Copy Markdown
Contributor Author

After reviewing the merged vector-forward Jacobian design and its void-returning derivative ABI, I am keeping this PR scoped to the current void-returning primal contract. Selective status-return support would require separate return-control-flow and output-classification semantics, so I will leave that as follow-up feature work unless maintainers prefer otherwise.

@github-actions github-actions Bot left a comment

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.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 15. Check the log or trigger a new build to see more.

return true;
return false;
}
static bool hasAttribute(const Decl* D, attr::Kind Kind) {

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.

warning: no header providing "clang::attr::Kind" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:52:

- #include <cstddef>
+ #include <clang/Basic/AttrKinds.h>
+ #include <cstddef>

ClonedFunction DerivativeBuilder::cloneFunction(const clang::FunctionDecl* FD,
clad::VisitorBase& VB,
clang::DeclContext* DC,
clang::SourceLocation& noLoc,

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.

warning: no header providing "clang::SourceLocation" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:52:

- #include <cstddef>
+ #include <clang/Basic/SourceLocation.h>
+ #include <cstddef>

clad::VisitorBase& VB,
clang::DeclContext* DC,
clang::SourceLocation& noLoc,
clang::DeclarationNameInfo name,

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.

warning: no header providing "clang::DeclarationNameInfo" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:52:

- #include <cstddef>
+ #include <clang/AST/DeclarationName.h>
+ #include <cstddef>

unsigned NamespaceCount = 0;
TypeSourceInfo* TSI = m_Context.getTrivialTypeSourceInfo(functionType);
if (isa<CXXMethodDecl>(FD)) {
CXXRecordDecl* CXXRD = cast<CXXRecordDecl>(DC);

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.

warning: use auto when initializing with a template cast to avoid duplicating the type name [modernize-use-auto]

Suggested change
CXXRecordDecl* CXXRD = cast<CXXRecordDecl>(DC);
auto* CXXRD = cast<CXXRecordDecl>(DC);

// even if their original function had different access specifier.
returnedFD->setAccess(AS_public);
} else {
assert(isa<FunctionDecl>(FD) && "Unexpected!");

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.

warning: no header providing "assert" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:52:

- #include <cstddef>
+ #include <cassert>
+ #include <cstddef>

assert(isa<FunctionDecl>(FD) && "Unexpected!");
NamespaceCount = VB.RebuildEnclosingNamespaces(DC);

auto TrailingRequiresClause =

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.

warning: 'auto TrailingRequiresClause' can be declared as 'auto *TrailingRequiresClause' [llvm-qualified-auto]

Suggested change
auto TrailingRequiresClause =
auto *TrailingRequiresClause =

OverloadExpr* ovl = find.Expression;

if (isa<UnresolvedLookupExpr>(ovl)) {
ExprResult result;

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.

warning: no header providing "clang::ExprResult" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:52:

- #include <cstddef>
+ #include <clang/Sema/Ownership.h>
+ #include <cstddef>

SS.Extend(m_Context, NSD, noLoc, noLoc);
LookupResult DerivativeBuilder::LookupCustomDerivativeOrNumericalDiff(
const std::string& Name, const clang::DeclContext* originalFnDC,
CXXScopeSpec& SS, bool forCustomDerv /*=true*/,

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.

warning: no header providing "clang::CXXScopeSpec" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:52:

- #include <cstddef>
+ #include <clang/Sema/DeclSpec.h>
+ #include <cstddef>

bool namespaceShouldExist /*=true*/) {

IdentifierInfo* II = &m_Context.Idents.get(Name);
DeclarationName name(II);

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.

warning: no header providing "clang::DeclarationName" is directly included [misc-include-cleaner]

  DeclarationName name(II);
  ^

const Expr* Callee = CE->getCallee()->IgnoreParenCasts();
if (const auto* DRE = dyn_cast<DeclRefExpr>(Callee))
originalFnDC =
const_cast<DeclContext*>(DRE->getFoundDecl()->getDeclContext());

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.

warning: do not use const_cast to remove const qualifier [cppcoreguidelines-pro-type-const-cast]

            const_cast<DeclContext*>(DRE->getFoundDecl()->getDeclContext());
            ^

@Elvand-Lie
Elvand-Lie force-pushed the fix/jacobian-void-return-scalar-fn branch from 870f7ba to 75e544e Compare August 21, 2026 09:11
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@Elvand-Lie
Elvand-Lie force-pushed the fix/jacobian-void-return-scalar-fn branch from 75e544e to 4520092 Compare August 21, 2026 09:18
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

Clad's Jacobian API generates a void derivative function and exposes
outputs through pointer, reference, or array parameters.

Requesting clad::jacobian for a non-void primal previously proceeded into
derivative generation and emitted invalid C++, resulting in a cryptic
downstream compiler error.

Reject unsupported non-void primal functions in DerivativeBuilder::Derive
with a direct diagnostic explaining that Jacobian mode currently requires a
void-returning primal and how outputs must be provided.

Add regression coverage for scalar returns, output pointers with scalar
returns (reproducing Issue vgvassilev#1306), and status-returning functions to
document the strict void-returning contract.

Signed-off-by: Tempris Admin <elvandlie@gmail.com>

Fixes vgvassilev#1306
@Elvand-Lie
Elvand-Lie force-pushed the fix/jacobian-void-return-scalar-fn branch from 4520092 to 23412ba Compare August 21, 2026 10:01
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@Elvand-Lie
Elvand-Lie marked this pull request as ready for review August 22, 2026 11:28
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.

Jacobian Generates return in void Function

1 participant