From 6491ee0fd3c714a0915c8ef097d91e09a37987e5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 22 Aug 2026 13:13:39 +0200 Subject: [PATCH 01/15] document that extern statics may be bigger than the declared size --- src/items/external-blocks.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 8218b8f1c3..e4fb6118dd 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -74,6 +74,10 @@ Extern statics can be either immutable or mutable just like [statics] outside of r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. +r[items.extern.static.size] +The actual memory that the extern static resolves to must have *at least* the size and alignment of the type that it was declared with in the extern block. +If the actual memory is bigger, then it is permitted to access that extra memory. + r[items.extern.abi] ## ABI From ec8f4ef9c9d523203a85af8546b57fdf11601426 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Aug 2026 16:40:40 +0200 Subject: [PATCH 02/15] add UB: invalid extern static --- src/behavior-considered-undefined.md | 3 +++ src/items/external-blocks.md | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 935f0dd9fa..04a65fe520 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -61,6 +61,9 @@ r[undefined.invalid] r[undefined.asm] * Incorrect use of inline assembly. For more details, refer to the [rules] to follow when writing code that uses inline assembly. +r[undefined.extern-static] +* Declaring an `extern static` with some size/alignment/mutability, when the actual symbol this resolves to is smaller / less aligned / less mutable. + r[undefined.runtime] * Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented. * For assumptions specifically related to unwinding, see the [panic documentation][unwinding-ffi]. diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index e4fb6118dd..86a9f907c2 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -75,7 +75,7 @@ r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. r[items.extern.static.size] -The actual memory that the extern static resolves to must have *at least* the size and alignment of the type that it was declared with in the extern block. +The actual memory that the extern static resolves to [must have][extern-static-ub] *at least* the size and alignment of the type that it was declared with in the extern block. If the actual memory is bigger, then it is permitted to access that extra memory. r[items.extern.abi] @@ -470,6 +470,7 @@ Attributes on extern function parameters follow the same rules and restrictions [`verbatim` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-verbatim [`whole-archive` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-whole-archive [attributes]: ../attributes.md +[extern-static-ub]: ../behavior-considered-undefined.md#r-undefined.extern-static [functions]: functions.md [regular function parameters]: functions.md#attributes-on-function-parameters [statics]: static-items.md From 199eef1e14fb5b53a4dc56bbc4ca9f1aef1bcd77 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 26 Aug 2026 19:11:39 +0200 Subject: [PATCH 03/15] do not allow oversized raw-dylib statics --- src/behavior-considered-undefined.md | 1 + src/items/external-blocks.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 04a65fe520..ab2207e283 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -63,6 +63,7 @@ r[undefined.asm] r[undefined.extern-static] * Declaring an `extern static` with some size/alignment/mutability, when the actual symbol this resolves to is smaller / less aligned / less mutable. + For `extern static` with `raw-dylib` linkage, the actual symbol must have *exactly* the declared size. r[undefined.runtime] * Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented. diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 86a9f907c2..d8f101ccc0 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -372,6 +372,9 @@ Specifying `kind = "dylib"` instructs the Rust compiler to link an import librar r[items.extern.attributes.link.kind-raw-dylib.platform-specific] `raw-dylib` is only supported on Windows. Using it when targeting other platforms will result in a compiler error. +r[items.extern.attributes.link.kind-raw-dylib.size] +Unlike regular external statics, a `raw-dylib` static is *not* allowed to be bigger than its declared size. + r[items.extern.attributes.link.import_name_type] #### The `import_name_type` key From 41a5230c24d291185413e5fef076da9c9a392281 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 1 Sep 2026 22:31:59 +0200 Subject: [PATCH 04/15] fix typo Co-authored-by: Pete LeVasseur --- src/items/external-blocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index d8f101ccc0..7c5af3dcdd 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -75,7 +75,7 @@ r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. r[items.extern.static.size] -The actual memory that the extern static resolves to [must have][extern-static-ub] *at least* the size and alignment of the type that it was declared with in the extern block. +The actual memory that the extern static resolves to [must have][extern-static-ub] *at least* the size and alignment of the type that it was declared within the extern block. If the actual memory is bigger, then it is permitted to access that extra memory. r[items.extern.abi] From 68f84407c471e3fbe4641a44f52ecd784bceb8b6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 2 Sep 2026 08:40:52 +0200 Subject: [PATCH 05/15] tweak terminology, clarify --- src/behavior-considered-undefined.md | 4 ++-- src/items/external-blocks.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index ab2207e283..e514e9247b 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -62,8 +62,8 @@ r[undefined.asm] * Incorrect use of inline assembly. For more details, refer to the [rules] to follow when writing code that uses inline assembly. r[undefined.extern-static] -* Declaring an `extern static` with some size/alignment/mutability, when the actual symbol this resolves to is smaller / less aligned / less mutable. - For `extern static` with `raw-dylib` linkage, the actual symbol must have *exactly* the declared size. +* Declaring an `extern static` with some size/alignment/mutability, when the actual memory the extern static resolves to is smaller / less aligned / less mutable. + For `extern static` with `raw-dylib` linkage, the actual memory must have *exactly* the declared size. r[undefined.runtime] * Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented. diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 7c5af3dcdd..922e321ff5 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -373,7 +373,7 @@ r[items.extern.attributes.link.kind-raw-dylib.platform-specific] `raw-dylib` is only supported on Windows. Using it when targeting other platforms will result in a compiler error. r[items.extern.attributes.link.kind-raw-dylib.size] -Unlike regular external statics, a `raw-dylib` static is *not* allowed to be bigger than its declared size. +Unlike regular external statics, a `raw-dylib` static is *not* allowed to be bigger than its declared size. The size needs to match exactly. r[items.extern.attributes.link.import_name_type] #### The `import_name_type` key From 1dc181cee69a4935486890e216bdcf6c077b4256 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 9 Oct 2026 18:47:52 +0200 Subject: [PATCH 06/15] null is still special --- src/behavior-considered-undefined.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index e514e9247b..984a8123fb 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -62,7 +62,7 @@ r[undefined.asm] * Incorrect use of inline assembly. For more details, refer to the [rules] to follow when writing code that uses inline assembly. r[undefined.extern-static] -* Declaring an `extern static` with some size/alignment/mutability, when the actual memory the extern static resolves to is smaller / less aligned / less mutable. +* Declaring an `extern static` with some size/alignment/mutability, when the actual memory the extern static resolves to is smaller / less aligned / less mutable / located at address 0. For `extern static` with `raw-dylib` linkage, the actual memory must have *exactly* the declared size. r[undefined.runtime] From 1f9bdca3e0121d60c54cf608f08723f92169cbaf Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 10 Oct 2026 07:33:27 +0000 Subject: [PATCH 07/15] Say that extern statics resolve to *allocations* The new rules talked about "the actual memory" that an extern static resolves to. It'd be better to talk about the *allocation*, as the Reference already does elsewhere. Let's do that, and let's talk about allocations being *larger* rather than *bigger*. --- src/behavior-considered-undefined.md | 4 ++-- src/items/external-blocks.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 984a8123fb..039680ef15 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -62,8 +62,8 @@ r[undefined.asm] * Incorrect use of inline assembly. For more details, refer to the [rules] to follow when writing code that uses inline assembly. r[undefined.extern-static] -* Declaring an `extern static` with some size/alignment/mutability, when the actual memory the extern static resolves to is smaller / less aligned / less mutable / located at address 0. - For `extern static` with `raw-dylib` linkage, the actual memory must have *exactly* the declared size. +* Declaring an `extern static` with some size/alignment/mutability, when the allocation the extern static resolves to is smaller / less aligned / less mutable / located at address 0. + For `extern static` with `raw-dylib` linkage, the allocation must have *exactly* the declared size. r[undefined.runtime] * Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented. diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 922e321ff5..d06fa1d4dc 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -75,8 +75,8 @@ r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. r[items.extern.static.size] -The actual memory that the extern static resolves to [must have][extern-static-ub] *at least* the size and alignment of the type that it was declared within the extern block. -If the actual memory is bigger, then it is permitted to access that extra memory. +The allocation that the extern static resolves to [must have][extern-static-ub] *at least* the size and alignment of the type that it was declared within the extern block. +If the allocation is larger, then it is permitted to access the extra bytes. r[items.extern.abi] ## ABI @@ -373,7 +373,7 @@ r[items.extern.attributes.link.kind-raw-dylib.platform-specific] `raw-dylib` is only supported on Windows. Using it when targeting other platforms will result in a compiler error. r[items.extern.attributes.link.kind-raw-dylib.size] -Unlike regular external statics, a `raw-dylib` static is *not* allowed to be bigger than its declared size. The size needs to match exactly. +Unlike regular external statics, a `raw-dylib` static is *not* allowed to be larger than its declared size. The size needs to match exactly. r[items.extern.attributes.link.import_name_type] #### The `import_name_type` key From 9c315ea379088d4214df105ff0638d4cdf628507 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 11 Oct 2026 09:50:54 +0000 Subject: [PATCH 08/15] Remove emphasis in new extern static rules Every word in the Reference is important, so we generally don't emphasize words such as *exactly*, *at least*, and *not*. This helps us reserve emphasis for when we're defining a term. Let's remove the stray emphasis added in the new rules. --- src/behavior-considered-undefined.md | 2 +- src/items/external-blocks.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 039680ef15..8cc4dbf98e 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -63,7 +63,7 @@ r[undefined.asm] r[undefined.extern-static] * Declaring an `extern static` with some size/alignment/mutability, when the allocation the extern static resolves to is smaller / less aligned / less mutable / located at address 0. - For `extern static` with `raw-dylib` linkage, the allocation must have *exactly* the declared size. + For `extern static` with `raw-dylib` linkage, the allocation must have exactly the declared size. r[undefined.runtime] * Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented. diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index d06fa1d4dc..afb6087759 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -75,7 +75,7 @@ r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. r[items.extern.static.size] -The allocation that the extern static resolves to [must have][extern-static-ub] *at least* the size and alignment of the type that it was declared within the extern block. +The allocation that the extern static resolves to [must have][extern-static-ub] at least the size and alignment of the type that it was declared within the extern block. If the allocation is larger, then it is permitted to access the extra bytes. r[items.extern.abi] @@ -373,7 +373,7 @@ r[items.extern.attributes.link.kind-raw-dylib.platform-specific] `raw-dylib` is only supported on Windows. Using it when targeting other platforms will result in a compiler error. r[items.extern.attributes.link.kind-raw-dylib.size] -Unlike regular external statics, a `raw-dylib` static is *not* allowed to be larger than its declared size. The size needs to match exactly. +Unlike regular external statics, a `raw-dylib` static is not allowed to be larger than its declared size. The size needs to match exactly. r[items.extern.attributes.link.import_name_type] #### The `import_name_type` key From d1d36bd5ea5397b22257b4e682f180753c0a6419 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 10 Oct 2026 07:33:27 +0000 Subject: [PATCH 09/15] Reword the extern static size rule Let's revise this rule to match the Reference's style and to fix where two prepositions had been mistakenly fused. --- src/items/external-blocks.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index afb6087759..8efd0a3f61 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -75,8 +75,7 @@ r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. r[items.extern.static.size] -The allocation that the extern static resolves to [must have][extern-static-ub] at least the size and alignment of the type that it was declared within the extern block. -If the allocation is larger, then it is permitted to access the extra bytes. +The allocation that an extern static resolves to must have at least the size and alignment of the type it is declared with (see [undefined.extern-static]). If the allocation is larger, accessing the extra bytes is allowed. r[items.extern.abi] ## ABI @@ -473,7 +472,6 @@ Attributes on extern function parameters follow the same rules and restrictions [`verbatim` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-verbatim [`whole-archive` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-whole-archive [attributes]: ../attributes.md -[extern-static-ub]: ../behavior-considered-undefined.md#r-undefined.extern-static [functions]: functions.md [regular function parameters]: functions.md#attributes-on-function-parameters [statics]: static-items.md From 97eb32c675cf5ed4cdda5524846c0358f99ff5b0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 10 Oct 2026 07:33:27 +0000 Subject: [PATCH 10/15] Separate out the `raw-dylib` size UB rule Rather than appending the bit about `raw-dylib` to the `undefined.extern-static` UB rule, let's add a separate rule for this. This reads better and makes for better internal links. --- src/behavior-considered-undefined.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 8cc4dbf98e..1a1fe4c9f5 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -63,7 +63,9 @@ r[undefined.asm] r[undefined.extern-static] * Declaring an `extern static` with some size/alignment/mutability, when the allocation the extern static resolves to is smaller / less aligned / less mutable / located at address 0. - For `extern static` with `raw-dylib` linkage, the allocation must have exactly the declared size. + +r[undefined.extern-static-raw-dylib] +* Declaring an `extern static` imported with the [`raw-dylib` link kind] when the allocation it resolves to does not have the same size as the type it is declared with. r[undefined.runtime] * Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented. @@ -224,3 +226,4 @@ r[undefined.validity.undef] [const-promoted]: destructors.md#constant-promotion [lifetime-extended]: destructors.md#temporary-lifetime-extension [zero-sized]: glossary.zst +[`raw-dylib` link kind]: items.extern.attributes.link.raw-dylib From 45d409764edb307140acdcc5376339ad4007bcd6 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 10 Oct 2026 07:33:27 +0000 Subject: [PATCH 11/15] Reword the extern static UB entry The `undefined.extern-static` rule was leaning heavily on slashed lists. It's better to not do this. Let's revise editorially. --- src/behavior-considered-undefined.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 1a1fe4c9f5..820428da72 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -62,7 +62,7 @@ r[undefined.asm] * Incorrect use of inline assembly. For more details, refer to the [rules] to follow when writing code that uses inline assembly. r[undefined.extern-static] -* Declaring an `extern static` with some size/alignment/mutability, when the allocation the extern static resolves to is smaller / less aligned / less mutable / located at address 0. +* Declaring an `extern static` when the allocation it resolves to is located at address 0 or is smaller, less aligned, or less mutable than declared. r[undefined.extern-static-raw-dylib] * Declaring an `extern static` imported with the [`raw-dylib` link kind] when the allocation it resolves to does not have the same size as the type it is declared with. From 617bb2ac321ef4be71e5e4e0291c3de16d43bd9d Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 10 Oct 2026 07:33:27 +0000 Subject: [PATCH 12/15] Clarify the `raw-dylib` size exception The rule talked about a static being larger than its declared size, but this is a bit loose. The static is always the size of its declared type. What might be a different size is the allocation. Let's revise to be more precise. And let's add an admonition on the `items.extern.static.size` rule pointing to this rule so the exception isn't missed. --- src/items/external-blocks.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 8efd0a3f61..154eae9391 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -77,6 +77,9 @@ An immutable static *must* be initialized before any Rust code is executed. It i r[items.extern.static.size] The allocation that an extern static resolves to must have at least the size and alignment of the type it is declared with (see [undefined.extern-static]). If the allocation is larger, accessing the extra bytes is allowed. +> [!NOTE] +> See [items.extern.attributes.link.kind-raw-dylib.size] for an exception to this rule. + r[items.extern.abi] ## ABI @@ -372,7 +375,7 @@ r[items.extern.attributes.link.kind-raw-dylib.platform-specific] `raw-dylib` is only supported on Windows. Using it when targeting other platforms will result in a compiler error. r[items.extern.attributes.link.kind-raw-dylib.size] -Unlike regular external statics, a `raw-dylib` static is not allowed to be larger than its declared size. The size needs to match exactly. +Unlike other extern statics, a `raw-dylib` static must resolve to an allocation whose size is exactly that of the type it is declared with (see [undefined.extern-static-raw-dylib]). r[items.extern.attributes.link.import_name_type] #### The `import_name_type` key From 8d110a90e8fcd87e4c64dda68eae186752473fff Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 12 Oct 2026 06:44:07 +0000 Subject: [PATCH 13/15] Clarify allocations start at an extern static's address The bytes used for an extern static will often fall within a linker *section* that starts earlier. Let's clarify that the *allocation* starts at the address of the extern static. --- src/items/external-blocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 154eae9391..4eef9c2a12 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -75,7 +75,7 @@ r[items.extern.static.read-only] An immutable static *must* be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. Once Rust code runs, mutating an immutable static (from inside or outside Rust) is UB, except if the mutation happens to bytes inside of an `UnsafeCell`. r[items.extern.static.size] -The allocation that an extern static resolves to must have at least the size and alignment of the type it is declared with (see [undefined.extern-static]). If the allocation is larger, accessing the extra bytes is allowed. +An extern static resolves to an allocation that starts at the address of the static; the allocation must have at least the size and alignment of the type it is declared with (see [undefined.extern-static]). If the allocation is larger, accessing the extra bytes is allowed. > [!NOTE] > See [items.extern.attributes.link.kind-raw-dylib.size] for an exception to this rule. From d3a25893a404d2eb0928b05e585e8a825e505ba6 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 12 Oct 2026 09:02:26 +0000 Subject: [PATCH 14/15] Clarify that extern static allocations are disjoint That some extern statics may be used to access more bytes than would be implied by the size of their types does not imply that they may be used to access the bytes of other statics (with different symbols), even if those bytes are in the same linker section. Let's make this explicit. And since statics with the same symbol do resolve to the same allocation, let's add a rule making that explicit too. --- src/items/external-blocks.md | 96 ++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 4eef9c2a12..20dd363db5 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -80,6 +80,102 @@ An extern static resolves to an allocation that starts at the address of the sta > [!NOTE] > See [items.extern.attributes.link.kind-raw-dylib.size] for an exception to this rule. +r[items.extern.static.same-symbol] +An extern static resolves to the same allocation as any other static with the same symbol. + +```rust +#[unsafe(no_mangle)] +static mut COUNTER: u8 = 0; + +unsafe extern "C" { + #[link_name = "COUNTER"] + static mut COUNTER_ALIAS: u8; +} + +fn main() { + unsafe { + COUNTER += 1; + COUNTER_ALIAS += 2; + // The two statics share one allocation. + assert!(COUNTER == COUNTER_ALIAS && COUNTER == 3); + assert!(&raw const COUNTER == &raw const COUNTER_ALIAS); + } +} +``` + +r[items.extern.static.allocations-disjoint] +The allocation that an extern static resolves to does not include the bytes of any static with a different symbol, even when the linker places the statics in the same section. + +> [!NOTE] +> A pointer to one allocation can't be used, via offsetting, to access bytes in a different allocation. Since statics with different symbols have different allocations, you can't use a pointer derived from one static to access the bytes of another static that has a different symbol. +> +> In particular, an extern static that marks the start of a linker section cannot be used to access statics with different symbols placed in that section. + +```rust,standalone_crate +// Bytes placed in a section without a static may be read through the +// section's start symbol. + +// Three bytes placed in a section named `entries`. +# #[cfg(target_os = "linux")] +core::arch::global_asm!( + ".pushsection entries,\"aR\"", // `R` is like `#[used]`. + ".byte 1, 2, 3", + ".popsection", +); + +# #[cfg(target_os = "linux")] +unsafe extern "C" { + // On ELF targets, the linker defines these symbols at the start + // and the end of the `entries` section. + static __start_entries: [u8; 0]; + static __stop_entries: [u8; 0]; +} + +fn main() { +# #[cfg(target_os = "linux")] { + let start = &raw const __start_entries as *const u8; + let len = (&raw const __stop_entries).addr() - start.addr(); + // The bytes of the section belong to no static, so they are part + // of the allocation that `__start_entries` resolves to. + let entries = unsafe { core::slice::from_raw_parts(start, len) }; + assert_eq!(entries, &[1, 2, 3]); +# } +} +``` + +```rust,standalone_crate +// A static placed in a section is an allocation of its own, which the +// section's start symbol cannot be used to read. + +// A static placed in a section named `entries`. +# #[cfg(target_os = "linux")] +#[used] +#[unsafe(link_section = "entries")] +static ENTRY: [u8; 3] = [1, 2, 3]; + +# #[cfg(target_os = "linux")] +unsafe extern "C" { + // On ELF targets, the linker defines these symbols at the start + // and the end of the `entries` section. + static __start_entries: [u8; 0]; + static __stop_entries: [u8; 0]; +} + +fn main() { +# #[cfg(target_os = "linux")] { + let start = &raw const __start_entries as *const u8; + let stop = &raw const __stop_entries as *const u8; + // The section holds exactly the bytes of `ENTRY`. + assert_eq!(stop.addr() - start.addr(), 3); + assert_eq!(start.addr(), (&raw const ENTRY).addr()); + // But those bytes are the allocation of `ENTRY`, not of + // `__start_entries`, so reading them through `start` is undefined + // behavior. + //let x = unsafe { *start }; // UB. +# } +} +``` + r[items.extern.abi] ## ABI From 932a1f38672ce565c9c040549526dbf865e34341 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 5 Nov 2026 03:27:23 +0000 Subject: [PATCH 15/15] Demonstrate the decentralized registration pattern Sometimes it's useful for multiple crates (or external compilation units) to be able to each add entries at link time to a single table that can be iterated over at runtime. Let's show how to soundly encode this pattern using the guarantees that we're adding. --- src/items/external-blocks.md | 144 +++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 20dd363db5..3dcfbba907 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -176,6 +176,150 @@ fn main() { } ``` +r[items.extern.static.decentralized-registration] +> [!EXAMPLE] +> ### Decentralized registration +> +> Sometimes it's useful for multiple crates (or external compilation units) to be able to each add entries at link time to a single table that can be iterated over at runtime. This can be used, e.g., for registering handlers. It can be done without allocation, without code before `main`, and with `no_std`. +> +> We'll describe how to do this *decentralized registration* pattern soundly, using the guarantees provided in this section; then we'll show an optimized form that requires fewer bytes per entry, avoids the overhead of relocation records, and allows the memory pages used for the table to be marked as read-only, which lets them be shared across processes. +> +> #### Basic form using pointers as entries +> +> In the code below, each `register!` call adds a function pointer to the `handlers` section; `handlers()` returns the resulting table as a slice. +> +> ```rust,standalone_crate +> // This form works on 64-bit ELF targets. +> #[cfg(all(target_os = "linux", target_pointer_width = "64"))] +> mod registry { +> macro_rules! register { +> ($f:path) => { +> core::arch::global_asm!( +> // `aw`: Allocated and writable. The memory must be +> // writable since the pointer is filled in by a +> // relocation when the program is loaded. +> // `R`: Retained. This causes the linker to keep the +> // entry even when nothing refers to it. +> ".pushsection handlers,\"awR\"", +> ".balign 8", +> ".8byte {f}", // An 8-byte entry for 64-bit targets. +> ".popsection", +> f = sym $f, +> ); +> }; +> } +> +> mod a { // This could be in a different crate. +> pub extern "C" fn handler() -> u8 { 1 } +> register!(handler); +> } +> +> mod b { // This could be in a different crate. +> pub extern "C" fn handler() -> u8 { 2 } +> register!(handler); +> } +> +> pub type Handler = extern "C" fn() -> u8; +> +> unsafe extern "C" { +> // On ELF targets, the linker defines these symbols at the +> // start and the end of the `handlers` section. +> static __start_handlers: [Handler; 0]; +> static __stop_handlers: [Handler; 0]; +> } +> +> pub fn handlers() -> &'static [Handler] { +> # use core::slice; +> let start = (&raw const __start_handlers).cast::(); +> let len = ((&raw const __stop_handlers).addr() - start.addr()) +> / size_of::(); +> // SAFETY: The entries belong to no static, so they are part +> // of the allocation to which `__start_handlers` resolves. +> unsafe { slice::from_raw_parts(start, len) } +> } +> } +> +> fn main() { +> # #[cfg(all(target_os = "linux", target_pointer_width = "64"))] { +> let sum: u8 = registry::handlers().iter().map(|f| f()).sum(); +> assert_eq!(sum, 3); +> # } +> } +> ``` +> +> In this form, the memory for the table must be read-write, as the entries are filled in by a relocation when the program is loaded. And, in a shared library or position-independent executable, each entry requires a relocation record (24 bytes in a 64-bit ELF file). +> +> #### Optimized form using place-relative offsets +> +> To avoid this overhead and allow the memory pages for the table to be marked read-only, we can instead have each entry hold a 32-bit *place-relative offset*. This offset is the distance from the entry to the handler. +> +> In this form, each entry takes just 4 bytes on all platforms. (By contrast, each entry in the basic form can take up to 32 bytes.) As an added benefit, with this form, the assembler directives do not depend on the pointer width. +> +> This requires only that the function lie within 2 GiB of the table. +> +> ```rust,standalone_crate +> // This form works on ELF targets regardless of the pointer width. +> #[cfg(target_os = "linux")] +> mod registry { +> macro_rules! register { +> ($f:path) => { +> core::arch::global_asm!( +> // Since the entries don't need to be filled in when +> // the program is loaded, the memory can be read-only. +> ".pushsection handlers,\"aR\"", +> ".balign 4", +> ".4byte {f} - .", // Offset from the entry to the handler. +> ".popsection", +> f = sym $f, +> ); +> }; +> } +> +> # mod a { // This could be in a different crate. +> # pub extern "C" fn handler() -> u8 { 1 } +> # register!(handler); +> # } +> # +> # mod b { // This could be in a different crate. +> # pub extern "C" fn handler() -> u8 { 2 } +> # register!(handler); +> # } +> # +> unsafe extern "C" { +> static __start_handlers: [i32; 0]; +> static __stop_handlers: [i32; 0]; +> } +> +> pub type Handler = extern "C" fn() -> u8; +> +> pub fn handlers() -> impl Iterator { +> # use core::{mem, slice}; +> let start = (&raw const __start_handlers).cast::(); +> let len = ((&raw const __stop_handlers).addr() - start.addr()) +> / size_of::(); +> let offsets = unsafe { slice::from_raw_parts(start, len) }; +> offsets.iter().map(|entry| { +> // The address of the function is the address of the entry +> // plus the offset stored in the entry. +> let addr = (entry as *const i32) +> .addr().wrapping_add_signed(*entry as isize); +> unsafe { mem::transmute::(addr) } +> }) +> } +> } +> +> fn main() { +> # #[cfg(target_os = "linux")] { +> let sum: u8 = registry::handlers().map(|f| f()).sum(); +> assert_eq!(sum, 3); +> # } +> } +> ``` +> +> For shared libraries and position-independent executables, the size saved per entry is the pointer and its relocation record, less the 4-byte offset: 28 bytes on 64-bit targets (an 8-byte pointer and a 24-byte record), and 8 bytes on 32-bit targets, or 12 on those whose relocation records carry an explicit addend, such as PowerPC and RISC-V. +> +> Which form should you use? As long as the functions can lie within 2 GiB of the table and you don't need the entries to be pointers, prefer the optimized form. + r[items.extern.abi] ## ABI