Erratic dead code elimination?

I noticed (in optimized builds) that dead code stripping didn't seem to be performed for sqrt, while it did when replacing sqrt with eg exp.

What's the reason for this?


Here's a little program to demonstrate it:

import Cocoa // (For CACurrentMediaTime)

struct DeadCodeEliminationTester {
    var usedValue: UInt64 = 0 // Will be printed at the end. Non-ignored results are xored into this value.

    // Function that returns the time (in seconds) it takes to make a
    // hundred million calls to fn, using or ignoring the result of the calls.
    mutating func timeLotsOfCallsTo(fn: (Double) -> Double, useResults: Bool) -> Double {
        let startTime = CACurrentMediaTime()
        var temporaryUsedValue: UInt64 = 0
        for i in 0 ..< 100_000_000 {
            let doubleResult = fn(Double(i))
            temporaryUsedValue ^= unsafeBitCast(doubleResult, UInt64.self)
        }
        let stopTime = CACurrentMediaTime()
        if useResults { usedValue ^= temporaryUsedValue }
        return stopTime - startTime
    }

    mutating func testFunction(fn: (Double) -> Double, label: String) {
        let timeUsing = timeLotsOfCallsTo(fn, useResults: true)
        let timeIgnoring = timeLotsOfCallsTo(fn, useResults: false)
        let ratio = timeUsing / timeIgnoring // Will be close to 1.0 if no dead code elimination was performed, big otherwise.
        let clearText = ratio > 100.0 ? "Dead code eliminated!" : "    No dead code?    "
        print(label, clearText,
            String(format: "( timeUsing: %.10f | timeIgnoring: %.10f | ratio: % 12.2f )", timeUsing, timeIgnoring, ratio))
    }

    func useUsedResults() {
        print("\n(Printing this: \(String(usedValue, radix: 36)) just to make extra sure the used results are actually considered used.)")
    }
}

var t = DeadCodeEliminationTester()
t.testFunction(exp,   label: "exp  ")
t.testFunction(abs,   label: "abs  ")
t.testFunction(cos,   label: "cos  ")
t.testFunction(log,   label: "log  ")
t.testFunction(round, label: "round")
t.testFunction(sqrt,  label: "sqrt ")
t.testFunction(tan,   label: "tan  ")
t.testFunction(acos,  label: "acos ")

t.useUsedResults()


Compiling and running that with Xcode 7.1 beta 2:

$ swiftc -Ounchecked test.swift
$ ./test
exp   Dead code eliminated! ( timeUsing: 0.3607922090 | timeIgnoring: 0.0000000300 | ratio:  12023982.45 )
abs   Dead code eliminated! ( timeUsing: 0.0676673420 | timeIgnoring: 0.0000000850 | ratio:    796107.57 )
cos   Dead code eliminated! ( timeUsing: 2.4501839200 | timeIgnoring: 0.0000000330 | ratio:  74239575.35 )
log   Dead code eliminated! ( timeUsing: 0.6963752020 | timeIgnoring: 0.0000000360 | ratio:  19342982.82 )
round Dead code eliminated! ( timeUsing: 0.3099052790 | timeIgnoring: 0.0000000330 | ratio:   9390003.80 )
sqrt      No dead code?     ( timeUsing: 0.4656082430 | timeIgnoring: 0.4724121530 | ratio:         0.99 )
tan       No dead code?     ( timeUsing: 2.9236285690 | timeIgnoring: 2.9138148010 | ratio:         1.00 )
acos      No dead code?     ( timeUsing: 0.4560453720 | timeIgnoring: 0.4093574440 | ratio:         1.11 )

(Printing this: 1eazf78rd4j7q just to make extra sure the used results are actually considered used.)



AFAICS, the only thing that separates those two groups of example functions is that when you cmd-click on them, you are taken to either

Darwin

or

Darwin -> C -> math


Don't know what to make of it though. Filed 22828651 anyway.

Answered by in 66763022

The issue here has to do with information propagated to the optimizer about the properties of these functions.


The functions that are dead code eliminated are implemented via LLVM intrinsics implying that the optimizer has information about their properties. On the other hand, functions that are /not/ dead code eliminated are represented via calls to the relevant library. Currently we do not propagate information about those into the optimizer. So the optimizer is conservative and assumes that sqrt could do "evil" things.

Accepted Answer

The issue here has to do with information propagated to the optimizer about the properties of these functions.


The functions that are dead code eliminated are implemented via LLVM intrinsics implying that the optimizer has information about their properties. On the other hand, functions that are /not/ dead code eliminated are represented via calls to the relevant library. Currently we do not propagate information about those into the optimizer. So the optimizer is conservative and assumes that sqrt could do "evil" things.

Ok, thanks.

Erratic dead code elimination?
 
 
Q