SE-0492: Section Placement Control

Hi everyone,

The review of SE-0492 "Section Placement Control" begins now and runs through October 6, 2025.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager via the forum messaging feature. When contacting the review manager directly, please keep the proposal link at the top of the message.

Try it out

Development toolchains built from main have this feature available as an experimental feature with the name SymbolLinkageMarkers. The attribute names also have underscores, i.e., @_section and @_used, as does the #if check (_objectFileFormat).

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at https://github.com/swiftlang/swift-evolution/blob/main/process.md .

Thanks for contributing to Swift!

Doug Gregor
Review Manager

15 Likes

Overall, I think that this is a positive change. Some comments:

  1. I don't think that we should use the @section(".mysection"). In particular, the . should be omitted as that is technically reserved for the system and will also prevent the synthesis of the start and stop markers. As such, we should use @section("mysection").
  2. I don't think we should be indicating the limitation to builtin types but rather limit to "standard types". That is, Int is not a builtin type, but rather a standard type. Builtin.Int32 (referencing the i32 type in LLVM IR) is different.
  3. I am still not a fan of the spelling of objectFileFormat. I think that objectformat or objectFormat (or even better ObjectFormat, alas we do not use Pascal Case) is slightly better.

Yes

It does mostly fit well with the feel and direction of Swift. I think that the piece which stands out the most of the change is the objectFileFormat spelling.

This feels a bit more cumbersome than C/C++ where #if __ELF__, #if __APPLE__ && __MACH__ can identify the file format more conveniently. zig has comptime { builtin.target.ofmt } which feels a bit more structured. rust and go require inference via the target (e.g. cfg!, objabi.GOOS) which feel a bit more limiting. The recommendation here is more flexible allowing scaling to other environments, e.g. aarch64-apple-none-coff which would be useful for targeting environments such as UEFI.

1 Like

I think this is a super-useful feature, but I think the case for the alternative @section(ELF: "...", MachO: "...", COFF: "...") design, or something similar, might be stronger than the proposal suggests.

Now, before I start, I have to admit that I have limited experience with section names, and mostly in the context of Swift IRGen rather than directly in source code. However, my understanding is that section names are usually specific to the object file format and there's little chance that the same name will work correctly in different formats. If I've misunderstood the situation, take this post with a grain of salt.

(I should also admit that I didn't follow the pitch thread and I don't know if these points have been thoroughly discussed already.)

If I'm right that section names are not usually portable, there are four three reasons why I think a more structured design would be better:

  1. A @section attribute is kind of meaningless without knowing the object format you're targeting. You can't tell if it's correct or how it will be interpreted without referencing information that is either provided by an enclosing #if condition or is implicit in the project configuration. It would be better to have the object format right there, adjacent to the name, whenever you read or write an @section attribute.

  2. Since section names aren't portable, I think that perhaps the best default would be that when you compile a project with an object format it was not designed for, the existing @section attributes become inert until you add more information to them. That's not what happens with the current design.

  3. @section attributes in single-platform codebases are slightly burdened by having to write a section name that could have been implied. However, @section attributes in multi-platform code are much more heavily burdened by a design where you have to use #if conditions to select the right attribute. Like, we're talking an extra identifier and a colon in single-platform code vs. a multi-line conditional structure in multi-platform code. Multi-platform uses of @section would have to be really rare for that trade-off to balance.

  4. As a practical matter, I'm not sure if macros will be able to generate correct @section attributes if their names need to vary by platform or object format. I certainly don't see anything in MacroExpansionContext that can evaluate conditional compilation expressions, and in a quick search I don't see any test cases where a macro adds a #if condition to a macro expansion. (I could be wrong—maybe this works fine. I just can't quickly confirm it.)

Overall, it's as though we decided to design an @available which only mentions the platform version, not the name:

#if os(macOS)
  @available(15)
#elseif os(iOS) || os(tvOS)
  @available(18)
#elseif os(watchOS)
  @available(11)
#elseif os(visionOS)
  @available(2)
#endif

This is totally a workable design, and a very convenient one for single-platform code! But it artificially separates two concepts that are pretty tightly coupled, it behaves poorly when you want to turn a single-platform codebase into a multi-platform one, it levies a high syntax penalty on uses of the attribute in multi-platform codebases, and it raises the same questions about conditional compilation in macros.

I think we were right to include platform names in @available attributes, and I think we might also be right to include object format names in @section attributes.

Update: Thanks to @grynspan for confirming that macro expansions can use #if successfully.

7 Likes

The difference here is that there is no universal catch all. The @available attribute makes it available to all. Consider a package which does not support MachO, what would the spelling for the section be? I think that the separation as is makes much more sense here.

IIUC, this is a just comment on the sample code snippets using .mysection, and you’re not actually proposing that dot-prefixed section are disallowed from being used in the @section attribute, correct? If yes, then I agree – it’s a badly chosen sample code, and I’ll fix the proposal’s text.

  • I don't think we should be indicating the limitation to builtin types but rather limit to "standard types". That is, Int is not a builtin type, but rather a standard type. Builtin.Int32 (referencing the i32 type in LLVM IR) is different.

Agree, badly chosen wording on my side.

We need to support the . prefixed sections as well, so, we can’t drop support for them. But, we should ensure that we don't put the . in the examples.

Sections can be named arbitrarily in all of PE/COFF, Mach-O, and ELF. Whether the names are meaningful is up to the dynamic linker. As I’m sure you’re aware, dyld has grown support for new Swift metadata sections as Darwin has evolved. None of that required changes to the Mach-O format, and Mach-O files with these sections can be examined, if not dynamically linked, by tools that predate the introduction of these sections.

One format-specific difference is that Mach-O sections are grouped into “segments” that can also be arbitrarily named. Mach-O tools conventionally accept a fully-qualified syntax of the form «segment name»,«section name» to disambiguate when sections with the same name are present in multiple sections.

1 Like

It should come as no surprise to @kubamracek that I am in favour of this proposal. Swift Testing needs this feature, or some approximation thereof, in order to: support Embedded Swift; improve the performance of test discovery across all platforms; and enable future metadata-based functionality that would get bogged down in our current implementation (which is based on walking Swift's type metadata section at runtime. Yikes!)

I don't have a strong opinion about the spelling of @section("...") vs. @section(elf: "...", macho: "...", ...) etc. The former is closer to what clang and GCC do with __attribute__((__section__("..."))) of course. The latter is slightly more concise, but our own code would probably end up looking like:

#if objectFileFormat(elf) || objectFileFormat(coff) || ...
@section(elf: "...", coff: "...", ...)
#else
#error("Unknown object file format!")
#endif
static let $s_macro_emitted_symbol_name: (...) = (...)

So I'd personally actually lean toward @section("...") and just let me, the code author, figure out what string to use depending on the format.

Regardless of how the attribute is spelled, we will still need #if objectFileFormat(...) to correctly support Embedded Swift because we also need to read from these sections at runtime and the code for doing so is both platform- and format-specific. Right now we have a file with #if os(macOS) || os(iOS) || ..., #elseif os(Linux) || os(FreeBSD), #elseif os(Windows) etc. and that's all well and good when you're dealing with a dynamically linked toolchain, but in Embedded Swift we may not even have an OS in the traditional sense, so it's the wrong value to look at.

2 Likes

For reference, Swift Testing's @Test attribute/macro currently emits code of the following form if built from a package and its client enables the SymbolLinkageMarkers experimental feature:

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS)
    @_section("__DATA_CONST,__swift5_tests")
#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI)
    @_section("swift5_tests")
#elseif os(Windows)
    @_section(".sw5test$B")
#else
    @__testing(warning: "Platform-specific implementation missing: test content section name unavailable")
#endif
    @_used
    private static let $s___: Testing.__TestContentRecord = (...)
3 Likes

One of the big reasons that Clang/GCC don't suffer from just having __attribute__((__section__("..."))) is because people writing a multi-platform library are pretty much always going to hide that attribute in a preprocessor macro. The section names would rarely appear as literal strings at the usage site; they'd define a custom macro per "thing" and it would just expand to the right section name based on target environment.

It would be nice if we could define a custom macro like this that would expand to the right conditionals:

@MyCustomSection func foo() {}

But that's not possible with today's attached macros AFAIK; macros can only apply attributes to a type's members (and even those can only be AttributeSyntax nodes, which I believe forbids injecting attributes that need to be surrounded by #if). Even if it were possible, asking a library to write a macro just to do this substitution is a pretty large undertaking.

I think the future direction of allowing a compile-time-constant string to be used for the section name is the most scalable choice going forward. That would let a library author define once the target-specific names for whatever sections they need, and then at all the usage sites they would only need to write @section(whateverTheVariableIsNamed).

On the other hand, the suggested alternative of @section(elf: ..., macho:, ..., ...) wouldn't permit that simplification. Even if the individual section names were defined as compile-time constants, all of them would still need to be repeated.

If we look at them side-by-side, first with the singular attribute:

#if objectFileFormat(MachO)
/*constant initialized*/ let mySection = "__DATA,mySection"
#elseif objectFileFormat(ELF)
/*constant initialized*/ let mySection = "something_else"
#elseif objectFileFormat(COFF)
/*constant initialized*/ let mySection = "i_dont_know_windows"
#endif

@section(mySection)
func someFunction() {}

@section(mySection)
func someOtherFunction() {}

And now with a version of the attribute that takes the object formats inline:

/*constant initialized*/ let mySectionMachO = "__DATA,mySection"
/*constant initialized*/ let mySectionELF = "something_else"
/*constant initialized*/ let mySectionCOFF = "i_dont_know_windows"
#endif

@section(macho: mySectionMachO, elf: mySectionELF, coff: mySectionCOFF)
func someFunction() {}

@section(macho: mySectionMachO, elf: mySectionELF, coff: mySectionCOFF)
func someOtherFunction() {}

I think the first one is the clear winner in clarity. So even though we have to pay the cost of more repetition in the short term, in the long term the singular @section("...") scales far better. I think we would end up regretting a version of @section that has to keep chasing after various configurations with an ad hoc syntax vs. one that composes more simply using the right conditionals.

4 Likes

For the sake of discussion, @kubamracek how difficult would it be to make this work as part of the initial feature, assuming some reasonable as-yet-unspecified constraints on the declaration of whateverTheVariableIsNamed?

If we did end up using the singular @section(elf: ...) variant, then I think it would be necessary to emit a warning or error in the case where the current object file format is not listed, otherwise a developer may not realize that their code is miscompiling when they start building it for a new platform.

If an opt-out is needed, some special "shut up compiler!" syntax like @section(elf: ..., default: nil) might be appropriate.

1 Like

That would be a thin slice of another general feature (constant or literal expressions), which are almost certainly going to require new language syntax beyond what's in this proposal. It's best left to a separate proposal.

Doug

Good work, +1.

Just want to pose a related question. In my experience, the underscored attributes @_section does not work well when ASan is enabled, and because we don't have a Swiftly way to write __attribute__((no_sanitize_address)) equivalent, I had to turn off ASan globally. Does this proposal have a solution for this?

The proposal introduces lazy let variables:

@section("__DATA,colocated") lazy let data1: Int = 42 // âś…
@section("__DATA,colocated") lazy let data2: Int = Int.random(in: 0 ..< 10) // âś…

which is a long requested feature in general, most recently in It's 2025, can we have a “lazy let”? - #32 by michelf .

But there seems to be no discussion on its use outside of @section(…) annotations. Is lazy let intended to be allowed elsewhere too, or should there be at least a mention about it in future directions?

1 Like

Note: The intention is that the @section and @used attributes are to be used rarely and only by specific use cases; high-level application code should not need to use them directly and instead should rely on libraries, macros and other abstractions over the low-level attributes.

As an iOS app developer, I’m a bit worried that attributes like @section and @used are named with just a single word.

It’s not obvious at first glance that these are meant for advanced use cases, not for beginners or typical app code.

I think using a longer, more descriptive name like @linkageSection would make that clearer.

15 Likes

lazy let

I don’t think the other forum thread is talking about the same lazy let as the Section Placement Control proposal. In here, the proposal is strictly saying that you can put the lazy keyword only on global and static variables. Those do already have the “lazy behavior” today.

I see. Why the lazy keyword then though?

1 Like

Will the string be passed verbatim to the compiler backend and assembler? Thinking about mach-o specifically where there are often section types or other flags passed in addition to the section name.

For example, instead of “__TEXT,__cstring” its common for folks to write “__TEXT,__cstring,cstring_literals”.

1 Like

I think this is explained in the proposal:

On global and static variables that are annotated with @section, the compiler will now allow the lazy keyword (which is currently disallowed on all global and static variables). Using it will opt such variable out of the "mandatory static initialization" behavior and instead use the at-runtime lazy initialization that traditional global and static variables have. The initializer expression does not need to be a constant expression in this case.

Essentially, globals are lazy normally. @section makes the global non-lazy. Using the lazy keyword gives you the "lazy behavior" back. Inventing a new keyword or a new attribute for that purpose seems completely unnecessary, because the word "lazy" exactly matches the intent. The reason why the compiler as of today rejects the keyword lazy on global variables is that it's superfluous -- globals already get the "lazy behavior" -- not that it's a missing/unavailable feature.

All the above ^ only applies when talking about global (and static) variables.

4 Likes

Yes, passed as is to the rest of the compiler. Same as how __attribute__((section("..."))) works in Clang. From the proposal:

...custom section names can be specified as a string literal. The name will be directly set for the symbol in the resulting object file, without any processing.

Though I guess this ^ is technically an invalid statement, because in an actual object file, symbols do not carry section names as unparsed strings. Let me try to adjust the wording on this to make it clear.

1 Like