Convincing optimizer to simplify 'as AnyObject' in specialized generic functions

Hello everyone,

I’ve noticed an optimization gap when casting a value to AnyObject inside a generic function compared to a direct cast at the call site. Even when the generic function is specialized for a final class, the compiler still emits a call to the bridging runtime.

func cast<T>(_ v: T) -> AnyObject {
  v as AnyObject
}

final class C: AnyObject {}

func test1(_ v: C) -> AnyObject {
  cast(v)
}

func test2(_ v: C) -> AnyObject {
  v as AnyObject
}
output.test1(output.C) -> Swift.AnyObject:
        sub     sp, sp, #32
        stp     x29, x30, [sp, #16]
        add     x29, sp, #16
        ldr     x1, [x0]
        str     x0, [sp, #8]
        add     x0, sp, #8
        bl      ($ss27_bridgeAnythingToObjectiveCyyXlxlF)
        ldp     x29, x30, [sp, #16]
        add     sp, sp, #32
        ret

output.test2(output.C) -> Swift.AnyObject:
        b       swift_retain

(Compiler Explorer)

Is it possible to "convince" the optimizer that a specialized version of cast<T> where T: AnyObject doesn't need the full bridging logic?

I can workaround it with an overload - func cast<T: AnyObject>(_ v: T) -> AnyObject. But in the actual code, the cast happens several calls deeper than the functions receiving the values, so I'd have to write a considerable amount of overloads.

1 Like

strangely, it seems this may work, at least with the sample:

func cast<T>(_ v: T) -> AnyObject {
    return v as! AnyObject
}

you do get a warning though, unfortunately...

It doesn't call $ss27_bridgeAnythingToObjectiveCyyXlxlF but it calls swift_dynamicCast instead. Or am I missing something?

ah yeah, it does. i thought you just wanted to eliminate that particular runtime call.

i wonder if it has to do with objc interop... if i build the sample on darwin, the runtime calls seem to be elided.

1 Like

Great observation, indeed, they are elided on darwin :thinking:

1 Like