Call proper function based on parameter's dynamic type

Certainly, if the compiler can see the type passed to myDump at the point of the call.

Many of these questions can be easily answered just by compiling programs; I use this command to dump readable assembly for a single function or method that I call testMe:

swiftc -O -S sourcefile.swift \ 
  | sed -e '/testMe/,/retq/!d;/retq/q' \
  | xcrun swift-demangle

(The second line strips everything between the first mention of testMe and its return instruction. The third line demangles names to make things readable)

Interestingly, Swift's optimizer forgets important type information when faced with an Any, as you can see by running it on this program (I removed printing and string interpolation for simplicity):

protocol Dumpable {
  func dump() -> String
}

extension Int: Dumpable {
  @inlinable
  func dump() -> String { "int" }
}

extension String: Dumpable {
  func dump() -> String { "string" }
}

func myDump(_ value: Any) -> String {
  return !(value is Dumpable) ? "any" : (value as! Dumpable).dump()
}

func testMe() -> String {
  return myDump(3)
}

In the result you can see the creation of boxed existentials and calls to the Swift runtime's dynamic casting machinery.

Assembly output
	.private_extern	x.testMe() -> Swift.String
	.globl	x.testMe() -> Swift.String
	.p2align	4, 0x90
x.testMe() -> Swift.String:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	pushq	%r15
	pushq	%r14
	pushq	%r13
	pushq	%r12
	pushq	%rbx
	subq	$104, %rsp
	.cfi_offset %rbx, -56
	.cfi_offset %r12, -48
	.cfi_offset %r13, -40
	.cfi_offset %r14, -32
	.cfi_offset %r15, -24
	movq	type metadata for Swift.Int@GOTPCREL(%rip), %rax
	movq	%rax, -48(%rbp)
	movq	$3, -72(%rbp)
	leaq	-144(%rbp), %r15
	leaq	-72(%rbp), %rdi
	movq	%r15, %rsi
	callq	outlined init with copy of Any
	leaq	-112(%rbp), %r12
	leaq	demangling cache variable for type metadata for x.Dumpable(%rip), %rdi
	callq	___swift_instantiateConcreteTypeFromMangledName
	movq	%rax, %r14
	movq	type metadata for Any@GOTPCREL(%rip), %rbx
	addq	$8, %rbx
	movl	$6, %r8d
	movq	%r12, %rdi
	movq	%r15, %rsi
	movq	%rbx, %rdx
	movq	%rax, %rcx
	callq	_swift_dynamicCast
	testb	%al, %al
	je	LBB10_1
	leaq	-112(%rbp), %rdi
	callq	___swift_destroy_boxed_opaque_existential_0
	leaq	-144(%rbp), %r15
	leaq	-72(%rbp), %rdi
	movq	%r15, %rsi
	callq	outlined init with copy of Any
	leaq	-112(%rbp), %r12
	movl	$7, %r8d
	movq	%r12, %rdi
	movq	%r15, %rsi
	movq	%rbx, %rdx
	movq	%r14, %rcx
	callq	_swift_dynamicCast
	movq	-88(%rbp), %rbx
	movq	-80(%rbp), %r14
	movq	%r12, %rdi
	movq	%rbx, %rsi
	callq	___swift_project_boxed_opaque_existential_1
	movq	%rax, %r13
	movq	%rbx, %rdi
	movq	%r14, %rsi
	callq	*8(%r14)
	movq	%rax, %rbx
	movq	%rdx, %r14
	movq	%r12, %rdi
	callq	___swift_destroy_boxed_opaque_existential_0
	jmp	LBB10_3
LBB10_1:
	movabsq	$-2089670227099910144, %r14
	movl	$7958113, %ebx
LBB10_3:
	leaq	-72(%rbp), %rdi
	callq	___swift_destroy_boxed_opaque_existential_0
	movq	%rbx, %rax
	movq	%r14, %rdx
	addq	$104, %rsp
	popq	%rbx
	popq	%r12
	popq	%r13
	popq	%r14
	popq	%r15
	popq	%rbp
	retq

However, you only need to write myDump as a generic to get the performance back:

func myDump<T>(_ value: T) -> String {
  return !(value is Dumpable) ? "any" : (value as! Dumpable).dump()
}

which results in:

	.private_extern	x.testMe() -> Swift.String
	.globl	x.testMe() -> Swift.String
	.p2align	4, 0x90
x.testMe() -> Swift.String:
	pushq	%rbp
	movq	%rsp, %rbp
	movabsq	$-2089670227099910144, %rdx
	movl	$7630441, %eax
	popq	%rbp
	retq

Since the generic formulation and the Any formulation are semantically equivalent, there's no reason the optimizer couldn't have made the former as efficient as the latter; it just didn't.

4 Likes