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.
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.