Skip to content

internal/godebug: let interp fold settings so unused FIPS self-tests are removed - #5883

Draft
davecheney wants to merge 1 commit into
tinygo-org:devfrom
davecheney:godebug-override
Draft

davecheney wants to merge 1 commit into
tinygo-org:devfrom
davecheney:godebug-override

Conversation

@davecheney

@davecheney davecheney commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5835.

TinyGo does not read GODEBUG at startup. A setting only changes when the program calls os.Setenv("GODEBUG", ...). But the upstream internal/godebug keeps its cache in sync.Map and sync.Once, and both use defer. The interp pass cannot run functions with defer frames, which the compiler emits on all targets that support recover(), so godebug.Value is left for run time. Because of this, crypto/internal/fips140.Enabled is not known at compile time, and every CAST self-test closure (SHA-3, AES, GCM, SHA-2, HMAC, DRBG) stays in the binary when net/http is imported. A small TinyGo version of the package gives the same values without that machinery, so interp folds Enabled to false and the closures are removed.

Results

Target waveshare-rp2350-pizero with the reproducer from #5835. Both builds use the same source tree, with and without this change. "fips140 symbols" counts every symbol in the ELF whose name contains fips140.

scheduler opt flash before flash after fips140 symbols before fips140 symbols after
cores s 129440 75584 133 (42766 bytes) 0
cores 1 174460 115312 119 (46930 bytes) 0
tasks s 122496 69072 131 (42858 bytes) 0
tasks 1 166916 108408 119 (46326 bytes) 0

All crypto/internal/fips140 symbols are removed in every configuration. This includes the CAST self-test closures and the code that only they use: fips140.CAST, sha3.keccakF1600Generic (10734 bytes at -opt=s), and the init#1$1 closures in aes, aes/gcm, drbg, hmac, sha256, sha3 and sha512. It also includes the related functions, strings, and type data in fips140, subtle, check and fips140deps/byteorder.

Reviewer notes

The bisect #pattern suffix is ignored, IncNonDefault does nothing, and an unknown setting name does not panic. TinyGo did not support metrics or bisect before this change either. The exported API is the same in Go 1.25, 1.26 and 1.27. archive/zip, net/url, mime/multipart and crypto/ecdsa tests pass on darwin/arm64.

…are removed

Fixes tinygo-org#5835. TinyGo does not read GODEBUG at startup. A setting only changes when the program calls os.Setenv("GODEBUG", ...). But the upstream internal/godebug keeps its cache in sync.Map and sync.Once, and both use defer. The interp pass cannot run functions with defer frames on setjmp targets, so godebug.Value is left for run time. Because of this, crypto/internal/fips140.Enabled is not known at compile time, and every CAST self-test closure (SHA-3, AES, GCM, SHA-2, HMAC, DRBG) stays in the binary when net/http is imported. A small TinyGo version of the package gives the same values without that machinery, so interp folds Enabled to false and the closures are removed.

On waveshare-rp2350-pizero with the reproducer from tinygo-org#5835, flash use goes from 129440 to 75584 bytes (-scheduler=cores -opt=s) and from 122496 to 69072 bytes (-scheduler=tasks -opt=s).

Reviewer notes: The bisect #pattern suffix is ignored, IncNonDefault does nothing, and an unknown setting name does not panic. TinyGo did not support metrics or bisect before this change either. The exported API is the same in Go 1.25, 1.26 and 1.27. archive/zip, net/url, mime/multipart and crypto/ecdsa tests pass on darwin/arm64.

Deferred: the runtime Setenv hooks call update("GODEBUG", value) where upstream passes the default GODEBUG string as the first argument. The interp limits with defer frames and llvm.memset are a separate and larger change.
@davecheney

davecheney commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

/cc @jakebailey I know this runs contrary to your "make more of the stdlib pass" work, but the savings by being able to statically eliminate all the crypto code kept alive by the FIPS-140 yak shave are worth the pain I think

/cc @deadprogram as the keeper of interp.

@jakebailey

Copy link
Copy Markdown
Member

TinyGo does not read GODEBUG at startup

Yes, I do quite literally have a change I haven't sent yet which makes this work as it's required to test things...

I think I want to look to see if something like this PR is possible after my change. I could just send it now and we can cross compare

@jakebailey

Copy link
Copy Markdown
Member

#5885 is the code I had; maybe for baremetal we can just say that one can't set anything at runtime and do this?

@davecheney

Copy link
Copy Markdown
Contributor Author

#5885 is the code I had; maybe for baremetal we can just say that one can't set anything at runtime and do this?

Yeah, that’s an idea, for baremetal make godebug read only, I’ll play with it this evening to see if i can do this with an overlay

@davecheney

Copy link
Copy Markdown
Contributor Author

#5885 is the code I had; maybe for baremetal we can just say that one can't set anything at runtime and do this?

@davecheney davecheney closed this Oct 16, 2026
@davecheney davecheney reopened this Oct 16, 2026
davecheney added a commit to davecheney/tinygo that referenced this pull request Oct 22, 2026
Without tinygo-org#5883 the size of any program using a godebug setting changes
with the Go version, so these savings could not be tracked. With it,
math/rand drops from about 15.3K/10.3K to 3857/1727 code/rodata bytes
on cortex-m-qemu at opt=z, and the size is the same on Go 1.25 to 1.27.
@davecheney
davecheney marked this pull request as draft November 1, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

initAll retains crypto/internal/fips140/sha3.keccakF1600Generic from an unused HTTP import

2 participants