Programming

How I escaped a Swift compiler hang

I am in the unfortunate situation of 1) needing to use Xcode 8, 2) needing to use the Address Sanitizer to track down a bug, and 3) compiling against Swift 2.3.

Screen Shot 2016-09-12 at 10.03.01 AM (2).png
Xcode really knows how to ruin your day.

Well, I need to move to Swift 3 eventually, why not today?  Migrate, wait, edit, build, edit, build… Things are going okay.  Then… … … … … huh, build has taken 10 minutes and is still compiling swift.  No error messages, no log output, nothing.

This is not a situation I was really ready for.  I’m used to Swift failing at the drop of a hat, but failing to fail is new to me.  A large part of me was ready to just revert back to Xcode 7 but I had come to far to quit now.

So I started randomly guessing

The first thing I did was look for something to check type solver performance, since Swift is notoriously slow when dealing with multiple types.  I found the flags -Xfrontend -debug-time-function-bodies in Nick O’Neill’s post on improving compile time.   Unfortunately, because the build was hanging it wasn’t printing out the logs.

I then noticed that these were files that were now referencing Objective-C lightweight generics.  I thought that maybe something was going wrong because I didn’t implement classForGenericArgumentAtIndex:.  After a lot of digging, it looks like although it was included in the accepted proposal, it isn’t used in Swift 3 at all.  That was just a shot in the dark anyway.

Then I remembered I’m a programmer

After a few failed guessed, I remembered that a compiler is just a program and I can debug programs.  Specifically, I could connect LLDB to the hanging process and see what was going on.

(lldb) process attach --name swift
Process 83048 stopped
* thread #1: tid = 0x13e334f, 0x0000000102f8136d swift`swift::constraints::ConstraintSystem::compareSolutions(swift::constraints::ConstraintSystem&, llvm::ArrayRef, swift::constraints::SolutionDiff const&, unsigned int, unsigned int) + 1213, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x0000000102f8136d swift`swift::constraints::ConstraintSystem::compareSolutions(swift::constraints::ConstraintSystem&, llvm::ArrayRef, swift::constraints::SolutionDiff const&, unsigned int, unsigned int) + 1213
swift`swift::constraints::ConstraintSystem::compareSolutions:
->  0x102f8136d <+1213>: movq   -0xa8(%rbp), %rcx
    0x102f81374 <+1220>: movq   (%rax,%rcx,8), %rdi
    0x102f81378 <+1224>: movq   %rdi, -0x90(%rbp)
    0x102f8137f <+1231>: movq   0x8(%rax,%rcx,8), %r13

And there it was.  Every time I hooked into swift, paused or resumed, it was hanging out in. ConstraintSystem::compareSolutions.

Debugging the constraint system

Now that I knew what was failing, I went to the Swift source repository for the file to poke around.  Yay open source!  I didn’t even have to go that far into the source code because  the very first line said that if the DebugConstraintSolver option was true it would dump a bunch of useful information to the logs.  Searching for where this was set lead to the front-end option flags file, and the flag -Xfrontend -debug-constraints.  Adding this to to my build immediately caused the compiler to crash on one of the source files. Progress!

Aside: Since the build was still hanging even after failing on one file and Xcode logs are a terrible place to try to read command output, I copied and pasted the program arguments from Xcode into the terminal. This was actually a huge win because 1) it totally worked, 2) it was easier to read, 3) I could tweak the arguments to add and remove flags, and 4) I didn’t have to wait for the build process.

The crash told me roughly which line of code was causing the error and at the bottom had a decent message: While type-checking setter for postIds at <invalid loc>.  I was fortunately able to comment it out for now, which resolved the constraint issue and got me to other issues in the file.

But this was just a red herring.  The build was still hanging.  I tried a few more files until I found the exact problem.  The same messages spewing out of the console over and over preceded by messages about string interpolation.

Screen Shot 2016-09-12 at 3.13.30 PM.png
Looks like an infinite loop.

This lead me to the offending code:

SiteCache.shared().loadItems(options: [], success: { sites in
    DDLogInfo("Site cache loaded \(sites.count) sites")
}, failure: { error in
    DDLogError("Failed to load site cache with error: \(error.domain) \(error.code) \(error.localizedDescription)")
}, queue: nil)

Since the last readable message said it was working on string interpolation, in the error block.  I replaced the long error message with a simple one, and magically it worked!

Letting through a single error parameter got me the error I had needed all along: Cannot convert value of type ‘($T1) -> $T0) to expected argument type ((Error) -> Void)?

And the problem was…

Type conversion.

Before Swift 3, loadItems(options:success:failure:) took a ((NSError) -> Void)?.  After Swift 3, it took a ((Error) -> Void)?.  The compiler saw a function with an unspecified input type that should be an Error but with three properties domain, code, and localizedDescription.  It then tried to make sense of what type the function could be, got into a bad state, and got stuck looping forever.

The solution on my end was to do a shadow cast to get it back to the right type:

SiteCache.shared().loadItems(options: [], success: { sites in
    DDLogInfo("Site cache loaded \(sites.count) sites")
}, failure: { error in
    let error = error as NSError
    DDLogError("Failed to load site cache with error: \(error.domain) \(error.code) \(error.localizedDescription)")
}, queue: nil)

You can try it out for yourself with sample code from the bug report.

Conclusion

I’m now back on my way to converting my app to Swift 3.  I still have the compiler crash issue to resolve but issues with errors are easier than issues with no errors.  I guess the biggest takeaway is that the Swift compiler is not a black box.  You can debug it, add a variety of logging flags, and really track down what caused an issue.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s