|
9 | 9 |
|
10 | 10 | #include "clang/AST/Decl.h" |
11 | 11 | #include "clang/AST/DeclBase.h" |
| 12 | +#include "clang/AST/Expr.h" |
12 | 13 | #include "clang/AST/ExprCXX.h" |
13 | 14 | #include "clang/AST/RecursiveASTVisitor.h" |
14 | 15 | #include "clang/AST/Stmt.h" |
|
17 | 18 |
|
18 | 19 | #include "llvm/ADT/DenseSet.h" |
19 | 20 | #include "llvm/ADT/SmallVector.h" |
| 21 | +#include "llvm/ADT/StringSwitch.h" |
| 22 | +#include "llvm/Support/Casting.h" |
20 | 23 | #include "llvm/Support/Compiler.h" |
21 | 24 | #include "llvm/Support/SaveAndRestore.h" |
22 | 25 | #include "llvm/Support/raw_ostream.h" |
23 | 26 |
|
| 27 | +#include <cstdint> |
24 | 28 | #include <functional> |
25 | 29 | #include <iterator> |
26 | 30 | #include <map> |
@@ -49,6 +53,84 @@ using ParamInfo = std::map<const clang::FunctionDecl*, ParamSet>; |
49 | 53 | /// rediscovering them inside a visitor, keeps them available to every visitor |
50 | 54 | /// and correct after a request is copied and re-pointed at another Function. |
51 | 55 | struct DiffRequest { |
| 56 | + /// Recognises a C heap-memory builtin call and centralises the invariants |
| 57 | + /// reverse mode must preserve for it, so all memory-op reasoning goes through |
| 58 | + /// one place instead of ad-hoc name checks scattered across the code base. |
| 59 | + /// The planner uses it to record which pointers are reallocated in place; the |
| 60 | + /// reverse-mode visitor uses it to emit and undo the resize. |
| 61 | + class AllocCallInfo { |
| 62 | + public: |
| 63 | + enum class Kind : std::uint8_t { None, Malloc, Calloc, Realloc, Free }; |
| 64 | + |
| 65 | + AllocCallInfo() = default; |
| 66 | + |
| 67 | + // Recognise E as a memory builtin; Kind::None if it is not one. Strips the |
| 68 | + // C-style cast that wraps the call (e.g. `(double*)realloc(...)`), so |
| 69 | + // IgnoreParenCasts, not IgnoreParenImpCasts, is required here. |
| 70 | + [[nodiscard]] static AllocCallInfo recognize(clang::Expr* E) { |
| 71 | + auto* CE = llvm::dyn_cast_or_null<clang::CallExpr>( |
| 72 | + E ? E->IgnoreParenCasts() : nullptr); |
| 73 | + if (!CE) |
| 74 | + return {}; |
| 75 | + const clang::FunctionDecl* FD = CE->getDirectCallee(); |
| 76 | + // getName() asserts on non-identifier names (operators, constructors), |
| 77 | + // which vector/STL code produces; the builtins are plain identifiers. |
| 78 | + if (!FD || !FD->getDeclName().isIdentifier()) |
| 79 | + return {}; |
| 80 | + Kind k = llvm::StringSwitch<Kind>(FD->getName()) |
| 81 | + .Case("malloc", Kind::Malloc) |
| 82 | + .Case("calloc", Kind::Calloc) |
| 83 | + .Case("realloc", Kind::Realloc) |
| 84 | + .Case("free", Kind::Free) |
| 85 | + .Default(Kind::None); |
| 86 | + return AllocCallInfo(k, CE); |
| 87 | + } |
| 88 | + |
| 89 | + [[nodiscard]] Kind getKind() const { return m_Kind; } |
| 90 | + [[nodiscard]] clang::CallExpr* getCall() const { return m_Call; } |
| 91 | + |
| 92 | + // The number-of-bytes operand that a following memset must zero: |
| 93 | + // malloc(n) -> n, realloc(p, n) -> n. calloc self-zeroes and needs no |
| 94 | + // memset, so it (and free/none) report null here. |
| 95 | + [[nodiscard]] clang::Expr* memsetByteSize() const { |
| 96 | + switch (m_Kind) { |
| 97 | + case Kind::Malloc: |
| 98 | + return m_Call->getArg(0); |
| 99 | + case Kind::Realloc: |
| 100 | + return m_Call->getArg(1); |
| 101 | + default: |
| 102 | + return nullptr; |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + // True for an in-place `p = realloc(p, n)`: the LHS is realloc's own |
| 107 | + // pointer argument. Only then may the reallocated pointer be kept across |
| 108 | + // the call (realloc frees the old block, so a saved pointer would dangle). |
| 109 | + [[nodiscard]] bool isInPlaceRealloc(const clang::Expr* LHS) const { |
| 110 | + if (m_Kind != Kind::Realloc || m_Call->getNumArgs() == 0) |
| 111 | + return false; |
| 112 | + const auto* LDRE = |
| 113 | + llvm::dyn_cast<clang::DeclRefExpr>(LHS->IgnoreParenCasts()); |
| 114 | + const auto* ArgDRE = llvm::dyn_cast<clang::DeclRefExpr>( |
| 115 | + m_Call->getArg(0)->IgnoreParenCasts()); |
| 116 | + return LDRE && ArgDRE && LDRE->getDecl() == ArgDRE->getDecl(); |
| 117 | + } |
| 118 | + |
| 119 | + // The pointer variable of an in-place `p = realloc(p, n)`, or null. |
| 120 | + [[nodiscard]] const clang::VarDecl* |
| 121 | + getInPlaceReallocPtr(const clang::Expr* LHS) const { |
| 122 | + if (!isInPlaceRealloc(LHS)) |
| 123 | + return nullptr; |
| 124 | + return llvm::dyn_cast<clang::VarDecl>( |
| 125 | + llvm::cast<clang::DeclRefExpr>(LHS->IgnoreParenCasts())->getDecl()); |
| 126 | + } |
| 127 | + |
| 128 | + private: |
| 129 | + AllocCallInfo(Kind k, clang::CallExpr* c) : m_Kind(k), m_Call(c) {} |
| 130 | + Kind m_Kind = Kind::None; |
| 131 | + clang::CallExpr* m_Call = nullptr; |
| 132 | + }; |
| 133 | + |
52 | 134 | private: |
53 | 135 | /// Based on To-Be-Recorded analysis performed before differentiation, tells |
54 | 136 | /// UsefulToStoreGlobal whether a variable with a given SourceLocation has to |
@@ -109,6 +191,16 @@ struct DiffRequest { |
109 | 191 | const clang::Expr* Args = nullptr; |
110 | 192 | /// Indexes of global GPU args of function as a subset of Args. |
111 | 193 | std::vector<size_t> CUDAGlobalArgsIndexes; |
| 194 | + /// Pointer variables that are the target of an in-place `p = realloc(p, n)` |
| 195 | + /// in this function, collected by DiffCollector during planning. Reverse |
| 196 | + /// mode gives exactly these an allocation-size shadow so the realloc can be |
| 197 | + /// undone; other allocated pointers get none. Empty unless the body |
| 198 | + /// reallocates in place. |
| 199 | + std::set<const clang::VarDecl*> InPlaceReallocPtrs; |
| 200 | + /// Whether VD is reallocated in place somewhere in this function. |
| 201 | + bool isInPlaceReallocated(const clang::VarDecl* VD) const { |
| 202 | + return InPlaceReallocPtrs.count(VD) != 0; |
| 203 | + } |
112 | 204 | /// Requested differentiation mode, forward or reverse. |
113 | 205 | DiffMode Mode = DiffMode::unknown; |
114 | 206 | /// If function appears in the call to clad::gradient/differentiate, |
@@ -323,6 +415,10 @@ struct RequestOptions { |
323 | 415 | void Walk(clang::DeclGroupRef DGR); |
324 | 416 | bool VisitCallExpr(clang::CallExpr* E); |
325 | 417 | bool VisitDeclRefExpr(clang::DeclRefExpr* DRE); |
| 418 | + /// Record an in-place `p = realloc(p, n)` on the request whose body is |
| 419 | + /// being traversed, so reverse mode knows p needs an allocation-size |
| 420 | + /// shadow. |
| 421 | + bool VisitBinaryOperator(clang::BinaryOperator* BO); |
326 | 422 | bool VisitCXXConstructExpr(clang::CXXConstructExpr* e); |
327 | 423 | bool shouldVisitImplicitCode() const { return true; } |
328 | 424 | /// Here we use TraverseLambdaExpr and not VisitLambdaExpr to ensure the |
|
0 commit comments