Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: reduce static stack guard size #51256

Open
cherrymui opened this issue Feb 18, 2022 · 5 comments
Open

runtime: reduce static stack guard size #51256

cherrymui opened this issue Feb 18, 2022 · 5 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@cherrymui
Copy link
Member

cherrymui commented Feb 18, 2022

What version of Go are you using (go version)?

tip (eaf0405)

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

darwin/amd64

What did you do?

In cmd/internal/objabi and runtime we have stackGuardMultiplierDefault, which is normally 1 but set to 2 by cmd/dist if the GO_GCFLAGS includes -N when building the toolchain and the standard library at make.bash(bat/rc) time. The idea is that for a no-opt build where the functions consume more stack space, we double the stack guard to avoid nosplit overflow.

But I suspect most users who use -N -l build do not build the toolchain and the standard library this way. It is more likely that one would build the toolchain normally but use -gcflags=all="-N -l" on the command line (at least since the introduce of build cache and automatic rebuild of the standard library packages). This way, stackGuardMultiplierDefault is still 1. So we're effectively keeping -N -l build work with the non-doubled stack guard, which sometimes is difficult.

What did you expect to see?

Have some way to double the stack guard with -N -l build that is actually useful. I can think of a few possibilities:

  1. double the stack guard if -N is specified. Requires -N to be used for all packages or nothing. And needs some magic way to set the value in the runtime and tell the linker.
  2. double the stack guard if -N is specified when compiling the runtime. The runtime and the linker will use the doubled stack guard (needs the same magic way). When compiling other packages it may still use the smaller one, but it probably doesn't hurt (besides slightly more frequent stack copying).
  3. define a noopt (or debug or something) flag for the go command that applies -N -l to all packages and double the stack guard. Deprecate the direct use of -N -l.
  4. make -N no-op for a few more nosplit-heavy packages. It is already no-op for the runtime package. Maybe it can also apply to reflect and syscall. (I suspect most times the user who use -N is not debugging those packages)

Maybe there are other options.

Or, drop stackGuardMultiplierDefault and keep -N -l build work with the non-doubled stack guard. Again, this is sometimes difficult.

cc @aclements

@dmitshur dmitshur added this to the Backlog milestone Feb 18, 2022
@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Feb 18, 2022
@aclements
Copy link
Member

Stepping back, unless I'm mistaken, it seems like it's not necessarily safe to mix different size stack guards in the same process. If a splittable function in a package compiled with a small stack guard calls a nosplit function in a package compiled with a large stack guard, this could corrupt the stack. I think this means option 2 doesn't quite work.

I suppose the linker's stack size check could check for this, but it looks like that uses the dist-baked stack guard, so it isn't affected by -N and has no notion that different functions could ensure different guard zones are left. I think this means the linker's check is always conservative right now, which prevents us from corrupting the stack, but can cause surprise build failures.

I certainly agree that it seems like stackGuardMultiplierDefault is a footgun at best.

I'm slightly leaning toward option 4, but I worry that this whole mechanism is getting shaky.

@cherrymui
Copy link
Member Author

Yeah, I agree option 2 doesn't work. It might work if the run-time check is more conservative (using larger stack guard) than the actual limit, but that doesn't help anything.

I think this means the linker's check is always conservative right now, which prevents us from corrupting the stack, but can cause surprise build failures.

Yes, this is what is happening now. It is safe. But sometimes we need to work quite hard to fit things into the threshold with -N build, e.g. #45698, #45658, #51247.

Yeah, option 4 isn't ideal. E.g. in the future we may add nosplit functions in other places. Also, what about -l? There are quite a few simple helper functions (for better readability) which would normally be inlined but could use some stack in -l build.

@aclements
Copy link
Member

@cherrymui and I were just chatting about this. I charged her to think about whether we could eliminate or at least significantly reduce the static stack guard size. She proposed an interesting solution: when you're about to make a splittable -> nosplit transition, before setting up the arguments to the nosplit call, grow the stack to ensure there's enough space for the nosplit chain. It would be pretty easy for the linker to patch in the necessary stack depth (since only it really knows the depth of nosplit chains).

We'd have to be careful about nosplit functions put into closures (and perhaps those that have their PC taken). These should be pretty rare. Perhaps for those we keep a static stack guard size and any nosplit function reference that isn't part of a "grow stack and call" sequence gets checked against the static guard, like we do now.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Jul 7, 2022
@gopherbot
Copy link

Change https://go.dev/cl/416859 mentions this issue: cmd/dist: force stackGuardMultiplierDefault to 1

@rsc
Copy link
Contributor

rsc commented Aug 4, 2022

Sent CL 416859 to remove stackGuardMultiplierDefault, but this issue should remain open to track what @aclements wrote above, namely "whether we could eliminate or at least significantly reduce the static stack guard size".

@rsc rsc changed the title cmd/dist, cmd/internal/objabi, runtime: stackGuardMultiplierDefault is not useful runtime: reduce static stack guard size Aug 4, 2022
gopherbot pushed a commit that referenced this issue Aug 5, 2022
Nothing seems to break, not even the noopt builder.

For #51256 (the conversation there is headed toward additional changes).

Change-Id: Icb7ca451159a74f351c25d2cefb32c773b9bb017
Reviewed-on: https://go-review.googlesource.com/c/go/+/416859
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
jproberts pushed a commit to jproberts/go that referenced this issue Aug 10, 2022
Nothing seems to break, not even the noopt builder.

For golang#51256 (the conversation there is headed toward additional changes).

Change-Id: Icb7ca451159a74f351c25d2cefb32c773b9bb017
Reviewed-on: https://go-review.googlesource.com/c/go/+/416859
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
Status: Triage Backlog
Development

No branches or pull requests

6 participants
@rsc @dmitshur @aclements @gopherbot @cherrymui and others