I have a DSL library that I'm writing that looks something like this in use:
// Client
struct MyDataFilter: Filter {
@FilterBodyBuilder func body() -> some FilterBody {
filter("dogs") {
filter("pomeranians") {
group("age") {
// ...
}
}
}
}
}
Each filter()
method returns a FilterNode<T>
type that statically encodes what the filter tree looks like, which helps me know some things about the tree ahead of time. The closure that filter()
accepts never escape the stack, and are always run immediately (or skipped.). Basically:
// Library
// Empty type, literally only used to encode static type information.
public struct FilterNode<T> {
static func inspect() -> Inspection { }
}
extension Filter {
public func filter<T: FilterBody>(_ filter: String, @FilterBodyBuilder body: () -> T) -> FilterNode<T> {
withCurrentData {
data
if data.matches(filter) {
body()
}
}
return .init()
}
}
My real code involves quite a bit more logic than this, but in essence, that body
closure gets passed down a call chain of other with
-style functions that decide if it gets executed or not.
Depending on the size of the data, the Filter.body
method gets called a lot. Potentially millions of times. When profiling, I notice a fun little friend eating up much of the run time, even with very shallow (one-level Filter.body
-ies): ___chkstk_darwin
.
In my case — thanks to a suggestion from ChatGPT — I notice that sprinkling @inline(never)
down the call stack reduces the time spent in ___chkstk_darwin
, but doesn't eliminate it. Shortening filter
to just immediately call the closure it's handed does eliminate it, but obviously that's not realistic.
Hopefully someone knowledgeable here can shed light on:
- Exactly what
___chkstk_darwin
is meant to do in this case, - Why Swift is choosing to emit it here — what are the thresholds?, and
- How I can reduce (or eliminate) the amount of time my code is spending it