Skip to content

runtime: fully implement GOROOT and GODEBUG - #5885

Open
jakebailey wants to merge 4 commits into
tinygo-org:devfrom
jakebailey:runtime-godebug-environment
Open

jakebailey wants to merge 4 commits into
tinygo-org:devfrom
jakebailey:runtime-godebug-environment

Conversation

@jakebailey

Copy link
Copy Markdown
Member

This captures GOROOT and GODEBUG from the env at startup, then keeps things up to date when they're updated at runtime.

Eventually enables some stdlib testing, though they can't yet be enabled by this PR alone.

@jakebailey

Copy link
Copy Markdown
Member Author

Hm, I have some work to do, then

@davecheney

Copy link
Copy Markdown
Contributor

@jakebailey thank you for working on this. So I'm on the same page, is the new optimisation pass aimed at removing the FIPS code that is reachable through runtime.GODEBUG?

@jakebailey

Copy link
Copy Markdown
Member Author

No, your whole-module replacement gets rid of a lot more since all I'm working on is what's upstream. Haven't really had the time to look into something that combines the two, if such a thing is even possible.

@davecheney

davecheney commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

No, your whole-module replacement gets rid of a lot more since all I'm working on is what's upstream. Haven't really had the time to look into something that combines the two, if such a thing is even possible.

Thank you for adding

https://github.com/tinygo-org/tinygo/pull/5885/changes#diff-11dbd6d9eac4e1ab5f407bf7e6918cf1e7bb1c0501a031c2807176f1071a18d1R212

The issue is this only removes the GODEBUG support when no reader survives, and the FIPS code provides such a reader because Settings.Value isn't a compile time constant on bare metal.

What if we were try something like this

  1. in this PR, remove the transform pass
  2. I'll stack a branch on this one that provides a bare metal Settings.Value that interp can evaluate at runtime -- basically its read only, which will collapse that branch and DCE should be a able to remove the rest of the code from init.

@jakebailey

Copy link
Copy Markdown
Member Author

The transform pass is actually required to make the PR work, unfortunately...

@jakebailey

Copy link
Copy Markdown
Member Author

Seems like with some improvements to interp, the existing godebug package can actually be trimmed away without being replaced, maybe. Screwing around with it

@davecheney

Copy link
Copy Markdown
Contributor

The transform pass is actually required to make the PR work, unfortunately...

opps, my mistake. The transform pass works around interps inability to see through Settings.Value because the defer and the atomic. Even if I propose a read only Settings.Value for bare metal, this won't solve for non bare metal builds. Mia culpa, please ignore my first suggestion.

@jakebailey

Copy link
Copy Markdown
Member Author

Can you see if https://github.com/jakebailey/tinygo/tree/interp-init-evaluation does the trick? or rather, this PR + that branch maybe 😄

@jakebailey
jakebailey force-pushed the runtime-godebug-environment branch from 35b4365 to 9393554 Compare October 22, 2026 00:19
@davecheney davecheney added this to the 0.44 milestone Oct 22, 2026
@deadprogram

Copy link
Copy Markdown
Member

@jakebailey can you please rebase and resolve merge confict? Thanks!

@jakebailey
jakebailey force-pushed the runtime-godebug-environment branch from 9393554 to 153c83b Compare October 23, 2026 16:16
@deadprogram

Copy link
Copy Markdown
Member

Sorry @jakebailey just one more merge conflict, please. 😸

@jakebailey
jakebailey force-pushed the runtime-godebug-environment branch from 153c83b to 47a1253 Compare October 26, 2026 03:41
Comment thread src/runtime/nonhosted.go

if gorootEnvEnabled() {
if value := startupEnv("GOROOT"); value != "" {
goroot = value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This puts the feature-gate calls in the same initializer that parses osArgs and osEnv. Interp runs before the optimizer resolves these gates, so it cannot interpret this initializer and leaves the parsing code to run at startup. Could we split the gate checks into a second initializer?

 		env = append(env, s[start:])
 	}
+}
+
+func init() {
 	if gorootEnvEnabled() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed that, but not really clear it did much?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I’ll test.

@deadprogram deadprogram modified the milestones: 0.44, 0.45 Oct 27, 2026
@jakebailey
jakebailey force-pushed the runtime-godebug-environment branch from 47a1253 to 898fee5 Compare October 29, 2026 02:24
@davecheney

davecheney commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Thanks @jakebailey. The rebase keeps the interp work intact. I found one remaining regression: with -scheduler=cores, interp no longer removes the FIPS self-tests.

package main

import _ "crypto/sha256"

func main() {}

tinygo build -size short -target pico -scheduler=cores . increases flash from 10,560 bytes on dev to 81,704 bytes on this PR. The cause appears to be that godebug.Value in fips140.init#1 can no longer be evaluated at compile time.

@jakebailey
jakebailey force-pushed the runtime-godebug-environment branch from 898fee5 to bc532bb Compare October 29, 2026 04:39
@jakebailey
jakebailey requested a review from davecheney October 29, 2026 04:40
@davecheney

Copy link
Copy Markdown
Contributor

I revalidated the latest PR head, bc532bbd, against dev at 344590c7. The rebase keeps the interp work.

With Go 1.27.3, the crypto repro from my earlier comment matches dev:

package main

import _ "crypto/sha256"

func main() {}

Built for Pico with -scheduler=cores, this program is 10,560 bytes on both trees. The PR build has no FIPS init functions.

There is a separate regression with Go 1.25.14. This program grows from 8,824 bytes on dev to 28,800 bytes on the PR when built for Pico:

package main

import "time"

func main() {
    time.Sleep(time.Second)
}

Go 1.25's time package registers the asynctimerchan GODEBUG setting. Because of this, the PR build keeps internal/godebug, internal/bisect and reflectlite. With Go 1.27.3, both trees build this program to 8,824 bytes.

This also explains the two CI failures, because both jobs use Go 1.25. sizediff reports flash and RAM overflows for the arduino hd44780/keypadshield example. test-compat fails on arduino-uno blinky1 with LLVM 15 and "Cannot generate unaligned atomic store". I reproduced both failures locally with Go 1.25.14 on the PR, but not on dev.

If Go 1.25 is no longer supported for 0.45, I think we can accept this regression after the minimum Go version and the CI toolchains move to 1.26, as tracked in #5984.

@jakebailey

Copy link
Copy Markdown
Member Author

Was already looking; try again.

Not happy about the extra passes though.

@davecheney

davecheney commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Was already looking; try again.

Not happy about the extra passes though.

If you can wait for #6084 to land then this might fix itself.

davecheney added a commit to davecheney/tinygo that referenced this pull request Oct 29, 2026
Go 1.25 is no longer supported upstream. Running these jobs with Go 1.26 lets tinygo-org#5885 land without failures caused only by Go 1.25 GODEBUG settings.
Go defines runtime.GOROOT from the process environment captured at
startup, with the build-time root as a fallback. TinyGo only used the
build-time value, so binaries could not select a relocated toolchain root.

Capture GOROOT before user package initialization and retain that value
after later environment changes.
internal/godebug needs both the compiled defaults and the current
environment value when it registers with the runtime. Without that initial
state, package behavior can differ from the selected Go version and startup
environment until the process changes GODEBUG.

Pass the main package defaults from go list to the runtime and retain
GODEBUG changes that happen before internal/godebug registers.
Removing GODEBUG must immediately expose the compiled defaults again.
TinyGo did not notify internal/godebug for every unset path, which let an
old environment override remain cached after cleanup or Clearenv.
GOROOT and GODEBUG startup support retains environment access even when
programs do not use either feature. Go 1.25 also registers the GODEBUG
updater without any setting reads, retaining its parser and atomic
operations. This adds imports and code to small WASI and bare-metal
programs and prevents AVR builds with LLVM 15.

Unused startup support should not impose those costs on programs that
never read the settings. Startup settings must also remain available to
compile-time initialization so disabled FIPS self-tests do not survive
in multicore builds.
@jakebailey
jakebailey force-pushed the runtime-godebug-environment branch from 6545b50 to 2b2958a Compare October 30, 2026 19:11
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.

3 participants