Skip to content

Support differentiation of lambdas with active captures - #1859

Open
Fermats-Last-Theorem wants to merge 1 commit into
vgvassilev:masterfrom
Fermats-Last-Theorem:lambda-capture
Open

Support differentiation of lambdas with active captures#1859
Fermats-Last-Theorem wants to merge 1 commit into
vgvassilev:masterfrom
Fermats-Last-Theorem:lambda-capture

Conversation

@Fermats-Last-Theorem

@Fermats-Last-Theorem Fermats-Last-Theorem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Enables differentiation of lambdas with captures in clad. For each captured variable, the derivative lambda has an extra parameter pair. Captured Value and derivative for forward mode. Captured Value and a pointer to its adjoint for reverse mode.

Fixes #1053.
Fixes #1054.
Fixes #1769.

@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 23. Check the log or trigger a new build to see more.

llvm::SmallVectorImpl<clang::Expr*>& clonedArgs,
llvm::SmallVectorImpl<clang::Expr*>& derivedArgs);

clang::QualType GetLambdaPushforwardType(const clang::LambdaExpr* LE);

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::QualType" is directly included [misc-include-cleaner]

include/clad/Differentiator/BaseForwardModeVisitor.h:17:

- #include <stack>
+ #include <clang/AST/Type.h>
+ #include <stack>

if (LE->capture_size() == 0)
return baseTy;
clang::ASTContext& C = m_Sema.getASTContext();
const auto* FPT = baseTy->castAs<clang::FunctionProtoType>();

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::FunctionProtoType" is directly included [misc-include-cleaner]

      const auto* FPT = baseTy->castAs<clang::FunctionProtoType>();
                                              ^

if (!Capture.capturesVariable())
continue;
const auto* capVD =
llvm::dyn_cast<clang::VarDecl>(Capture.getCapturedVar());

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 "llvm::dyn_cast" is directly included [misc-include-cleaner]

include/clad/Differentiator/ReverseModeVisitor.h:34:

- #include <memory>
+ #include <llvm/Support/Casting.h>
+ #include <memory>

/// Maps a (lambda, by value captured variable) to its creation snapshot
llvm::DenseMap<std::pair<const clang::LambdaExpr*, const clang::VarDecl*>,
clang::VarDecl*>
m_LambdaCaptureSnapshots;

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: member variable 'm_LambdaCaptureSnapshots' has protected visibility [cppcoreguidelines-non-private-member-variables-in-classes]

        m_LambdaCaptureSnapshots;
        ^

#if CLANG_VERSION_MAJOR > 16
clang::Expr*
BaseForwardModeVisitor::buildPushforwardLambda(const LambdaExpr* LE) {
LambdaIntroducer Intro;

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::LambdaIntroducer" is directly included [misc-include-cleaner]

  LambdaIntroducer Intro;
  ^

clang::Expr*
BaseForwardModeVisitor::buildPushforwardLambda(const LambdaExpr* LE) {
LambdaIntroducer Intro;
Intro.Default = LCD_None;

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::LCD_None" is directly included [misc-include-cleaner]

lib/Differentiator/BaseForwardModeVisitor.cpp:47:

- #include <string>
+ #include <clang/Basic/Lambda.h>
+ #include <string>

Intro.Default = LCD_None;
Intro.Range.setBegin(noLoc);
Intro.Range.setEnd(noLoc);
AttributeFactory AttrFactory;

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::AttributeFactory" is directly included [misc-include-cleaner]

lib/Differentiator/BaseForwardModeVisitor.cpp:47:

- #include <string>
+ #include <clang/Sema/ParsedAttr.h>
+ #include <string>

Intro.Range.setBegin(noLoc);
Intro.Range.setEnd(noLoc);
AttributeFactory AttrFactory;
const DeclSpec DS(AttrFactory);

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::DeclSpec" is directly included [misc-include-cleaner]

  const DeclSpec DS(AttrFactory);
        ^

Intro.Range.setEnd(noLoc);
AttributeFactory AttrFactory;
const DeclSpec DS(AttrFactory);
Declarator D(DS, clang::ParsedAttributesView::none(),

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::Declarator" is directly included [misc-include-cleaner]

  Declarator D(DS, clang::ParsedAttributesView::none(),
  ^

Intro.Range.setEnd(noLoc);
AttributeFactory AttrFactory;
const DeclSpec DS(AttrFactory);
Declarator D(DS, clang::ParsedAttributesView::none(),

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::ParsedAttributesView" is directly included [misc-include-cleaner]

  Declarator D(DS, clang::ParsedAttributesView::none(),
                          ^

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.88581% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/Differentiator/BaseForwardModeVisitor.cpp 98.38% 3 Missing ⚠️
lib/Differentiator/ReverseModeVisitor.cpp 96.05% 3 Missing ⚠️
lib/Differentiator/VisitorBase.cpp 77.77% 2 Missing ⚠️
lib/Differentiator/DiffPlanner.cpp 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@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 12. Check the log or trigger a new build to see more.

AttributeFactory AttrFactory;
const DeclSpec DS(AttrFactory);
Declarator D(DS, clang::ParsedAttributesView::none(),
clang::DeclaratorContext::LambdaExpr);

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::DeclaratorContext" is directly included [misc-include-cleaner]

               clang::DeclaratorContext::LambdaExpr);
                      ^


QualType dFnType = GetLambdaPushforwardType(LE);

auto* DC = const_cast<DeclContext*>(LE->getCallOperator()->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: redundant explicit casting to the same type 'DeclContext *' as the sub-expression, remove this casting [readability-redundant-casting]

Suggested change
auto* DC = const_cast<DeclContext*>(LE->getCallOperator()->getDeclContext());
auto* DC = LE->getCallOperator()->getDeclContext();
Additional context

llvm/include/clang/AST/DeclBase.h:450: source type originates from the invocation of this method

  DeclContext *getDeclContext() {
               ^

m_Sema.ActOnLambdaExpressionAfterIntroducer(Intro, getCurrentScope());
beginScope(Scope::FunctionPrototypeScope | Scope::FunctionDeclarationScope |
Scope::DeclScope);
llvm::SmallVector<DeclaratorChunk::ParamInfo> ParamInfoLambda;

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::DeclaratorChunk" is directly included [misc-include-cleaner]

  llvm::SmallVector<DeclaratorChunk::ParamInfo> ParamInfoLambda;
                    ^

/*EllipsisLoc=*/SourceLocation(), /*RParenLoc=*/paramListLoc,
/*RefQualifierIsLValueRef=*/true,
/*RefQualifierLoc=*/SourceLocation(),
/*MutableLoc=*/SourceLocation(), /*ESpecType=*/EST_None,

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::EST_None" is directly included [misc-include-cleaner]

lib/Differentiator/BaseForwardModeVisitor.cpp:47:

- #include <string>
+ #include <clang/Basic/ExceptionSpecificationType.h>
+ #include <string>

const LambdaExpr* calledLambda = nullptr;
const VarDecl* lambdaVD = nullptr;
if (const auto* DRE = dyn_cast<DeclRefExpr>(origCallee))
if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))

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: an assignment within an 'if' condition is bug-prone [bugprone-assignment-in-if-condition]

        if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))
                      ^
Additional context

lib/Differentiator/BaseForwardModeVisitor.cpp:1205: if it should be an assignment, move it out of the 'if' condition

        if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))
                      ^

lib/Differentiator/BaseForwardModeVisitor.cpp:1205: if it is meant to be an equality check, change '=' to '=='

        if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))
                      ^

continue;
QualType valTy = utils::getNonConstType(
capVD->getType().getNonReferenceType(), m_Sema);
if (Capture.getCaptureKind() == LCK_ByCopy) {

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::LCK_ByCopy" is directly included [misc-include-cleaner]

            if (Capture.getCaptureKind() == LCK_ByCopy) {
                                            ^

if (m_Sema.LookupName(R, getCurrentScope()) && R.isSingleResult())
if (auto* found = dyn_cast<VarDecl>(R.getFoundDecl()))
return found;
return const_cast<VarDecl*>(capVD);

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]

    return const_cast<VarDecl*>(capVD);
           ^

Comment thread lib/Differentiator/VisitorBase.cpp Outdated
#if CLANG_VERSION_MAJOR > 16
clang::Expr* VisitorBase::buildPrimalLambda(const clang::LambdaExpr* LE) {
LambdaIntroducer Intro;
Intro.Default = LCD_None;

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::LCD_None" is directly included [misc-include-cleaner]

lib/Differentiator/VisitorBase.cpp:47:

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

Comment thread lib/Differentiator/VisitorBase.cpp Outdated
Intro.Default = LCD_None;
Intro.Range.setBegin(noLoc);
Intro.Range.setEnd(noLoc);
AttributeFactory AttrFactory;

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::AttributeFactory" is directly included [misc-include-cleaner]

lib/Differentiator/VisitorBase.cpp:47:

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

Comment thread lib/Differentiator/VisitorBase.cpp Outdated
Intro.Range.setEnd(noLoc);
AttributeFactory AttrFactory;
const DeclSpec DS(AttrFactory);
Declarator D(DS, clang::ParsedAttributesView::none(),

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::ParsedAttributesView" is directly included [misc-include-cleaner]

    Declarator D(DS, clang::ParsedAttributesView::none(),
                            ^

@Fermats-Last-Theorem
Fermats-Last-Theorem force-pushed the lambda-capture branch 2 times, most recently from ea2e2db to dd26b81 Compare July 11, 2026 05:53

@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

FPT->param_types().begin(), FPT->param_types().end());
for (const clang::LambdaCapture& Capture : LE->captures()) {
const auto* capVD =
llvm::cast<clang::VarDecl>(Capture.getCapturedVar());

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 "llvm::cast" is directly included [misc-include-cleaner]

include/clad/Differentiator/ReverseModeVisitor.h:34:

- #include <memory>
+ #include <llvm/Support/Casting.h>
+ #include <memory>

const LambdaExpr* calledLambda = nullptr;
const VarDecl* lambdaVD = nullptr;
if (const auto* DRE = dyn_cast<DeclRefExpr>(origCallee))
if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))

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: an assignment within an 'if' condition is bug-prone [bugprone-assignment-in-if-condition]

        if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))
                      ^
Additional context

lib/Differentiator/BaseForwardModeVisitor.cpp:1203: if it should be an assignment, move it out of the 'if' condition

        if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))
                      ^

lib/Differentiator/BaseForwardModeVisitor.cpp:1203: if it is meant to be an equality check, change '=' to '=='

        if ((lambdaVD = dyn_cast<VarDecl>(DRE->getDecl())))
                      ^

@Fermats-Last-Theorem

Fermats-Last-Theorem commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@vgvassilev This PR should now be ready for review. It now builds the primal closure using buildClonedLambda from #1875.
Additionally, the Kokkos reverse mode work (#783) needs active lambda captures before it can be implemented.

@Fermats-Last-Theorem
Fermats-Last-Theorem marked this pull request as ready for review July 11, 2026 07:14
@vgvassilev

Copy link
Copy Markdown
Owner

@vgvassilev This PR should now be ready for review. It now builds the primal closure using buildClonedLambda from #1875. Additionally, the Kokkos reverse mode work (#783) needs active lambda captures before it can be implemented.

I have some version of that with some overall design improvements. Let me land this first and we can adapt that to these changes.

@Fermats-Last-Theorem

Copy link
Copy Markdown
Contributor Author

I have some version of that with some overall design improvements. Let me land this first and we can adapt that to these changes.

Sounds good. The CI is finally green as well.

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

Labels

None yet

Projects

None yet

2 participants