Skip to content

feat: MaybeUnint sockets to save space by putting sockets in .bss - #1199

Closed
sigvartmh wants to merge 1 commit into
smoltcp-rs:mainfrom
sigvartmh:feat/uninit-sockets-so-they-get-to-bss
Closed

feat: MaybeUnint sockets to save space by putting sockets in .bss#1199
sigvartmh wants to merge 1 commit into
smoltcp-rs:mainfrom
sigvartmh:feat/uninit-sockets-so-they-get-to-bss

Conversation

@sigvartmh

@sigvartmh sigvartmh commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Proposal

So this may be a bit more controversial but sockets currently don't get the 00 bit patteren meaning they can't be in .bss on embedded targets. Maybe there is a better way of not initializing them but this is the solution I found. Feel free to deny or come with feed back on it. This also adds unsafe to the code which may not be wanted.

Commit message:

Initialise a slice of uninitialised socket storage in place, returning the initialised slice.

This exists to keep socket storage out of .data on bare-metal targets.

SocketStorage is a niche-filled Option<Item>: None occupies zero bytes of payload and its bit pattern is chosen by the compiler, so EMPTY is not all-zero. A

static mut STORAGE: [SocketStorage; N] = [SocketStorage::EMPTY; N];

therefore cannot live in .bss; it goes in .data and the linker emits an initialiser image of N * size_of::<SocketStorage>() bytes into flash, almost all of which is zeros. Declaring the storage MaybeUninit instead places it in .bss, and this function fills it at startup:

static mut STORAGE: MaybeUninit<[SocketStorage; N]> = MaybeUninit::uninit();
let sockets = SocketSet::new(SocketStorage::init_slice(unsafe {
    &mut *(core::ptr::addr_of_mut!(STORAGE) as *mut [MaybeUninit<SocketStorage>; N])
}));

Measured on thumbv7em-none-eabihf with 7 sockets, opt-level="z" and fat LTO: 3616 bytes of .data removed, of which 3584 were zeros, at a cost of ~380 bytes of .rodata and a short init loop. RAM use is unchanged.

Initialise a slice of uninitialised socket storage in place, returning the
initialised slice.

This exists to keep socket storage out of `.data` on bare-metal targets.

`SocketStorage` is a niche-filled `Option<Item>`: `None` occupies zero
bytes of payload and its bit pattern is chosen by the compiler, so
`EMPTY` is *not* all-zero. A

```
static mut STORAGE: [SocketStorage; N] = [SocketStorage::EMPTY; N];
```

therefore cannot live in `.bss`; it goes in `.data` and the linker emits
an initialiser image of `N * size_of::<SocketStorage>()` bytes into
flash, almost all of which is zeros. Declaring the storage
`MaybeUninit` instead places it in `.bss`, and this function fills it at
startup:

```
static mut STORAGE: MaybeUninit<[SocketStorage; N]> = MaybeUninit::uninit();
let sockets = SocketSet::new(SocketStorage::init_slice(unsafe {
    &mut *(core::ptr::addr_of_mut!(STORAGE) as *mut [MaybeUninit<SocketStorage>; N])
}));
```

Measured on thumbv7em-none-eabihf with 7 sockets, `opt-level="z"` and fat
LTO: 3616 bytes of `.data` removed, of which 3584 were zeros, at a cost
of ~380 bytes of `.rodata` and a short init loop. RAM use is unchanged.
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.61%. Comparing base (f676d39) to head (c1c9768).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1199      +/-   ##
==========================================
+ Coverage   82.57%   82.61%   +0.03%     
==========================================
  Files          83       83              
  Lines       26174    26194      +20     
==========================================
+ Hits        21613    21639      +26     
+ Misses       4561     4555       -6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@whitequark

Copy link
Copy Markdown
Contributor

I've designed to keep the core of smoltcp (excluding phy drivers) free of unsafe as an intentional tradeoff. I would like to keep that tradeoff intact. There are quite a few optimizations that can be done if you're OK writing unsafe code, but I think there is a place for a TCP/IP stack which chooses robustness over absolute highest performance (or code size, etc), so smoltcp is such a stack.

Regarding your particular problem: smoltcp has been originally designed to have its structures allocated on stack, perhaps in main. Would that solve your problem?

@sigvartmh

sigvartmh commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

I've designed to keep the core of smoltcp (excluding phy drivers) free of unsafe as an intentional tradeoff. I would like to keep that tradeoff intact. There are quite a few optimizations that can be done if you're OK writing unsafe code, but I think there is a place for a TCP/IP stack which chooses robustness over absolute highest performance (or code size, etc), so smoltcp is such a stack.

Regarding your particular problem: smoltcp has been originally designed to have its structures allocated on stack, perhaps in main. Would that solve your problem?

Sure if I self allocate them on the stack having thme being allocated/initialized from .bss that's fine thanks for your reply :)

I looked through the code and found a no places of unsafe which is why I assumed this maybe wouldn't go in anyways great work on the stack thanks for making it public 👍

@sigvartmh sigvartmh closed this Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants