Closed
Description
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")