Skip to content

[Autodiff] Memory leaks found under certain conditions. #67323

Closed
@fibrechannelscsi

Description

@fibrechannelscsi

Description
The following code was found to leak memory upon the process exiting.

Steps to reproduce
First, compile the following code in Debug mode (in a single file):

import _Differentiation; import Foundation
public struct B: Differentiable & Z {public var e: Float = 0}
extension B {@differentiable(reverse) func uu(dt: Float, nt: A<SIMD8<Float>>, f: A<Float>, zs: [S]) -> N {let nt = nt; let f = f; return N(nt: nt, f: f)}}
extension B {@differentiable(reverse) mutating func a(_ r: C) {}}
struct S: Differentiable & Z {}
struct C: Differentiable {var wm: Array<SIMD8<Float>>}
struct W: Differentiable {var h: B}
struct N: Differentiable {var nt: A<SIMD8<Float>>; var f: A<Float>}
struct O: Differentiable {@noDerivative public var h: B; @differentiable(reverse) public init(h: B) {self.h = h}}
public struct A<T>: Differentiable where T: Differentiable, T: AdditiveArithmetic {
    public struct TangentVector: Differentiable, AdditiveArithmetic {
        public typealias TangentVector = A.TangentVector
        public var _b: [T.TangentVector]
        public var _a: T.TangentVector
        public init(_b: [T.TangentVector], _a: T.TangentVector) {self._b = _b; self._a = _a}
    }
    @usableFromInline var _v: [T]
    @inlinable @differentiable(reverse) public init(_ values: [T], _a: T = .zero) {self._v = values}
    @inlinable @differentiable(reverse) public var _r: [T] { return _v }
    @inlinable @derivative(of: init(_:_a:)) static func _vjpInit(_ values: [T], _a: T = .zero) -> (value: A, pullback: (TangentVector) -> (Array<T>.TangentVector, T.TangentVector)){return (A(values, _a: _a), {v in return (Array<T>.TangentVector(v._b), v._a)})}
    @inlinable @derivative(of: _r) func vjpArray() -> (value: [T], pullback: (Array<T>.TangentVector) -> TangentVector) {func pullback(v: Array<T>.TangentVector) -> TangentVector {return TangentVector(_b: v.base, _a: T.TangentVector.zero)}; return (_v, pullback)}
    public mutating func move(by offset: TangentVector) {}
}
public extension A.TangentVector { // Not mathematically correct, of course, but simplified to this to demonstrate the memory leak(s).
    @inlinable static func + (lhs: Self, rhs: Self) -> Self {return lhs}
    @inlinable static func - (lhs: Self, rhs: Self) -> Self {return lhs}
    @inlinable static var zero: Self { Self(_b: [], _a: .zero) }
}
public protocol Z: Differentiable {}
func g(h: B, dt: Float) -> C {
    let nt = A([SIMD8<Float>(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)])
    let N = h.uu(dt: 180.0, nt: nt, f: A([Float(0.0)]), zs: [S]())
    return C(wm: N.nt._r)
}
func o<T, R>(_ x: T, _ f: @differentiable(reverse) (T) -> R) -> R {f(x)}
func p<T, R>(_ f: @escaping @differentiable(reverse) (T) -> R) -> @differentiable(reverse) (T) -> R {{ x in o(x, f) }}
@differentiable(reverse) func j(h: B, r: S) -> O {
    @differentiable(reverse) func q(_ l: W) -> W {var h = l.h; d(h: &h, r: r); var n = l; n.h = h; return n}
    var W = W(h: h)
    for _ in 0 ..< 1 {W = p(q)(W)} // The single-iteration for-loop needs to be here, otherwise the leak(s) won't occur.
    return O(h: W.h)
}
func b(h: B, r: S) -> (value: O, pullback: (O.TangentVector) -> (B.TangentVector, S.TangentVector))
{
    let s = valueWithPullback(at: h, r, of: j)
    return (value: s.value, pullback: s.pullback)
}
@differentiable(reverse) func d(h: inout B, r: S) {h.a(g(h: h, dt: h.e))}
func main() throws{_ = b(h: B(), r: S())}
try! main()

Next, run leaks, or another memory leak-checking tool.
The command line for leaks to generate the following output is: leaks --atExit -- ./executableName.
Note that Xcode's memory leak checker may give inconsistent results from run to run with this code snippet.

The stack trace of the first leak is as follows:

STACK OF 1 INSTANCE OF 'ROOT LEAK: <Swift closure context>':
10  dyld                                  0x182ddff28 start + 2236
9   memleak2                              0x104cfaea0 main + 28  main.swift:50
8   memleak2                              0x104cfea38 main() + 36  main.swift:49
7   memleak2                              0x104cfe790 b(h:r:) + 436  main.swift:45
6   libswift_Differentiation.dylib        0x21081f0dc valueWithPullback<A, B, C>(at:_:of:) + 160
5   memleak2                              0x104cfe950 thunk for @callee_guaranteed (@unowned B, @unowned S) -> (@unowned O, @owned @escaping @callee_guaranteed (@unowned O.TangentVector) -> (@unowned B.TangentVector, @unowned S.TangentVector)) + 48  <compiler-generated>:0
4   memleak2                              0x104d00550 reverse-mode derivative of j(h:r:) + 1072  main.swift:40
3   memleak2                              0x104cfe570 thunk for @escaping @callee_guaranteed (@in_guaranteed W) -> (@out W, @owned @escaping @callee_guaranteed (@in_guaranteed W.TangentVector) -> (@out W.TangentVector)) + 100  <compiler-generated>:0
2   libswiftCore.dylib                    0x1921e0790 swift_allocObject + 64
1   libswiftCore.dylib                    0x1921e0594 swift_slowAlloc + 64
0   libsystem_malloc.dylib                0x182f78d88 _malloc_zone_malloc_instrumented_or_legacy + 128 
====
    14 (864 bytes) ROOT LEAK: <Swift closure context 0x147708560> [48]
       13 (816 bytes)  + 8 --> <Swift closure context 0x147708510> [80]
          12 (736 bytes)  + 8 --> <Swift closure context 0x1477084c0> [80]
             11 (656 bytes)  + 8 --> <Swift closure context 0x147708470> [80]
                10 (576 bytes)  + 8 --> <Swift closure context 0x147707ed0> [80]
                   9 (496 bytes)  + 8 --> <Swift closure context 0x147708440> [48]
                      8 (448 bytes)  + 8 --> <Swift closure context 0x147708410> [48]
                         7 (400 bytes)  + 8 --> <Swift closure context 0x147708360> [48]
                            6 (352 bytes)  + 8 --> <Swift closure context 0x1477081e0> [64]
                               5 (288 bytes)  + 8 --> <malloc in reverse-mode derivative of g(h:dt:) 0x1477083c0> [80]
                                  3 (160 bytes) <malloc in thunk for @escaping @callee_guaranteed (@guaranteed A<SIMD8<Float>>) -> (@owned [SIMD8<Float>], @owned @escaping @callee_guaranteed @substituted <A, B> (@guaranteed A) -> (@out B) for <[SIMD8<Float>]<A>.DifferentiableViewA<SIMD8<Float>>.TangentVector>) 0x147708390> [48]
                                     2 (112 bytes) <malloc in reverse-mode derivative of A._r.getter 0x147708140> [64]
                                        1 (48 bytes) <Swift closure context 0x147708330> [48]
                                  1 (48 bytes) <Swift closure context 0x147708300> [48]

The second one looks like this:

STACK OF 1 INSTANCE OF 'ROOT LEAK: <Swift closure context>':
20  dyld                                  0x182ddff28 start + 2236
19  memleak2                              0x104cfaea0 main + 28  main.swift:50
18  memleak2                              0x104cfea38 main() + 36  main.swift:49
17  memleak2                              0x104cfe790 b(h:r:) + 436  main.swift:45
16  libswift_Differentiation.dylib        0x21081f0dc valueWithPullback<A, B, C>(at:_:of:) + 160
15  memleak2                              0x104cfe950 thunk for @callee_guaranteed (@unowned B, @unowned S) -> (@unowned O, @owned @escaping @callee_guaranteed (@unowned O.TangentVector) -> (@unowned B.TangentVector, @unowned S.TangentVector)) + 48  <compiler-generated>:0
14  memleak2                              0x104d00550 reverse-mode derivative of j(h:r:) + 1072  main.swift:40
13  memleak2                              0x104cfe544 thunk for @escaping @callee_guaranteed (@in_guaranteed W) -> (@out W, @owned @escaping @callee_guaranteed (@in_guaranteed W.TangentVector) -> (@out W.TangentVector)) + 56  <compiler-generated>:0
12  memleak2                              0x104cff2dc partial apply + 96  <compiler-generated>:0
11  memleak2                              0x104d02498 reverse-mode derivative of closure #1 in p<A, B>(_:) + 636  main.swift:36
10  memleak2                              0x104d02850 reverse-mode derivative of o<A, B>(_:_:) + 188  main.swift:35
9   memleak2                              0x104cfe440 thunk for @escaping @callee_guaranteed (@unowned W) -> (@unowned W, @owned @escaping @callee_guaranteed (@unowned W.TangentVector) -> (@unowned W.TangentVector)) + 48  <compiler-generated>:0
8   memleak2                              0x104cfff88 reverse-mode derivative of q #1 (_:) in j(h:r:) + 60  main.swift:38
7   memleak2                              0x104d020d8 autodiff subset parameters thunk for reverse-mode derivative from d(h:r:) + 44  <compiler-generated>:0
6   memleak2                              0x104d00bf4 reverse-mode derivative of d(h:r:) + 84  main.swift:48
5   memleak2                              0x104d01b68 reverse-mode derivative of g(h:dt:) + 532  main.swift:32
4   memleak2                              0x104d01fcc autodiff subset parameters thunk for reverse-mode derivative from B.uu(dt:nt:f:zs:) + 44  <compiler-generated>:0
3   memleak2                              0x104cff8d0 reverse-mode derivative of B.uu(dt:nt:f:zs:) + 224  main.swift:3
2   libswiftCore.dylib                    0x1921e0790 swift_allocObject + 64
1   libswiftCore.dylib                    0x1921e0594 swift_slowAlloc + 64
0   libsystem_malloc.dylib                0x182f78d88 _malloc_zone_malloc_instrumented_or_legacy + 128 
====
    1 (48 bytes) ROOT LEAK: <Swift closure context 0x1477082d0> [48]

Expected behavior
No leaks should be detected, and the program should exit with an exit code of 0.

Environment

  • Swift compiler version info: Toolchain 2023-07-10a. Toolchains as far back as 2023-01-09a will also exhibit this issue.
  • Xcode version info: 14.2
  • Deployment target: M1

Additional context

  • Removing the for loop in line 40, and running its contents once, will cause the leaks to vanish.
  • Removing the variable f from lines 3, 8 and 32 will cause one, but not both, of the leaks to vanish.
  • Removing the conformance to AdditiveArithmetic in line 11 will crash the compiler, with the following assertion failing: Assertion failed: (!ActiveDiagnostic && "Already have an active diagnostic")

Metadata

Metadata

Assignees

Labels

AutoDiffbugA deviation from expected or documented behavior. Also: expected but undesirable behavior.memory leakbug: Memory leak

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions