Find out which object gets released in swift_release

Hi!

I have a program compiled with whole module optimization and try to get rid of all swift_release's in the hot loop. I'm on Linux. The backtrace in lldb is not particularly helpful because the line numbers are not reliable (A.swift:0:73 or :0). I just try to find out which struct or class is being released. How do I get that information?

Thanks.

Typical backtrace:

* thread #2, name = 'gonzales', stop reason = breakpoint 1.1
  * frame #0: 0x00007ffff7016a90 libswiftCore.so`swift_release
    frame #1: 0x00005555556451b0 gonzales`BoundingHierarchy.intersect(ray=<unavailable>, tHit=<unavailable>, material=<unavailable>, self=0x0000555555712090) at BoundingHierarchy.swift:0:73 [opt]
    frame #2: 0x000055555564548b gonzales`protocol witness for Intersectable.intersect(ray:tHit:material:) in conformance BoundingHierarchy at <compiler-generated>:0 [opt]
    frame #3: 0x000055555565f656 gonzales`Scene.intersect(ray=gonzales.Ray @ 0x00007ffff33fbb40, tHit=<unavailable>, self=<unavailable>) at Scene.swift:22:38 [opt]
    frame #4: 0x0000555555672ff2 gonzales`specialized Integrator.intersectOrInfiniteLights(ray=gonzales.Ray @ 0x00007ffff33fbb40, tHit=<unavailable>, bounce=0, l=$s8gonzales12BaseSpectrumVyAA6FloatXaGD @ 0x00007ffff33fb930, self=<unavailable>) at Integrator.swift:183:45 [opt]

Registers:

(lldb) register read
General Purpose Registers:
       rax = 0x00005555556dc4b0  gonzales`value witness table for gonzales.GeometricPrimitive
       rbx = 0x00000000000004f8
       rcx = 0xfffffffe00000000
       rdx = 0x00005555556dac58  gonzales`full type metadata for gonzales.Interaction + 8
       rdi = 0x0000555555715910
       rsi = 0x00005555556dc510  gonzales`full type metadata for gonzales.GeometricPrimitive + 8
       rbp = 0x00007ffff33fb670
       rsp = 0x00007ffff33fb568
        r8 = 0x000055555570cee0
        r9 = 0x00007ffff724d5a8
       r10 = 0x0000000100000000
       r11 = 0x088f1d98e3106f56
       r12 = 0x0000000000000000
       r13 = 0x00007ffff33fb670
       r14 = 0x0000000000000000
       r15 = 0x00005555556dc510  gonzales`full type metadata for gonzales.GeometricPrimitive + 8
       rip = 0x00007ffff7016a90  libswiftCore.so`swift_release
    rflags = 0x0000000000000202
        cs = 0x0000000000000033
        fs = 0x0000000000000000
        gs = 0x0000000000000000
        ss = 0x000000000000002b
        ds = 0x0000000000000000
        es = 0x0000000000000000

Did you try adding the @_semantics("optremark") attribute to the function, and building in release mode?

2 Likes

swift_release gets the object to be released as its first parameter. The first parameter on ARM64 is stored in the x0 register and on Intel it's in rdi. You will need to be either on the call/bl instruction that calls swift_release or in one of the first instructions inside swift_release before rdi/x0 potentially get overridden inside swift_release itself.

So in LLDB, you could for example do this (for arm64, for Intel you'd need to replace $x0 by $rdi).

expr -l Swift -- let ptr = UnsafeRawPointer(bitPattern: `$x0`); print("pointer:", ptr); print("meta:", unsafeBitCast(ptr, to: UnsafePointer<UnsafeRawPointer>.self).pointee);   print(unsafeBitCast(ptr, to: AnyObject.self))

which will print you the pointer to the object, the pointer to its metadata and it also tries to print it as an AnyObject (which doesn't always work as it might not be an AnyObject).

For example this may print

(lldb) expr -l Swift -- let ptr = UnsafeRawPointer(bitPattern: `$x0`); print("pointer:", ptr); print("meta:", unsafeBitCast(ptr, to: UnsafePointer<UnsafeRawPointer>.self).pointee);   print(unsafeBitCast(ptr, to: AnyObject.self))
test.Foopointer: Optional(0x0000600000004010)
meta: 0x00000001000080d0
test.Foo
() $R6 = {}

You can then further inspect the metadata pointer using im lookup -a 0x00000001000080d0, for example:

(lldb) im lookup -a 0x00000001000080d0
      Address: test[0x00000001000080d0] (test.__DATA.__data + 64)
      Summary: type metadata for test.Foo
(lldb) 

Alternatively, you can try to go through po:

(lldb) po $x0
test.Foo

or via ObjC:

(lldb) expr -l ObjC -- [(NSObject *)$x0 description]
(NSTaggedPointerString *) $50 = 0x82c075840a0dc94e @"test.Foo"
9 Likes

Bonus LLDB tip: you should be able to use $arg1 rather than remember what register the first argument is passed in (as long as the argument is in fact in a register).

12 Likes

I tried this and found some helpful remarks:

Sources/gonzales/Accelerators/BoundingHierarchy.swift:41:43: remark: release of type '__ContiguousArrayStorageBase'
                if nodes.isEmpty { return interaction }
                                          ^
Sources/gonzales/Accelerators/BoundingHierarchy.swift:78:24: remark: release of type '__ContiguousArrayStorageBase'
                return interaction
                       ^

I also found out about the @_noAllocation attribute which immediately errors out if there is an allocation in the marked function which is also very helpful.

Thanks.

1 Like

That gives me:

(lldb) expr -l Swift -- let ptr = UnsafeRawPointer(bitPattern: `$rdi`); print("pointer:", ptr); print("meta:", unsafeBitCast(ptr, to: UnsafePointer<UnsafeRawPointer>.self).pointee);   print(unsafeBitCast(ptr, to: AnyObject.self))
pointer: Optional(0x0000555555715230)
meta: 0x00007ffff7251228
error: Execution was interrupted, reason: signal SIGSEGV: invalid address (fault address: 0x52).
The process has been returned to the state before expression evaluation.

And the following lookups return nothing:

(lldb) im lookup -a 0x00007ffff7251228
(lldb) im lookup -a 0x0000555555715230

which gives me:

(lldb) po $rdi
93824994071088

Is there any useful information?

Thanks for your explanations but they don't seem to work here. :confused:

Like this?

(lldb) po $arg1
93824994071088

What instruction are you on? The output of a dis and a bt might help to determine that.

dis:

libswiftCore.so`swift_release:
->  0x7ffff7016a90 <+0>:  movq   0x1531b1(%rip), %rax
    0x7ffff7016a97 <+7>:  leaq   0x1912(%rip), %rcx        ; __swift_release_
    0x7ffff7016a9e <+14>: cmpq   %rcx, %rax
    0x7ffff7016aa1 <+17>: jne    0x7ffff7016ae2            ; <+82>
    0x7ffff7016aa3 <+19>: testq  %rdi, %rdi
    0x7ffff7016aa6 <+22>: jle    0x7ffff7016acf            ; <+63>
    0x7ffff7016aa8 <+24>: movabsq $-0x200000000, %rcx       ; imm = 0xFFFFFFFE00000000 
    0x7ffff7016ab2 <+34>: movq   0x8(%rdi), %rax
    0x7ffff7016ab6 <+38>: addq   $0x8, %rdi
    0x7ffff7016aba <+42>: nopw   (%rax,%rax)
    0x7ffff7016ac0 <+48>: movq   %rax, %rdx
    0x7ffff7016ac3 <+51>: addq   %rcx, %rdx
    0x7ffff7016ac6 <+54>: js     0x7ffff7016ad0            ; <+64>
    0x7ffff7016ac8 <+56>: lock   
    0x7ffff7016ac9 <+57>: cmpxchgq %rdx, (%rdi)
    0x7ffff7016acd <+61>: jne    0x7ffff7016ac0            ; <+48>
    0x7ffff7016acf <+63>: retq   
    0x7ffff7016ad0 <+64>: cmpl   $-0x1, %eax
    0x7ffff7016ad3 <+67>: je     0x7ffff7016acf            ; <+63>
    0x7ffff7016ad5 <+69>: movq   %rax, %rsi
    0x7ffff7016ad8 <+72>: movl   $0x1, %edx
    0x7ffff7016add <+77>: jmp    0x7ffff7018400            ; bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1> >::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int)

bt:

* thread #2, name = 'gonzales', stop reason = breakpoint 1.1
  * frame #0: 0x00007ffff7016a90 libswiftCore.so`swift_release
    frame #1: 0x00005555556746af gonzales`specialized Integrator.estimateDirect(light=<unavailable>, interaction=$s8gonzales11InteractionVD @ 0x3cbc0b803d93417a, bsdf=gonzales.BSDF @ 0x00007ffff33fbb58, sampler=<unavailable>) at <compiler-generated>:0 [opt]
    frame #2: 0x00005555556737dd gonzales`PathIntegrator.getRadianceAndAlbedo(from:tHit:for:with:) [inlined] generic specialization <gonzales.PathIntegrator> of gonzales.Integrator.estimateDirect(light: gonzales.Light, atInteraction: gonzales.Interaction, bsdf: gonzales.BSDF, withSampler: gonzales.Sampler) throws -> gonzales.BaseSpectrum<Swift.Float> at Integrator.swift:0:56 [opt]
    frame #3: 0x00005555556737bd gonzales`PathIntegrator.getRadianceAndAlbedo(from:tHit:for:with:) at Integrator.swift:41:36 [opt]
    frame #4: 0x0000555555673720 gonzales`PathIntegrator.getRadianceAndAlbedo(ray=<unavailable>, tHit=<unavailable>, scene=gonzales.Scene @ 0x0000555555706a60, sampler=0x000055555570eaa0, self=<unavailable>) at PathIntegrator.swift:56:35 [opt]
    frame #5: 0x0000555555661974 gonzales`Tile.render(reporter:scene:sampler:camera:integrator:) [inlined] protocol witness for gonzales.Integrator.getRadianceAndAlbedo(from: gonzales.Ray, tHit: inout Swift.Float, for: gonzales.Scene, with: gonzales.Sampler) throws -> (radiance: gonzales.BaseSpectrum<Swift.Float>, albedo: gonzales.BaseSpectrum<Swift.Float>, normal: gonzales.Normal3<Swift.Float>) in conformance gonzales.PathIntegrator : gonzales.Integrator in gonzales at <compiler-generated>:0 [opt]
    frame #6: 0x0000555555661951 gonzales`Tile.render(reporter:scene:sampler:camera:integrator:) at Tile.swift:37:66 [opt]
    frame #7: 0x00005555556617a3 gonzales`Tile.render(reporter=(current = 120491, total = 262144, frequency = 10000), scene=gonzales.Scene @ 0x0000555555706a60, sampler=<unavailable>, camera=0x0000555555710fa0, integrator=(payload_data_0 = 0x0000000000000041, payload_data_1 = 0x00007ffff704dff0 libswiftCore.so`std::_Function_handler<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int), swift_getTypeByMangledNameInContext::$_7>::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), payload_data_2 = 0x00007ffff704de30 libswiftCore.so`std::_Function_handler<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int), swift_getTypeByMangledNameInContext::$_7>::_M_invoke(std::_Any_data const&, unsigned int&&, unsigned int&&), metadata = 0x00005555556dbce8, wtable = 0x00005555556dbd00 gonzales`type metadata for gonzales.PathIntegrator + 24), self=gonzales.Tile @ 0x00005587fbee4de0) at Tile.swift:12:48 [opt]
    frame #8: 0x000055555565e0df gonzales`Renderer.renderTile(tile=<unavailable>, self=<unavailable>) at Renderer.swift:34:33 [opt]
    frame #9: 0x000055555565e1bf gonzales`closure #1 in Renderer.renderAsync(tile:) at Renderer.swift:44:35 [opt]
    frame #10: 0x000055555565e1ab gonzales`closure #1 in Renderer.renderAsync(self=0x00005555557069c0, tile=<unavailable>) at Renderer.swift:57:42 [opt]
    frame #11: 0x000055555564b166 gonzales`thunk for @escaping @callee_guaranteed () -> () at <compiler-generated>:0 [opt]
    frame #12: 0x00007ffff7d63287 libdispatch.so`_dispatch_call_block_and_release + 7
    frame #13: 0x00007ffff7d75c40 libdispatch.so`_dispatch_worker_thread + 944
    frame #14: 0x00007ffff6090402 libc.so.6`start_thread(arg=<unavailable>) at pthread_create.c:442:8
    frame #15: 0x00007ffff611f590 libc.so.6`__clone3 at clone3.S:81

I tried to do one step (s) and then the above expression yielded:

(lldb) expr -l Swift -- let ptr = UnsafeRawPointer(bitPattern: `$rdi`); print("pointer:", ptr); print("meta:", unsafeBitCast(ptr, to: UnsafePointer<UnsafeRawPointer>.self).pointee);   print(unsafeBitCast(ptr, to: AnyObject.self))
pointer: Optional(0x000055555570eaa0)
meta: 0x00005555556ec398
gonzales.RandomSampler

So I guess it's the RandomSampler that's released?

I also tried the following (/rdi/arg1/):

(lldb) expr -l Swift -- let ptr = UnsafeRawPointer(bitPattern: `$arg1`); print("pointer:", ptr); print("meta:", unsafeBitCast(ptr, to: UnsafePointer<UnsafeRawPointer>.self).pointee);   print(unsafeBitCast(ptr, to: AnyObject.self))
pointer: Optional(0x000055555570eaa0)
meta: 0x00005555556ec398
gonzales.RandomSampler

I believe that's what was meant with $arg1, right?

2 Likes

Yes, it’s a RandomSampler that gets released. And correct that’s exactly how the $arg1 is used. On your platform that should have exactly the same output as with $rdi.

You might be able to just po $arg1. Reading the object type from an object by just dereferencing the first word in it is not portable, unfortunately.

Continuing after a "break swift_release" stops at:

libswiftCore.so`swift_release:
->  0x7ffff7016a90 <+0>:  movq   0x1531b1(%rip), %rax
    0x7ffff7016a97 <+7>:  leaq   0x1912(%rip), %rcx        ; __swift_release_
    0x7ffff7016a9e <+14>: cmpq   %rcx, %rax
    0x7ffff7016aa1 <+17>: jne    0x7ffff7016ae2            ; <+82>

Then:

(lldb) po $arg1
93824994058384

So that reveals not much.

And sometimes the other approach fails:

lldb) expr -l Swift -- let ptr = UnsafeRawPointer(bitPattern: `$arg1`); print("pointer:", ptr); print("meta:", unsafeBitCast(ptr, to: UnsafePointer<UnsafeRawPointer>.self).pointee);   print(unsafeBitCast(ptr, to: AnyObject.self))
error: Execution was interrupted, reason: signal SIGSEGV: invalid address (fault address: 0x10).
The process has been returned to the state before expression evaluation.

So it seems there is no way that always works.

Did the optimiser remarks not help?

Whether or not a release causes the object to be deallocated can sometimes only be determined at runtime, but you should be able to get insight in to which things get retained/released at what points in the program at compile-time.

Personally, setting breakpoints in swift_release would be my very last resort. It can work, but as you're seeing, fumbling around in the debugger is not always the best choice for productivity (especially when you get on to the next stage -- trying to rework your code to avoid that ARC overhead).

The optimiser remarks give me the following:

/home/gonsolo/work/gonzales/Sources/gonzales/Shape/Triangle.swift:311:54: remark: retain of type '__ContiguousArrayStorageBase'
                let worldInteraction = objectToWorld * localInteraction
                                                     ^
/home/gonsolo/work/gonzales/Sources/gonzales/Shape/Triangle.swift:325:17: remark: release of type '__ContiguousArrayStorageBase'
                )

To give some context: I have a reasonable quick renderer that spends about 15% of its time in retain/release cycles. About 95% of the time is spent in triangle intersection and stepping through the bounding hierarchy. I'm investigating whether I can get rid of all ARC traffic in what is basically two function calls (Triangle.intersect and BoundingHierarchy.intersect).

Right now it seems to be SurfaceInteraction and Transform matrices that have to be changed.

I'm trying basically three approaches at the moment:

  1. @_semantics("optremark")
  2. @_noAllocation
  3. Fire up lldb, run, stop in the middle, break on swift_release/retain and see what's going on.

Adding @_noAllocation gives the following error:

/home/gonsolo/work/gonzales/Sources/gonzales/Api/Options.swift:42:9: error: ending the lifetime of a value of type 'TriangleMesh' can cause a deallocation
        }

from this code:

  func getObjectToWorldFor(meshIndex: Int) -> Transform {
A>                let mesh = meshes[meshIndex]
                  let objectToWorld = mesh.getObjectToWorld()
                  return objectToWorld
          }

where meshes is an Array, TriangleMesh is a struct (tried final class to no avail) and objectToWorld is a private member of TriangleMesh (basically 16 Floats). The triangle mesh array is read-only at this stage and I wonder why the optimiser is not able to eliminate this deallocation.