Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion include/clad/Differentiator/CladUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "clang/AST/DeclarationName.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Sema/Ownership.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/StringRef.h"

Expand Down Expand Up @@ -256,7 +257,8 @@ namespace clad {
clang::QualType qType,
clang::Expr* arraySize,
clang::Expr* initializer,
clang::TypeSourceInfo* TSI = nullptr);
clang::TypeSourceInfo* TSI = nullptr,
clang::MultiExprArg ArgExprs = {});
Comment thread
PetroZarytskyi marked this conversation as resolved.

/// Builds a static cast to RValue expression for the expression `E`.
///
Expand Down Expand Up @@ -359,6 +361,10 @@ namespace clad {
void GetInnermostReturnExpr(const clang::Expr* E,
llvm::SmallVectorImpl<clang::Expr*>& Exprs);

void
getRecordDeclFields(const clang::RecordDecl* RD,
llvm::SmallVectorImpl<const clang::FieldDecl*>& fields);

clang::Expr* getZeroInit(clang::QualType T, clang::Sema& S);

bool ContainsFunctionCalls(const clang::Stmt* E);
Expand Down
20 changes: 20 additions & 0 deletions include/clad/Differentiator/STLBuiltins.h
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,26 @@ template <typename... Args> auto make_tuple_pushforward(Args... args) noexcept {
second_half_tuple(t));
}

// std::forward custom derivatives
template <class T>
clad::ValueAndAdjoint<T&&, T&&> forward_reverse_forw(T&& t, T&& dt) {
return {::std::forward<T>(t), ::std::forward<T>(dt)};
}

template <class T>
clad::ValueAndAdjoint<T&, T&> forward_reverse_forw(T& t, T& dt) {
return {t, dt};
}

template <class T> constexpr void forward_pullback(T& t, T dy, T* dt) noexcept {
*dt += dy;
}

template <class T>
constexpr void forward_pullback(T&& t, T dy, T* dt) noexcept {
Comment thread
PetroZarytskyi marked this conversation as resolved.
*dt += dy;
}

// std::make_shared<T> custom derivatives...
template <typename T>
clad::ValueAndAdjoint<::std::shared_ptr<T>, ::std::shared_ptr<T>>
Expand Down
22 changes: 17 additions & 5 deletions lib/Differentiator/CladUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Sema/Lookup.h"
#include <clang/AST/DeclCXX.h>

#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
Expand Down Expand Up @@ -323,14 +323,25 @@ namespace clad {
for (CXXCtorInitializer* CI : CD->inits()) {
Expr* init = CI->getInit()->IgnoreImplicit();
Expr::EvalResult dummy;
if (!(isa<DeclRefExpr>(init) ||
if (!(isa<DeclRefExpr>(init) || isa<CXXConstructExpr>(init) ||
Comment thread
PetroZarytskyi marked this conversation as resolved.
clad_compat::Expr_EvaluateAsConstantExpr(init, dummy, C)))
return false;
}
// The constructor is linear
return true;
}

void getRecordDeclFields(
const clang::RecordDecl* RD,
llvm::SmallVectorImpl<const clang::FieldDecl*>& fields) {
for (const auto* field : RD->fields())
fields.push_back(field);
if (const auto* CRD = dyn_cast<CXXRecordDecl>(RD))
for (const CXXBaseSpecifier& base : CRD->bases())
if (const auto* baseRT = base.getType()->getAs<clang::RecordType>())
getRecordDeclFields(baseRT->getDecl(), fields);
}

clang::DeclarationNameInfo BuildDeclarationNameInfo(clang::Sema& S,
llvm::StringRef name) {
ASTContext& C = S.getASTContext();
Expand Down Expand Up @@ -468,7 +479,8 @@ namespace clad {

CXXNewExpr* BuildCXXNewExpr(Sema& semaRef, QualType qType,
clang::Expr* arraySize, Expr* initializer,
clang::TypeSourceInfo* TSI) {
clang::TypeSourceInfo* TSI,
clang::MultiExprArg ArgExprs) {
auto& C = semaRef.getASTContext();
if (!TSI)
TSI = C.getTrivialTypeSourceInfo(qType);
Expand All @@ -481,8 +493,8 @@ namespace clad {
auto newExpr =
semaRef
.BuildCXXNew(
SourceRange(), false, noLoc, MultiExprArg(), noLoc,
SourceRange(), qType, TSI,
SourceRange(), false, noLoc, ArgExprs, noLoc, SourceRange(),
qType, TSI,
arraySize ? arraySize : clad_compat::llvm_Optional<Expr*>(),
initializer ? GetValidSRange(semaRef) : SourceRange(),
initializer)
Expand Down
45 changes: 45 additions & 0 deletions lib/Differentiator/ReverseModeVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,51 @@ Expr* ReverseModeVisitor::getStdInitListSizeExpr(const Expr* E) {

StmtDiff ReverseModeVisitor::DifferentiateCtorInit(CXXCtorInitializer* CI,
Expr* thisExpr) {
// If we're dealing with a delegating constructor or a
// base initializer, we need to differentiate it as
// ```
// new (_this) ClassTy(args...);
// ...
// ClassTy::constructor_pullback(args..., _d_this, _d_args...);
// ```
if (!CI->isMemberInitializer()) {
beginBlock(direction::reverse);
Expr* dthisObj = BuildOp(UO_Deref, m_ThisExprDerivative);
StmtDiff initDiff = Visit(CI->getInit(), dthisObj);
// Build the placement new.
Expr* initCall = nullptr;
if (thisExpr) {
TypeSourceInfo* baseTSI = CI->getTypeSourceInfo();
QualType baseTy = baseTSI->getType();
if (CI->isBaseInitializer()) {
Expr* placementArg = thisExpr;
// If a base initializer is used, we need to explicitly cast the
// pointer to the base type. new (static_cast<BaseTy*>(derived_ptr))
// BaseTy(args...); Note: `derived_ptr` might not be the same memory
// address as after the cast, e.g. when having multiple inheritances.
QualType ptrBaseTy = m_Context.getPointerType(baseTy);
TypeSourceInfo* ptrTSI =
m_Context.getTrivialTypeSourceInfo(ptrBaseTy);
placementArg =
m_Sema
.BuildCXXNamedCast(noLoc, tok::TokenKind::kw_static_cast,
ptrTSI, thisExpr, noLoc, noLoc)
.get();
initCall = utils::BuildCXXNewExpr(m_Sema, baseTy, nullptr,
initDiff.getExpr(), baseTSI,
{placementArg});
} else if (CI->isDelegatingInitializer()) {
auto* thisDRE = cast<DeclRefExpr>(thisExpr);
auto* thisVD = cast<VarDecl>(thisDRE->getDecl());
Expr* newInit = utils::BuildCXXNewExpr(m_Sema, baseTy, nullptr,
initDiff.getExpr(), baseTSI);
SetDeclInit(thisVD, newInit);
}
}
CompoundStmt* block = endBlock(direction::reverse);
std::reverse(block->body_begin(), block->body_end());
return {initCall, utils::unwrapIfSingleStmt(block)};
}
llvm::StringRef fieldName = CI->getMember()->getName();
Expr* memberDiff = utils::BuildMemberExpr(m_Sema, getCurrentScope(),
m_ThisExprDerivative, fieldName);
Expand Down
6 changes: 5 additions & 1 deletion lib/Differentiator/TBRAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"

#include "clad/Differentiator/CladUtils.h"
#undef DEBUG_TYPE
#define DEBUG_TYPE "clad-tbr"

Expand Down Expand Up @@ -135,7 +137,9 @@ TBRAnalyzer::VarData::VarData(QualType QT, const ASTContext& C,
const auto* recordDecl = recordType->getDecl();
auto& newArrMap = m_Val.m_ArrData;
newArrMap = std::unique_ptr<ArrMap>(new ArrMap());
for (const auto* field : recordDecl->fields()) {
llvm::SmallVector<const FieldDecl*, 4> Fields;
utils::getRecordDeclFields(recordDecl, Fields);
Comment thread
PetroZarytskyi marked this conversation as resolved.
for (const auto* field : Fields) {
const auto varType = field->getType();
(*newArrMap)[getProfileID(field)] = VarData(varType, C);
}
Expand Down
176 changes: 175 additions & 1 deletion test/Gradient/Constructors.C
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ double fn1(double x, double y) {
argByVal g(x);
y = x;
return y + g.y;
}
} // x + x^2

// CHECK: static void constructor_pullback(double val, argByVal *_d_this, double *_d_val) {
// CHECK-NEXT: argByVal *_this = (argByVal *)malloc(sizeof(argByVal));
Expand Down Expand Up @@ -285,6 +285,168 @@ double fn4(double i, double j) {
// CHECK-NEXT: }
// CHECK-NEXT: }

struct argByValWrapper : public argByVal {
double z;
argByValWrapper(double v) : argByVal(v) {}
argByValWrapper(double v, double u) : argByValWrapper(v) {
z = y * u;
}
argByValWrapper(double v, bool) : argByVal(v) {
z = x * y;
}
};

double fn5(double x, double y) {
argByValWrapper g(x);
y = x;
return y + g.y;
} // x + x^2

// CHECK: static void constructor_pullback(double v, argByValWrapper *_d_this, double *_d_v) {
// CHECK-NEXT: {
// CHECK-NEXT: double _r0 = 0.;
// CHECK-NEXT: argByVal::constructor_pullback(v, &*_d_this, &_r0);
// CHECK-NEXT: *_d_v += _r0;
// CHECK-NEXT: }
// CHECK-NEXT: }

// CHECK: void fn5_grad(double x, double y, double *_d_x, double *_d_y) {
// CHECK-NEXT: argByValWrapper g(x);
// CHECK-NEXT: argByValWrapper _d_g(g);
// CHECK-NEXT: clad::zero_init(_d_g);
// CHECK-NEXT: double _t0 = y;
// CHECK-NEXT: y = x;
// CHECK-NEXT: {
// CHECK-NEXT: *_d_y += 1;
// CHECK-NEXT: _d_g.y += 1;
// CHECK-NEXT: }
// CHECK-NEXT: {
// CHECK-NEXT: y = _t0;
// CHECK-NEXT: double _r_d0 = *_d_y;
// CHECK-NEXT: *_d_y = 0.;
// CHECK-NEXT: *_d_x += _r_d0;
// CHECK-NEXT: }
// CHECK-NEXT: {
// CHECK-NEXT: double _r0 = 0.;
// CHECK-NEXT: argByValWrapper::constructor_pullback(x, &_d_g, &_r0);
// CHECK-NEXT: *_d_x += _r0;
// CHECK-NEXT: }
// CHECK-NEXT: }

double fn6(double x, double y) {
argByValWrapper g(x, y);
return g.z;
} // x^2 * y

// CHECK: static void constructor_pullback(double v, double u, argByValWrapper *_d_this, double *_d_v, double *_d_u) {
// CHECK-NEXT: argByValWrapper *_this = new argByValWrapper(v);
// CHECK-NEXT: double _t0 = _this->z;
// CHECK-NEXT: _this->z = _this->y * u;
// CHECK-NEXT: {
// CHECK-NEXT: _this->z = _t0;
// CHECK-NEXT: double _r_d0 = _d_this->z;
// CHECK-NEXT: _d_this->z = 0.;
// CHECK-NEXT: _d_this->y += _r_d0 * u;
// CHECK-NEXT: *_d_u += _this->y * _r_d0;
// CHECK-NEXT: }
// CHECK-NEXT: {
// CHECK-NEXT: double _r0 = 0.;
// CHECK-NEXT: argByValWrapper::constructor_pullback(v, &*_d_this, &_r0);
// CHECK-NEXT: *_d_v += _r0;
// CHECK-NEXT: }
// CHECK-NEXT: free(_this);
// CHECK-NEXT: }

// CHECK: void fn6_grad(double x, double y, double *_d_x, double *_d_y) {
// CHECK-NEXT: argByValWrapper g(x, y);
// CHECK-NEXT: argByValWrapper _d_g(g);
// CHECK-NEXT: clad::zero_init(_d_g);
// CHECK-NEXT: _d_g.z += 1;
// CHECK-NEXT: {
// CHECK-NEXT: double _r0 = 0.;
// CHECK-NEXT: double _r1 = 0.;
// CHECK-NEXT: argByValWrapper::constructor_pullback(x, y, &_d_g, &_r0, &_r1);
// CHECK-NEXT: *_d_x += _r0;
// CHECK-NEXT: *_d_y += _r1;
// CHECK-NEXT: }
// CHECK-NEXT: }

double fn7(double x, double y) {
argByValWrapper g(x, false);
return g.z;
} // x^3

// CHECK: static void constructor_pullback(double v, bool arg, argByValWrapper *_d_this, double *_d_v, bool *_d_arg) {
// CHECK-NEXT: argByValWrapper *_this = (argByValWrapper *)malloc(sizeof(argByValWrapper));
// CHECK-NEXT: new (static_cast<argByVal *>(_this)) argByVal(v);
Comment on lines +380 to +381

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

What do we lose if we call argByValWrapper *_this = new argByValWrapper(...)? The memory will be initialized with some values but we will overwrite them. That would not lead to significant performance regression I'd think...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Well, some constructor calls have side effects and shouldn't be called twice. A real-world example would be move-constructors. If that's better, we can check if the constructor doesn't have any side effects by examining its parameter types or other factors. Also, I think the question is more about the logic in constructor pullbacks in general than about this change in particular.

// CHECK-NEXT: double _t0 = _this->z;
// CHECK-NEXT: _this->z = _this->x * _this->y;
// CHECK-NEXT: {
// CHECK-NEXT: _this->z = _t0;
// CHECK-NEXT: double _r_d0 = _d_this->z;
// CHECK-NEXT: _d_this->z = 0.;
// CHECK-NEXT: _d_this->x += _r_d0 * _this->y;
// CHECK-NEXT: _d_this->y += _this->x * _r_d0;
// CHECK-NEXT: }
// CHECK-NEXT: {
// CHECK-NEXT: double _r0 = 0.;
// CHECK-NEXT: argByVal::constructor_pullback(v, &*_d_this, &_r0);
// CHECK-NEXT: *_d_v += _r0;
// CHECK-NEXT: }
// CHECK-NEXT: free(_this);
// CHECK-NEXT: }


// CHECK: void fn7_grad(double x, double y, double *_d_x, double *_d_y) {
// CHECK-NEXT: argByValWrapper g(x, false);
// CHECK-NEXT: argByValWrapper _d_g(g);
// CHECK-NEXT: clad::zero_init(_d_g);
// CHECK-NEXT: _d_g.z += 1;
// CHECK-NEXT: {
// CHECK-NEXT: double _r0 = 0.;
// CHECK-NEXT: bool _r1 = false;
// CHECK-NEXT: argByValWrapper::constructor_pullback(x, false, &_d_g, &_r0, &_r1);
// CHECK-NEXT: *_d_x += _r0;
// CHECK-NEXT: }
// CHECK-NEXT: }

double fn8(double u, double v) {
std::pair<double, double> p(u,v);
return p.first + p.second;
}

// CHECK: static constexpr void constructor_pullback(double &__{{u1|x}}, double &__{{u2|y}}, std::pair<double, double> *_d_this, double *_d___{{u1|x}}, double *_d___{{u2|y}}) {{.*}}{
// CHECK-NEXT: std::pair<double, double> *_this = (std::pair<double, double> *)malloc(sizeof(std::pair<double, double>));
// CHECK: double _t0 = __{{u1|x}};
// CHECK-NEXT: clad::ValueAndAdjoint<double &, double &> _t1 = clad::custom_derivatives::std::forward_reverse_forw(__{{u1|x}}, *_d___{{u1|x}});
// CHECK-NEXT: _this->first = _t1.value;
// CHECK-NEXT: double _t2 = __{{u2|y}};
// CHECK-NEXT: clad::ValueAndAdjoint<double &, double &> _t3 = clad::custom_derivatives::std::forward_reverse_forw(__{{u2|y}}, *_d___{{u2|y}});
// CHECK-NEXT: _this->second = _t3.value;
// CHECK: {
// CHECK-NEXT: clad::custom_derivatives::std::forward_pullback(__{{u2|y}}, _d_this->second, &*_d___{{u2|y}});
// CHECK-NEXT: __{{u2|y}} = _t2;
// CHECK-NEXT: _d_this->second = 0.;
// CHECK-NEXT: }
// CHECK-NEXT: {
// CHECK-NEXT: clad::custom_derivatives::std::forward_pullback(__{{u1|x}}, _d_this->first, &*_d___{{u1|x}});
// CHECK-NEXT: __{{u1|x}} = _t0;
// CHECK-NEXT: _d_this->first = 0.;
// CHECK-NEXT: }
// CHECK-NEXT: free(_this);
// CHECK-NEXT: }

// CHECK: void fn8_grad(double u, double v, double *_d_u, double *_d_v) {
// CHECK-NEXT: std::pair<double, double> p(u, v);
// CHECK-NEXT: std::pair<double, double> _d_p(p);
// CHECK-NEXT: clad::zero_init(_d_p);
// CHECK-NEXT: {
// CHECK-NEXT: _d_p.first += 1;
// CHECK-NEXT: _d_p.second += 1;
// CHECK-NEXT: }
// CHECK-NEXT: pair::constructor_pullback(u, v, &_d_p, &*_d_u, &*_d_v);
// CHECK-NEXT: }

int main() {
double d_i, d_j;

Expand All @@ -298,4 +460,16 @@ int main() {

INIT_GRADIENT(fn4);
TEST_GRADIENT(fn4, /*numOfDerivativeArgs=*/2, 3, 4, &d_i, &d_j); // CHECK-EXEC: {1.00, 0.00}

INIT_GRADIENT(fn5);
TEST_GRADIENT(fn5, /*numOfDerivativeArgs=*/2, 3, 4, &d_i, &d_j); // CHECK-EXEC: {7.00, 0.00}

INIT_GRADIENT(fn6);
TEST_GRADIENT(fn6, /*numOfDerivativeArgs=*/2, 3, 4, &d_i, &d_j); // CHECK-EXEC: {24.00, 9.00}

INIT_GRADIENT(fn7);
TEST_GRADIENT(fn7, /*numOfDerivativeArgs=*/2, 2, 9, &d_i, &d_j); // CHECK-EXEC: {12.00, 0.00}

INIT_GRADIENT(fn8);
TEST_GRADIENT(fn8, /*numOfDerivativeArgs=*/2, 7, 2, &d_i, &d_j); // CHECK-EXEC: {1.00, 1.00}
}
Loading