Skip to content

Undo a shrinking in-place realloc in the reverse sweep. - #1936

Open
vgvassilev wants to merge 1 commit into
masterfrom
shrink-realloc
Open

Undo a shrinking in-place realloc in the reverse sweep.#1936
vgvassilev wants to merge 1 commit into
masterfrom
shrink-realloc

Conversation

@vgvassilev

Copy link
Copy Markdown
Owner

Since #1925 an in-place p = realloc(p, n) keeps the reallocated pointer for both p and its adjoint instead of saving and restoring it, because realloc frees the old block and a saved pointer would dangle. That is correct only when the buffer grows or keeps its size: a shrinking realloc leaves the reverse sweep addressing a buffer smaller than the indices the forward pass wrote, reading past its end.

Track each differentiated pointer's allocation size in a size_t shadow, set at malloc/calloc/realloc. At an in-place realloc, save the pre-realloc size in the forward pass and, in the reverse sweep, call clad::reverse_realloc to resize both the primal and adjoint buffers back to it so the pre-realloc accesses stay in bounds; the adjoint's re-grown tail is zeroed so fresh derivatives start at 0. Without a tracked size the previous keep-the-pointer behaviour still applies.

Add Gradient/ReallocShrink.C, which is Memcheck-clean under the valgrind row only with this change; update Gradient/Pointers.C for the shadows.

@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

// being overwritten by value restores. Returns the possibly-moved pointer.
inline void* reverse_realloc(void* ptr, size_t oldBytes, size_t newBytes,
bool zeroGrownTail) {
ptr = ::realloc(ptr, oldBytes);

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: 'ptr' may be set to null if 'realloc' fails, which may result in a leak of the original buffer [bugprone-suspicious-realloc-usage]

  ptr = ::realloc(ptr, oldBytes);
        ^

// being overwritten by value restores. Returns the possibly-moved pointer.
inline void* reverse_realloc(void* ptr, size_t oldBytes, size_t newBytes,
bool zeroGrownTail) {
ptr = ::realloc(ptr, oldBytes);

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: assigning newly created 'gsl::owner<>' to non-owner 'void *' [cppcoreguidelines-owning-memory]

  ptr = ::realloc(ptr, oldBytes);
  ^

// being overwritten by value restores. Returns the possibly-moved pointer.
inline void* reverse_realloc(void* ptr, size_t oldBytes, size_t newBytes,
bool zeroGrownTail) {
ptr = ::realloc(ptr, oldBytes);

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: calling legacy resource function without passing a 'gsl::owner<>' [cppcoreguidelines-owning-memory]

  ptr = ::realloc(ptr, oldBytes);
        ^

// being overwritten by value restores. Returns the possibly-moved pointer.
inline void* reverse_realloc(void* ptr, size_t oldBytes, size_t newBytes,
bool zeroGrownTail) {
ptr = ::realloc(ptr, oldBytes);

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 manage memory manually; consider std::vector or std::string [cppcoreguidelines-no-malloc]

  ptr = ::realloc(ptr, oldBytes);
        ^

bool zeroGrownTail) {
ptr = ::realloc(ptr, oldBytes);
if (zeroGrownTail && ptr && oldBytes > newBytes)
::memset(static_cast<char*>(ptr) + newBytes, 0, oldBytes - newBytes);

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 pointer arithmetic [cppcoreguidelines-pro-bounds-pointer-arithmetic]

    ::memset(static_cast<char*>(ptr) + newBytes, 0, oldBytes - newBytes);
                                     ^

/// Maps a differentiated pointer variable to the `size_t` shadow that holds
/// its current allocation size in bytes. Populated at malloc/calloc/realloc
/// so an in-place realloc can restore the pre-realloc size in reverse.
llvm::DenseMap<const clang::VarDecl*, clang::VarDecl*> m_PointerAllocSize;

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

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

- #include <memory>
+ #include <llvm/ADT/DenseMap.h>
+ #include <memory>

@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

// it instead of duplicating the matcher.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
AllocCallInfo ac =
AllocCallInfo::recognize(const_cast<Expr*>(BO->getRHS()));

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 'Expr *' as the sub-expression, remove this casting [readability-redundant-casting]

Suggested change
AllocCallInfo::recognize(const_cast<Expr*>(BO->getRHS()));
AllocCallInfo::recognize(BO->getRHS());
Additional context

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

  Expr *getRHS() const { return cast<Expr>(SubExprs[RHS]); }
        ^

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/Differentiator/ReverseModeVisitor.cpp 93.33% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Since #1925 an in-place `p = realloc(p, n)` keeps the reallocated
pointer for both p and its adjoint instead of saving and restoring it,
because realloc frees the old block and a saved pointer would dangle.
That is correct only when the buffer grows or keeps its size: a
shrinking realloc leaves the reverse sweep addressing a buffer smaller
than the indices the forward pass wrote, reading past its end.

Track each differentiated pointer's allocation size in a size_t shadow,
set at malloc/calloc/realloc. At an in-place realloc, save the
pre-realloc size in the forward pass and, in the reverse sweep, call
clad::reverse_realloc to resize both the primal and adjoint buffers back
to it so the pre-realloc accesses stay in bounds; the adjoint's re-grown
tail is zeroed so fresh derivatives start at 0. Without a tracked size
the previous keep-the-pointer behaviour still applies.

Add Gradient/ReallocShrink.C, which is Memcheck-clean under the valgrind
row only with this change; update Gradient/Pointers.C for the shadows.
@github-actions

Copy link
Copy Markdown
Contributor

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

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.

1 participant