I've noticed this runtime stack protection happening even in places where I would expect the compiler to provide things safe statically.
I'm using withUnsafeBytes(of: self)
to work around the "outlined init with take" issue. It's important that I do that, because just applying it to one tiny enum improved the performance of lots of complex processing by up to 24%. Obviously I don't like to write this kind of code, and I don't recommend anybody does that by default, but it's a work-around for a demonstrable issue.
And it's still an issue, as of the latest nightly:
enum MyEnum {
case one
case four
}
struct HasAnEnum {
var something: String
var somethingTwo: String
var value: MyEnum?
}
func test(_ input: HasAnEnum) -> Int {
// This load results in very expensive runtime calls!
let x = input.value
if case .four = x { return 4 }
return -1
}
func test2(_ input: HasAnEnum) -> Int {
// Super ugly, but we can wrap this in a computed property to contain it.
// It's so much faster.
let x = withUnsafeBytes(of: input) {
let offset = MemoryLayout<HasAnEnum>.offset(of: \.value)!
return $0.load(fromByteOffset: offset, as: MyEnum?.self)
}
if case .four = x { return 4 }
return -1
}
Unfortunately, I'm seeing that this load requires runtime stack protection checks. It seems strange - we get the offset from MemoryLayout
, and the compiler constant-folds that (not seeing any runtime calls), and there doesn't seem to be any complex inter-procedural stuff going on. I'd expect the compiler to know that this is in-bounds.
Is this something that could reasonably be improved? Or does this involve some complex analysis that I'm not appreciating?