Skip to content

Commit adb8208

Browse files
committed
Add -fclad-porting-hints to name missing custom derivatives.
When clad has no custom derivative for a function whose definition it can see, it silently falls back to differentiating that definition, descending into the library's internals (reference counting, allocation, I/O). At a library boundary that is rarely intended and often yields ill-formed or incorrect derivatives. The hard part of teaching clad a new library is discovering which functions need a custom derivative -- or a non-differentiable marker -- and with which signature. -fclad-porting-hints surfaces every such boundary. For each function defined outside the main source file that clad differentiates by cloning, it emits a remark naming the expected custom-derivative signature and, for methods and constructors, the non-differentiable marker to declare instead. The hint is a per-request flag propagated to sub-requests, so the boundary function -- reached as a sub-request during a gradient -- is covered. It changes no generated code and is off by default. The custom-derivatives guide documents the workflow, and the flag is listed in -plugin-arg-clad -help.
1 parent 9f2e636 commit adb8208

10 files changed

Lines changed: 167 additions & 1 deletion

File tree

docs/userDocs/source/user/CustomDerivatives.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,55 @@ Note that the constructor pullback does not need anything such as
435435
:code:`clad::ConstructorPushforwardTag<::Coordinates>`. It is because
436436
the constructor pullback takes :code:`d_coordinates` as an argument, which can be
437437
used to identify the class for which the constructor pullback is defined.
438+
439+
Porting hints: discovering which custom derivatives to write
440+
============================================================
441+
442+
When you bring clad to a new library, the hard part is usually finding *which*
443+
functions need a custom derivative (or a non-differentiable marker) and what
444+
signature each one must have. If clad has no custom derivative for a function
445+
and can see its definition, it silently falls back to **differentiating that
446+
definition** -- recursively descending into the library's internals (reference
447+
counting, allocation, I/O, ...), which for a library boundary is rarely what
448+
you want and often produces ill-formed or incorrect derivatives.
449+
450+
The :code:`-fclad-porting-hints` plugin flag surfaces every such boundary. Pass
451+
it through the compiler driver:
452+
453+
.. code-block:: bash
454+
455+
clang -fplugin=/path/to/clad.so \
456+
-Xclang -plugin-arg-clad -Xclang -fclad-porting-hints \
457+
-I/path/to/clad/include yourcode.cpp
458+
459+
For every function that is defined **outside the main source file** (i.e. in an
460+
included header -- the library boundary) and that clad differentiates by cloning
461+
its definition, clad emits a remark naming the exact custom-derivative signature
462+
to provide *and* the marker to declare instead:
463+
464+
.. code-block:: text
465+
466+
remark: clad has no custom derivative for 'scale' and is differentiating its
467+
definition, descending into library internals
468+
note: to differentiate it, provide clad::custom_derivatives::scale_pullback
469+
with signature 'void (const Widget *, double, double, Widget *, double *)'
470+
note: or declare it non-differentiable with
471+
clad::custom_derivatives::nondifferentiable(clad::Tag<Widget>{})
472+
473+
Each remark gives you the two ways to resolve the boundary:
474+
475+
- **Differentiate it semantically.** Copy the printed signature and implement
476+
the custom derivative (a pushforward, pullback, or reverse-forward -- see the
477+
sections above). This is the right choice when the function has a meaningful
478+
derivative that is simpler or more correct than clad cloning its
479+
implementation (a matrix product's adjoint, a container's element access, ...).
480+
- **Mark it non-differentiable.** If the type carries no differentiable data
481+
(a stream, an allocator, a reference-count handle, ...), declare
482+
:code:`clad::custom_derivatives::nondifferentiable(clad::Tag<T>{})` and clad
483+
will treat every use of it as opaque. The marker note is emitted for member
484+
functions and constructors, where the enclosing type is the thing to mark.
485+
486+
Only functions outside the main file are reported, so differentiating your own
487+
code stays quiet; the remarks focus on the library edge you are porting. The
488+
flag is a diagnostic aid only -- it changes no generated code. It is also listed
489+
in :code:`-plugin-arg-clad -help`.

include/clad/Differentiator/DerivativeBuilder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ struct DerivativeAndOverload {
201201
/// context.
202202
///
203203
DerivativeAndOverload Derive(const DiffRequest& request);
204+
/// Under -fclad-porting-hints, when \p request will differentiate the
205+
/// definition of a function defined outside the main source file (a library
206+
/// boundary) with no custom derivative, emit a remark naming the expected
207+
/// custom-derivative signature and the non-differentiable marker.
208+
void EmitPortingHint(const DiffRequest& request);
204209
/// Find the derived function if present in the DerivedFnCollector.
205210
///
206211
/// \param[in] request The request to find the derived function.

include/clad/Differentiator/DiffPlanner.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ struct DiffRequest {
122122
bool EnableVariedAnalysis = false;
123123
/// A flag to enable useful analysis during reverse-mode differentiation.
124124
bool EnableUsefulAnalysis = false;
125+
/// A flag to emit porting-hint remarks (-fclad-porting-hints) when a function
126+
/// defined outside the main source file is differentiated by cloning its
127+
/// definition. Diagnostic-only; it does not affect the generated derivative
128+
/// and is therefore excluded from request equality.
129+
bool EmitPortingHints = false;
125130
/// A flag to request a clad::restore_tracker parameter in the generated
126131
/// _reverse_forw function.
127132
bool UseRestoreTracker = false;
@@ -279,6 +284,7 @@ struct RequestOptions {
279284
bool EnableTBRAnalysis = false;
280285
bool EnableVariedAnalysis = false;
281286
bool EnableUsefulAnalysis = false;
287+
bool EmitPortingHints = false;
282288
};
283289

284290
class DiffCollector: public clang::RecursiveASTVisitor<DiffCollector> {

lib/Differentiator/DerivativeBuilder.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,9 +470,49 @@ static void registerDerivative(Decl* D, Sema& S, const DiffRequest& R) {
470470
}
471471
}
472472

473+
void DerivativeBuilder::EmitPortingHint(const DiffRequest& request) {
474+
if (!request.EmitPortingHints)
475+
return;
476+
const FunctionDecl* FD = request.Function;
477+
// A custom derivative already covers this call; nothing to port. Only a
478+
// function whose *definition* clad is about to clone is a porting gap.
479+
if (!FD || request.CustomDerivative || !FD->isDefined() ||
480+
!FD->getDeclName().isIdentifier())
481+
return;
482+
// Hint only at a library boundary: a function defined outside the main
483+
// source file (an included header), where the user decides "differentiate
484+
// vs. mark opaque" rather than clad silently cloning library internals.
485+
if (m_Sema.getSourceManager().isInMainFile(FD->getLocation()))
486+
return;
487+
488+
SourceLocation Loc = request.CallContext
489+
? request.CallContext->getBeginLoc()
490+
: FD->getLocation();
491+
llvm::SmallVector<const ValueDecl*, 4> diffParams;
492+
for (const DiffInputVarInfo& VarInfo : request.DVI)
493+
diffParams.push_back(VarInfo.param);
494+
QualType DerivativeType = utils::GetDerivativeType(
495+
m_Sema, FD, request.Mode, diffParams, /*forCustomDerv=*/true);
496+
497+
diag(DiagnosticsEngine::Remark, Loc,
498+
"clad has no custom derivative for %0 and is differentiating its "
499+
"definition, descending into library internals")
500+
<< FD;
501+
diag(DiagnosticsEngine::Note, Loc,
502+
"to differentiate it, provide clad::custom_derivatives::%0 with "
503+
"signature %1")
504+
<< request.ComputeDerivativeName() << DerivativeType;
505+
if (const auto* MD = dyn_cast<CXXMethodDecl>(FD))
506+
diag(DiagnosticsEngine::Note, Loc,
507+
"or declare it non-differentiable with "
508+
"clad::custom_derivatives::nondifferentiable(clad::Tag<%0>{})")
509+
<< MD->getParent()->getQualifiedNameAsString();
510+
}
511+
473512
DerivativeAndOverload
474513
DerivativeBuilder::Derive(const DiffRequest& request) {
475514
TimedGenerationRegion G([&request]() { return (std::string)request; });
515+
EmitPortingHint(request);
476516
if (const FunctionDecl* FD = request.Function) {
477517
// Process the custom derivative
478518
if (request.CustomDerivative) {

lib/Differentiator/DiffPlanner.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ static QualType GetDerivedFunctionType(const CallExpr* CE) {
907907
request.EnableTBRAnalysis = ReqOpts.EnableTBRAnalysis;
908908
request.EnableVariedAnalysis = ReqOpts.EnableVariedAnalysis;
909909
request.EnableUsefulAnalysis = ReqOpts.EnableUsefulAnalysis;
910+
request.EmitPortingHints = ReqOpts.EmitPortingHints;
910911

911912
const TemplateArgumentList* TAL = FD->getTemplateSpecializationArgs();
912913
assert(TAL && "Call must have specialization args!");
@@ -1255,6 +1256,7 @@ static QualType GetDerivedFunctionType(const CallExpr* CE) {
12551256
request.EnableTBRAnalysis = m_TopMostReq->EnableTBRAnalysis;
12561257
request.EnableVariedAnalysis = m_TopMostReq->EnableVariedAnalysis;
12571258
request.EnableUsefulAnalysis = m_TopMostReq->EnableUsefulAnalysis;
1259+
request.EmitPortingHints = m_TopMostReq->EmitPortingHints;
12581260
request.EnableErrorEstimation = m_TopMostReq->EnableErrorEstimation;
12591261
request.CallContext = E;
12601262

@@ -1632,6 +1634,7 @@ static QualType GetDerivedFunctionType(const CallExpr* CE) {
16321634
request.VerboseDiags = false;
16331635
request.EnableTBRAnalysis = m_TopMostReq->EnableTBRAnalysis;
16341636
request.EnableVariedAnalysis = m_TopMostReq->EnableVariedAnalysis;
1637+
request.EmitPortingHints = m_TopMostReq->EmitPortingHints;
16351638

16361639
for (const auto* paramDecl : CD->parameters())
16371640
request.DVI.push_back(paramDecl);

test/Misc/Args.C

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// CHECK_HELP-NEXT: -disable-tbr
1111
// CHECK_HELP-NEXT: -fcustom-estimation-model
1212
// CHECK_HELP-NEXT: -fprint-num-diff-errors
13+
// CHECK_HELP-NEXT: -fclad-porting-hints
1314
// CHECK_HELP-NEXT: -help
1415

1516
// RUN: clang -fsyntax-only -fplugin=%cladlib -Xclang -plugin-arg-clad\

test/Misc/PortingHints.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// With -fclad-porting-hints, clad emits a remark for every function defined
2+
// outside the main file that it differentiates by cloning (i.e. that has no
3+
// custom derivative), naming the expected custom-derivative signature and the
4+
// non-differentiable marker.
5+
// RUN: clang -std=c++17 -fsyntax-only -fplugin=%cladlib -Xclang -plugin-arg-clad -Xclang \
6+
// RUN: -fclad-porting-hints %s -I%S/../../include 2>&1 | %filecheck %s
7+
//
8+
// Without the flag, clad is silent about the clone.
9+
// RUN: clang -std=c++17 -fsyntax-only -fplugin=%cladlib %s -I%S/../../include 2>&1 \
10+
// RUN: | %filecheck --check-prefix=CHECK-QUIET --allow-empty %s
11+
//
12+
// The flag is listed in -help.
13+
// RUN: clang -std=c++17 -fsyntax-only -fplugin=%cladlib -Xclang -plugin-arg-clad -Xclang \
14+
// RUN: -help %s -I%S/../../include 2>&1 | %filecheck --check-prefix=CHECK-HELP %s
15+
//
16+
#include "clad/Differentiator/Differentiator.h"
17+
#include "PortingHintsLib.h"
18+
19+
double f(double x) {
20+
Widget w{2.0};
21+
return w.scale(x);
22+
}
23+
24+
int main() {
25+
auto g = clad::gradient(f);
26+
double dx = 0;
27+
g.execute(2, &dx);
28+
}
29+
30+
// The main-file function under differentiation is never reported as a boundary
31+
// -- only functions defined outside it are. 'f' is differentiated before it
32+
// descends into 'scale', so a broken main-file guard would emit this first.
33+
// CHECK-NOT: no custom derivative for 'f'
34+
// CHECK: remark: clad has no custom derivative for 'scale' and is differentiating its definition, descending into library internals
35+
// CHECK: note: to differentiate it, provide clad::custom_derivatives::scale_{{.*}} with signature
36+
// CHECK: note: or declare it non-differentiable with clad::custom_derivatives::nondifferentiable(clad::Tag<Widget>{})
37+
38+
// CHECK-QUIET-NOT: remark:
39+
40+
// CHECK-HELP: -fclad-porting-hints

test/Misc/PortingHintsLib.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Companion "library" header for PortingHints.C. Its functions live outside the
2+
// test's main file, so clad treats them as a library boundary and (under
3+
// -fclad-porting-hints) emits porting remarks when it differentiates them.
4+
#pragma once
5+
6+
struct Widget {
7+
double value;
8+
double scale(double x) const { return x * value; }
9+
};

tools/ClangPlugin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ void InitTimers();
588588
SetTBRAnalysisOptions(m_DO, opts);
589589
SetActivityAnalysisOptions(m_DO, opts);
590590
SetUsefulAnalysisOptions(m_DO, opts);
591+
opts.EmitPortingHints = m_DO.EmitPortingHints;
591592
}
592593

593594
DiffScheduler& CladPlugin::getScheduler() {

tools/ClangPlugin.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ struct DifferentiationOptions {
6666
bool EnableUsefulAnalysis = false;
6767
bool DisableUsefulAnalysis = false;
6868
bool PrintNumDiffErrorInfo = false;
69+
bool EmitPortingHints = false;
6970
};
7071

7172
class CladExternalSource : public clang::ExternalSemaSource {
@@ -330,6 +331,8 @@ struct DifferentiationOptions {
330331
return false;
331332
} else if (args[i] == "-fprint-num-diff-errors") {
332333
m_DO.PrintNumDiffErrorInfo = true;
334+
} else if (args[i] == "-fclad-porting-hints") {
335+
m_DO.EmitPortingHints = true;
333336
} else if (args[i] == "-help") {
334337
// Print some help info.
335338
// CI.getFrontendOpts().ShowHelp does not give us control.
@@ -357,7 +360,13 @@ struct DifferentiationOptions {
357360
"shared object to use as the custom estimation model.\n"
358361
<< "-fprint-num-diff-errors - allows users to print the "
359362
"calculated numerical diff errors, this flag is overriden "
360-
"by -DCLAD_NO_NUM_DIFF.\n";
363+
"by -DCLAD_NO_NUM_DIFF.\n"
364+
<< "-fclad-porting-hints - When clad has no custom derivative "
365+
"for a function defined outside the main source file and "
366+
"falls back to differentiating its definition, emit a "
367+
"remark naming the expected custom-derivative signature and "
368+
"the non-differentiable marker. Useful when teaching clad "
369+
"about a new library.\n";
361370

362371
llvm::errs() << "-help - Prints out this screen.\n\n";
363372
} else if (args[i] == "-version" || args[i] == "-v") {

0 commit comments

Comments
 (0)