Skip to content

Commit 4dd4f53

Browse files
committed
Add definition of constant expressions, add 'lazy'
1 parent a0aa0d4 commit 4dd4f53

File tree

1 file changed

+71
-1
lines changed

1 file changed

+71
-1
lines changed

proposals/0nnn-section-control.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ This proposal recommends to use sections of the various object file formats as t
5555

5656
## Proposed Solution
5757

58-
The proposal is to add two new attributes `@section` and `@used` that will allow annotating global and static variables with directives to place the value into a custom section, and to require no-dead-stripping aka "attribute used". The `@section` attribute relies heavily on the facilities provided by [Swift Compile-Time Values](https://github.com/artemcm/swift-evolution/blob/const-values/proposals/0nnn-const-values.md), namely the ability to enforce constantness of an expressions. Using `@section` requires that the initializer expression is a constant expression:
58+
The proposal is to add two new attributes `@section` and `@used` that will allow annotating global and static variables with directives to place the value into a custom section, and to require no-dead-stripping aka "attribute used". Using `@section` requires that the initializer expression is a constant expression (see Constant Expressions below for the definition of that):
5959

6060
```swift
6161
// Place an entry into a section, mark as "do not dead strip".
@@ -100,6 +100,13 @@ var global = ...
100100

101101
For the ELF file format specifically, the compiler will also emit a “section index” into produced object files, containing an entry about each custom section used in the compilation. This is a solution to an ELF specific problem where the behavior of ELF linkers and loaders means that sections are not easily discoverable at runtime.
102102

103+
When placing variables into a custom section, it's also allowed to use the `lazy` keyword to opt out of the mandatory static initialization and mandatory constant expression behavior, while still achieving section placement of the backing store of the data:
104+
105+
```swift
106+
@section("__DATA,colocated") lazy let data1: Int = 42 //
107+
@section("__DATA,colocated") lazy let data2: Int = Int.random(in: 0 ..< 10) //
108+
```
109+
103110
> 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.
104111
105112
> The scope of this proposal is limited to compile-time behavior and compile-time control. We expect that full user-facing solutions for features like linker sets, test discovery or plugins will also require runtime implementations to discover and iterate the contents of custom sections, possibly from multiple modules. This proposal makes sure to provide the right building blocks and artifacts in binaries for the runtime components, but doesn’t prescribe the shape of those. However, it is providing a significant step towards generalized and safe high-level mechanisms for those use cases. See the discussion in [Runtime discovery of data in custom sections](#runtime-discovery-of-data-in-custom-sections) and [Linker sets, plugins as high-level APIs](#linker-sets-plugins-as-high-level-apis) in Future Directions.
@@ -180,6 +187,54 @@ When allowed, the `@used` attribute on a variable declaration has the following
180187

181188
The effects described above are applied to the storage symbols and don’t generally affect optimizations and other transformations in the compiler. For example, the compiler is still allowed to propagate and copy a the constant value to code that uses the value, so there’s no guarantee that a value stored into a global with a custom section will not be propagated and “leak” outside of the section. The `@used` annotation, however, does inform the optimizer that such a variable cannot be removed, even when it doesn’t have any observed users or even if it’s inaccessible due to language rules (e.g. if it’s a private static member on an otherwise empty type).
182189

190+
### Constant expressions
191+
192+
Swift currently does not have a formal notion of a **constant expression**, i.e. an expression with a syntactic form that *guarantees the ability to know it's value at compile-time*. This proposal provides a definition of a "bare minimum" constant expression, with the understanding that this does not cover the language needs in generality, and the expectation that the Swift compiler and language will keep expanding the allowed forms of constant expressions. See [Generalized constant values and expressions](#generalized-constant-values-and-expressions) in Future Directions for further discussion on this.
193+
194+
This proposal defines a **constant expression** as being one of:
195+
196+
- an integer literal using any of built-in integer types (Int, UInt, Int8/16/32/64/128, UInt8/16/32/64/128)
197+
- a floating-point literal of type Float or Double
198+
- a boolean literal of type Bool
199+
- a direct reference to a non-generic function using its name (the function itself is not generic, and also it must not be defined in a generic context)
200+
- a direct reference to a non-generic metatype using the type name directly (the type itself is not generic, and also it must not be defined in a generic context), where the type is non-resilient
201+
- a tuple composed of only other constant expressions
202+
- an array literal of type InlineArray composed of only other constant expressions
203+
204+
Explicitly, this definition currently does **not allow** any operators, using any user-defined named types, any other built-in type (e.g. strings, dictionaries, sets), using closures, or referencing any variables by name. See below for examples of valid and invalid constant expressions:
205+
206+
```swift
207+
@section("...") let a = 42 //
208+
@section("...") let b = 3.14 //
209+
@section("...") let c = 1 + 1 // ❌ operators not allowed
210+
@section("...") let d = Int.max // ❌ not a literal
211+
@section("...") let e: UInt8 = 42 //
212+
@section("...") let f = UInt8(42) // ❌ not a literal
213+
@section("...") let g: MyCustomExpressibleByIntegerLiteral = 42 // ❌ not a built-in type
214+
215+
@section("...") let composition1 = (1, 2, 3, 2.718, true) //
216+
@section("...") let composition2 = (1, 2, Int.max) // ❌ tuple component not constant
217+
@section("...") let composition3: InlineArray = [1, 2, 3] //
218+
@section("...") let composition4: InlineArray = [1, 2, Int.max] // ❌ array component not constant
219+
@section("...") let composition5: (Int, [1 of Int], [1 of (Int, Int)]) = (1, [1], [(1, 1)]) //
220+
221+
func foo() -> Int { return 42 }
222+
@section("...") let func1 = foo //
223+
@section("...") let func2 = foo() // ❌ not a function reference
224+
@section("...") let func3 = Bool.random //
225+
@section("...") let func4 = Bool.self.random // ❌ not a direct reference
226+
@section("...") let func5 = (Bool.self as Bool.Type).random // ❌ not a direct reference
227+
@section("...") let func6 = [Int].randomElement // ❌ generic
228+
@section("...") let func7 = { } // ❌ not using name
229+
230+
struct S { }
231+
@section("...") let metatype1 = S.self //
232+
@section("...") let metatype2 = Int.self //
233+
@section("...") let metatype3 = Int.self.self // ❌ not a direct reference
234+
import Foundation
235+
@section("...") let metatype4 = URL.self // ❌ resilient
236+
```
237+
183238
### Guaranteed static initialization
184239

185240
Using attribute `@section` requires the initializer expression of the variable to be a **constant expression**. It's not required to separately annotate the expression for being a compile-time expression, instead this is implied from the `@section` attribute. On top of the constant-ness, `@section` on a global or static variable enforces **static initialization** on that variable.
@@ -221,6 +276,17 @@ let a = (42, foo) // "foo" is statically initialized into a
221276
// linkable/relocatable pointer
222277
```
223278
279+
### Lazy variables with section placement
280+
281+
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. This is useful for the uncommon use case of placing variables into a custom section purely for colocation (e.g. to improve performance by increasing page/cacheline locality):
282+
283+
```swift
284+
@section("__DATA,colocated") lazy let data1: Int = 42 //
285+
@section("__DATA,colocated") lazy let data2: Int = Int.random(in: 0 ..< 10) //
286+
```
287+
288+
Traditional global and static variables are backed by two symbols: An init-once token and the actual storage for the variable's content. Both of these symbols are going to be placed into the custom section when using `@section` with `lazy`. This also means that any offline or in-process introspection mechanisms cannot assume a specific layout or state of such variables and their storage bytes in the sections, as the exact layout and content of the symbols of lazy variables is an implementation detail of the Swift language runtime.
289+
224290
### Cross-platform object file format support
225291
226292
The custom section name specified in the `@section` attribute is not validated by the compiler, instead it’s passed directly as a string to the linker. Different platforms supported by Swift are using different object and binary file formats (Linux uses ELF, Darwin uses Mach-O, Windows uses COFF), and that implies different restrictions and rules on what are valid section names. Because of that, a multi-platform library code is expected to use `#if os(...)` to use different section names for different platforms. Because of that, it’s expected that the attributes are to be typically only used indirectly via macros that hide the low-level nature of sections and object file formats from higher-level code developers:
@@ -325,6 +391,10 @@ This will require some design decisions to be made around when should that be al
325391
326392
Static initialization of a global can be useful on its own, without placing data into a custom section, and a separate attribute for that could be added. This way, one can get the same effects as the `@section` attribute (static initialization, normal initalization behavior if top-level code) except the symbol would not be actually placed into any custom section.
327393
394+
### Generalized constant values and expressions
395+
396+
The notions of constant expressions and constant values is applicable to a much wider set of use cases that just section placement, and the set of allowed types and syntactical forms should be expanded in the future into a full-featured system for compile-time programming. A dedicated proposal, [Swift Compile-Time Values](https://github.com/artemcm/swift-evolution/blob/const-values/proposals/0nnn-const-values.md), is being pitched [on the forums](https://forums.swift.org/t/pitch-3-swift-compile-time-values/77434) and describes in detail the possible future of generalized constants, the relevant motivation and use cases.
397+
328398
### Allowing a reference to a constant string declaration as a section name
329399
330400
The requirement to only use string literals as the section names could be lifted in the future, and we might allow referring to a declaration of variable with a compile-time string. This would be useful to avoid repetition when placing multiple values into the same section without needing to use macros.

0 commit comments

Comments
 (0)