From 6ab06553cb0bfdfd189a63583eeaaf4b39ecc1f2 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Wed, 14 Oct 2026 22:12:40 +0900 Subject: [PATCH] internal/godebug: let interp fold settings so unused FIPS self-tests are removed 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 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 #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. --- loader/goroot.go | 1 + src/internal/godebug/godebug.go | 83 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/internal/godebug/godebug.go diff --git a/loader/goroot.go b/loader/goroot.go index 0aab0a0e13..456f807db4 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -245,6 +245,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/cm/": false, "internal/futex/": false, "internal/fuzz/": false, + "internal/godebug/": false, "internal/itoa/": false, "internal/poll/": false, "internal/reflectlite/": false, diff --git a/src/internal/godebug/godebug.go b/src/internal/godebug/godebug.go new file mode 100644 index 0000000000..c758bb0e75 --- /dev/null +++ b/src/internal/godebug/godebug.go @@ -0,0 +1,83 @@ +// Package godebug is a simplified version of the upstream internal/godebug. +// TinyGo only sees GODEBUG changes made with os.Setenv. +package godebug + +import _ "unsafe" // go:linkname + +// A Setting is a single setting in the $GODEBUG environment variable. +type Setting struct { + name string +} + +// The current value of the $GODEBUG environment variable. +var env string + +// New returns a new Setting for the $GODEBUG setting with the given name. +// A name with a # prefix is an undocumented setting. +func New(name string) *Setting { + return &Setting{name: name} +} + +// Name returns the name of the setting. +func (s *Setting) Name() string { + if s.name != "" && s.name[0] == '#' { + return s.name[1:] + } + return s.name +} + +// Undocumented reports whether this is an undocumented setting. +func (s *Setting) Undocumented() bool { + return s.name != "" && s.name[0] == '#' +} + +// String returns a printable form for the setting: name=value. +func (s *Setting) String() string { + return s.Name() + "=" + s.Value() +} + +// IncNonDefault does nothing because TinyGo has no runtime/metrics support. +func (s *Setting) IncNonDefault() {} + +// Value returns the current value for the GODEBUG setting s. +func (s *Setting) Value() string { + return lookup(env, s.Name()) +} + +// lookup returns the value of name in s. Later settings win and a #pattern +// suffix for bisect is ignored. +func lookup(s, name string) string { + end := len(s) + eq := -1 + for i := end - 1; i >= -1; i-- { + if i == -1 || s[i] == ',' { + if eq >= 0 && s[i+1:eq] == name { + arg := s[eq+1 : end] + for j := 0; j < len(arg); j++ { + if arg[j] == '#' { + return arg[:j] + } + } + return arg + } + eq = -1 + end = i + } else if s[i] == '=' { + eq = i + } + } + return "" +} + +//go:linkname setUpdate +func setUpdate(update func(string, string)) + +func init() { + setUpdate(update) +} + +// update is called by the runtime when the GODEBUG environment variable +// changes. +func update(_, e string) { + env = e +}