Is the KeyPath access happening in the same module that declares Dimensions
?
I want to make sure that I'm answering the right question. Do you mean,
in the same package, or in the same source file?
The access happens in the package that declares Dimensions, however, the
in-package declarations and uses are spread over a few files.
My app imports the package and calls API functions with
KeyPath<Dimensions, Int>
s.
As you expected, it just loads the value directly:
output.test(output.Dimensions) -> Swift.Int:
mov rax, rdx
ret
Thanks, I should have thought of godbolt!
Looks like the compiler optimized the constant-keypath access,
dimensions[keyPath: \.steps]
-> dimensions.steps
.
Below, I have introduced a level of indirection:
func inner(_ dimensions: Dimensions, _ path: KeyPath<Dimensions, Int>) -> Int {
return dimensions[keyPath: path]
}
func outer(_ dimensions: Dimensions) -> Int {
return inner(dimensions, \.steps)
}
public struct Dimensions {
public var indices: Int {
return jots - steps
}
public var boundaries: Int {
return steps - units
}
public let units: Int
public let jots: Int
public let steps: Int
public static var zero: Dimensions {
return Dimensions()
}
public init(indices: Int = 0, boundaries: Int = 0,
units: Int = 0) {
self.units = units
self.jots = indices + boundaries + units
self.steps = boundaries + units
}
}
outer
still optimizes well:
output.outer(output.Dimensions) -> Swift.Int:
mov rax, rdx
ret
For inner
, however, the compiler generates a swift_getAtKeyPath
call:
output.inner(output.Dimensions, Swift.KeyPath<output.Dimensions, Swift.Int>) -> Swift.Int:
sub rsp, 40
mov qword ptr [rsp + 16], rdi
mov qword ptr [rsp + 24], rsi
mov qword ptr [rsp + 32], rdx
lea rax, [rsp + 8]
lea rdi, [rsp + 16]
mov rsi, rcx
call swift_getAtKeyPath@PLT
mov rax, qword ptr [rsp + 8]
add rsp, 40
ret
Following the ABI specification, I would hope for the compiler to emit a
fast path resembling this C code in concept:
int
inner(struct Dimensions dimensions, struct KeyPath_Dimensions_Int *path)
{
int result;
// fast path: stored property, only one key path component
if (path->keypath_object_header.kvc_string == NULL &&
path->keypath_buffer_header.buffer_size == sizeof(path->component[0]) &&
path->component[0].header.kind == stored)
return (int *)((char *)&dimensions + path->component[0].payload);
// slow path: computed property or other
swift_getAtKeyPath(dimensions, path, &result);
return result;
}
I believe that fast path would go a long way toward reducing the keypath
overhead in my program.
Since the compiler has full knowledge about the shape of struct Dimensions
, I believe that in principle some further optimization is
possible:
int
inner(struct Dimensions dimensions, struct KeyPath_Dimensions_Int *path)
{
int result;
// fast path: stored property, only one key path component
if (path->keypath_object_header.kvc_string == NULL &&
path->component[0].header.kind == stored)
return (int *)((char *)&dimensions + path->component[0].payload);
if (path->keypath_object_header.kvc_string == NULL &&
path->component[0].header.kind == stored) {
// slower path: computed property
((struct computed_component *)&path->component[0])->getter(
&dimensions, &result);
return result;
} else {
// slowest path: all other cases
swift_getAtKeyPath(dimensions, path, &result);
return result;
}
}
Dave