Hello, Swift Community!
Pitch
I'd like to make a pitch for adding two attributes to globals and functions in Swift that can be used to control exactly how are symbols (at the linker level) exported. Both the attributes have existed in the C/C++/ObjC world for a long time:
@used
... the analogue to GCC's/Clang's__attribute__((used))
@section("section_name")
... the analogue to GCC's/Clang's__attribute__((section("section_name")))
Motivation
The motivation has two goals:
-
To provide low-level building blocks for building more high-level APIs, e.g. "linker sets" (see below) or custom per-type metadata as described in SE-0385 (swift-evolution/proposals/0385-custom-reflection-metadata.md at main · apple/swift-evolution · GitHub). Though this pitch/proposal doesn't actually try to add or design the high-level APIs, just provide a path towards unblocking the design of them, separately.
-
To provide a low-level mechanism for systems programming use cases that are bespoke cases for concrete systems and building a generally reusable high-level API doesn't make sense (and the project author is free to build such a high-level API as an internal mechanism in their project).
The "linker set" mechanism is is an approach that Swift is already using: Nearly any kind of compiler-emited metadata is put into a specifically-named section in the binary and given a fixed-layout record. Then when we want to do a lookup for some information -- say, to find the protocol conformances in the binary -- we ask the loader (dyld on Darwin) to give us the start/end address of that section in each of the loaded images, and then you can iterate through all of the records in those sections. It's also possible to extract some of that metadata from outside the process, or dig it out of the binary itself. We do this with the existing reflection library in, e.g., swift-inspect and swift-reflection-dump.
This pitch suggests we add the ability into the Swift language to express the first part of this mechanism: Placing fixed-layout records into specifically-named sections.
Proposal
Some of this is already implemented under a feature flag as underscored attributes (@_section
, @_used
) in main via https://github.com/apple/swift/pull/65901. In summary:
@used
attribute that flags as a global variable or a top-level function as "do not dead-strip" via llvm.used, roughly the equivalent of__attribute__((used))
in C/C++.@section("...")
attribute that places a global variable or a top-level function into a section with that name, roughly the equivalent of__attribute__((section("...")))
in C/C++.
The annotations can only be applied to globals that are guaranteed to end up as "statically initialized" (instead of lazily initialized via a init_once runtime call), because the annotation doesn't make sense otherwise. This opens an interesting question: What expressions are guaranteed to be "statically initialized" when used to initialize a global? The proposal is to start with a very basic set of expressions, and improve on that in the future. As of today, the mandatory optimizations pipeline already makes integer literals, tuples of them, and simple arithmetic expressions to be "statically initialized" and we can cleanly reject the compilation at the end of the SIL pipeline if there's any global with a @section
attribute that's not statically initialized. Then as follow-up improvements, we should look at allowing POD struct types to also be handled in the mandatory optimizations pipeline and allowed to be used with @section
.
While outside of the scope of this pitch, here's a sketch of what the runtime side of a "linker set" API could look like:
// in Module1
@used @section("__DATA,mysection") private let my_entry: Int = ...
// in Module2
@used @section("__DATA,mysection") private let my_entry: Int = ...
for entry in SwiftRuntime.section("__DATA,mysection", as: Int.self) { // this uses the loader's APIs to locate and iterate over the section
...
}
And eventually, it might make sense to wrap this into a macro-based solution so that we don't expose the low-level attributes at all:
@LinkerSet(name: "myLinkerSet") private let myEntry: Int = 42
for entry in SwiftRuntime.linkerSet("myLinkerSet", as: Int.self) {
...
}
Or, in the case we want to attach metadata to types (as motivated by SE-0385):
@Registered(name: "My Favorite Type") // this creates a hidden global in a named section
class MyType { }
for regType in allRegisteredTypes { // queries over the entries in the section
...
}
Thoughs?