acb's technical journal

Posts matching tags 'war stories'

2015/6/19

The case of the crashing Swift compiler

Recently, whilst preparing release 1.1 of the MPDluxe iOS app, I encountered an unwelcome surprise: the app itself had built and worked without problems in Debug mode, but as soon as I tried to Archive it (i.e., building it in Release mode, with compiler optimisations switched on), the Swift compiler would, at some stage, choke on something and die with a segmentation fault:

0  swift                    0x00000001029e82b8 llvm::sys::PrintStackTrace(__sFILE*) + 40
1  swift                    0x00000001029e8794 SignalHandler(int) + 452
2  libsystem_platform.dylib 0x00007fff88c53f1a _sigtramp + 26
3  libsystem_platform.dylib 0x0000000104b06000 _sigtramp + 2079006976
4  swift                    0x0000000102526d5a (anonymous namespace)::DCE::markControllingTerminatorsLive(swift::SILBasicBlock*) + 346
5  swift                    0x0000000102526ae9 (anonymous namespace)::DCE::markValueLive(swift::ValueBase*) + 201
6  swift                    0x00000001025262ff (anonymous namespace)::DCE::run() + 1983
7  swift                    0x00000001024cdf3e swift::SILPassManager::runFunctionPasses(llvm::ArrayRef) + 1310
8  swift                    0x00000001024ce9c9 swift::SILPassManager::runOneIteration() + 633
9  swift                    0x00000001024cd436 swift::runSILOptimizationPasses(swift::SILModule&) + 790
10 swift                    0x00000001022d18e7 frontend_main(llvm::ArrayRef, char const*, void*) + 4695
11 swift                    0x00000001022d04e6 main + 1814
12 libdyld.dylib            0x00007fff90c545c9 start + 1

A web search revealed that others had had similar problems with the compiler, also in the optimisation stage of a release build. Everybody seemed to have their own superstition on which ostensibly legal syntactic feature to avoid to appease the gremlins, mostly involving being more circumspect about optionals; their cases were sufficiently different that there was no one thing to search for and eliminate.

With that in mind, I turned to git bisect. In short, bisect is a subcommand of the git version control system which allows you to specify a last-known-good version of your code, and semi-automates a binary search between then and the current (presumably broken) version, narrowing down on exactly which change coincided with the code breaking. In bisect mode, git checks out a revision from the middle of the as yet unsearched range; you then test it (in my case, getting Xcode to do an Archive build). If it works, you enter the git bisect good command and it skips forward; if not, you enter git bisect bad and it skips backward. Do this for a few steps and soon you will have exactly which commit broke things. Anyway, I ran git bisect between the current HEAD and version 1.0, as below:

% git bisect start
% git bisect bad
% git bisect good v1.0
Bisecting: 51 revisions left to test after this (roughly 6 steps)
[79b5f750178d09e924da572a4d83fb66bc9bed61] Removed redundant output-related methods

It had checked out a version from the middle of the set of check-ins between then and now; I went back into Xcode, did a Clean and an Archive, and, sure enough, Segmentation Fault. So this one's bad, and it's time to skip back halfway to the beginning and try from there:

% git bisect bad
Bisecting: 25 revisions left to test after this (roughly 5 steps)
[b49c1128168bb844f3b944d6e502d98f27e64f14] Added title to Search view controller

I tried building this, and it worked; so this one's good. Now to skip forward, and so on for the next few steps:

% git bisect good                                
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[b112971ad7a21cb6c47358d4e316380df42f006f] Removed dead code
% git bisect good
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[524b442c6c6c3d6ea02edc1d3a56abe82170d213] Hived navigateToContainingFolderOfFile off into MPDNavigationController
% git bisect bad
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[06f9a82e169617aeebeb85122bc5829d3c43b6af] DraggableTableViewCell now has hidden drawer for buttons
% git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[fce75660b3dd40a21abc996c4ed5fc7400d59d49] Navigate to path from playlist now works, modulo tidying up/refactoring/aesthetic grace notes
% git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[eb5ee15b0520e56177d69745f615d8f1afaed930] Implemented “navigate to” hidden button in PlaylistTableViewCell

Surely enough, we had the offending commit soon enough:

% git bisect good
fce75660b3dd40a21abc996c4ed5fc7400d59d49 is the first bad commit
commit fce75660b3dd40a21abc996c4ed5fc7400d59d49
Author: acb 
Date:   Fri Jun 5 17:38:33 2015 +0100
  
    Navigate to path from playlist now works, modulo tidying up/refactoring/aesthetic grace notes

The commit itself was a fairly small one, and (after going back to the latest revision), commenting out parts of the code added in it and trying to build soon found what the Swift optimiser was having trouble with.

The code in question deals with the app's Playlist view, a UITableView which shows a playlist of songs queued up for playing; each of these is represented by a UITableViewCell subclass named PlaylistTableViewCell. One of the added features of the app is a hidden button on the playlist items, allowing the user to go to a view of the directory the song is in (i.e., to browse the rest of the album it's on). This was implemented by having an optional closure on the PlaylistTableViewCell, which would be called when the button was tapped. This closure captured various relevant data (such as the directory path to go to) and was set when the table view cell was filled in; a greatly simplified version looks a little like:

class PlaylistTableViewCell {
   ...
    var onNavTo:(()->())? = nil

    func navToPressed(sender: AnyObject) {
        onNavTo?()
    }
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
 ...

  cell.onNavTo = {
     // code for navigating to item['file']
      if let fpath = item["file"] as? String {
          navigateToContainingFolderOfFile(fpath)
      }
  }

Which is perfectly legal Swift, though, with the Swift 1.2 optimising compiler, that plus £2.80 gets one a cup of coffee. Anyway, to appease the gremlins, I inlined the code, replacing the closure with an optional string:

class PlaylistTableViewCell {
   ...
    var navToPath: String? = nil

    func navToPressed(sender: AnyObject) {
        if let fpath = navToPath {
            navigateToContainingFolderOfFile(fpath)
        }
    }
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
 ...
    cell.navToPath = item["file"] as? String
}

From there onward, the code built and worked without a problem.

The moral of this story is that, while the Swift toolchain is a lot more stable than before, it (as of 1.2, the current production release) still has a few bugs, some of which may appear at various times and, through no fault of one's own, derail an ostensibly perfectly valid project. Working around them is a combination of detective work and making the right offerings to the trickster gods of the toolchain; and that git bisect is your friend.

debugging git ios swift war stories 0

This will be the comment popup.
Post a reply
Display name:

Your comment:


Please enter the text in the image above here: