]> Cypherpunks repositories - gostls13.git/commitdiff
internal/goexperiment,cmd: consolidate GOEXPERIMENTs into a new package
authorAustin Clements <austin@google.com>
Tue, 6 Apr 2021 12:25:01 +0000 (08:25 -0400)
committerAustin Clements <austin@google.com>
Thu, 8 Apr 2021 02:17:16 +0000 (02:17 +0000)
Currently there's knowledge about the list of GOEXPERIMENTs in a few
different places. This CL introduces a new package and consolidates
the list into one place: the internal/goexperiment.Flags struct type.

This package gives us a central place to document the experiments as
well as the GOEXPERIMENT environment variable itself. It will also
give us a place to put built-time constants derived from the enabled
experiments.

Now the objabi package constructs experiment names by reflecting over
this struct type rather than having a separate list of these names
(this is similar to how the compiler handles command-line flags and
debug options). We also expose a better-typed API to the toolchain for
propagating enabled experiments.

Change-Id: I06e026712b59fe2bd7cd11a869aedb48ffe5a4b7
Reviewed-on: https://go-review.googlesource.com/c/go/+/307817
Trust: Austin Clements <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
src/cmd/asm/internal/lex/input.go
src/cmd/dist/buildtool.go
src/cmd/go/internal/work/exec.go
src/cmd/go/internal/work/init.go
src/cmd/internal/objabi/exp.go [new file with mode: 0644]
src/cmd/internal/objabi/util.go
src/internal/goexperiment/flags.go [new file with mode: 0644]

index 8aa6becf5524c98957ddbc61af0e534bfd4543ae..aa03759c7d2908a7b4bf0eaf32cf7b07587cfd2b 100644 (file)
@@ -46,31 +46,18 @@ func NewInput(name string) *Input {
 func predefine(defines flags.MultiFlag) map[string]*Macro {
        macros := make(map[string]*Macro)
 
-       // Set macros for various GOEXPERIMENTs so we can easily
-       // switch runtime assembly code based on them.
+       // Set macros for GOEXPERIMENTs so we can easily switch
+       // runtime assembly code based on them.
        if *flags.CompilingRuntime {
-               set := func(name string) {
+               for _, exp := range objabi.EnabledExperiments() {
+                       // Define macro.
+                       name := "GOEXPERIMENT_" + exp
                        macros[name] = &Macro{
                                name:   name,
                                args:   nil,
                                tokens: Tokenize("1"),
                        }
                }
-               if objabi.Experiment.RegabiWrappers {
-                       set("GOEXPERIMENT_regabiwrappers")
-               }
-               if objabi.Experiment.RegabiG {
-                       set("GOEXPERIMENT_regabig")
-               }
-               if objabi.Experiment.RegabiReflect {
-                       set("GOEXPERIMENT_regabireflect")
-               }
-               if objabi.Experiment.RegabiDefer {
-                       set("GOEXPERIMENT_regabidefer")
-               }
-               if objabi.Experiment.RegabiArgs {
-                       set("GOEXPERIMENT_regabiargs")
-               }
        }
 
        for _, name := range defines {
index 7520b0ef1864847d08286d6f8e0380c7129754f3..44b18869681ab41f1ac00a62e4a2beea7de29e9a 100644 (file)
@@ -58,6 +58,7 @@ var bootstrapDirs = []string{
        "debug/macho",
        "debug/pe",
        "go/constant",
+       "internal/goexperiment",
        "internal/goversion",
        "internal/race",
        "internal/unsafeheader",
index 11b7360d26b0a375ae7702b82f53a5a4d64c50b3..f692f386f536e54a793ca2eba1b1313991ae46e1 100644 (file)
@@ -277,8 +277,8 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
                key, val := cfg.GetArchEnv()
                fmt.Fprintf(h, "%s=%s\n", key, val)
 
-               if objabi.GOEXPERIMENT != "" {
-                       fmt.Fprintf(h, "GOEXPERIMENT=%q\n", objabi.GOEXPERIMENT)
+               if goexperiment := objabi.GOEXPERIMENT(); goexperiment != "" {
+                       fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment)
                }
 
                // TODO(rsc): Convince compiler team not to add more magic environment variables,
@@ -1251,8 +1251,8 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) {
                key, val := cfg.GetArchEnv()
                fmt.Fprintf(h, "%s=%s\n", key, val)
 
-               if objabi.GOEXPERIMENT != "" {
-                       fmt.Fprintf(h, "GOEXPERIMENT=%q\n", objabi.GOEXPERIMENT)
+               if goexperiment := objabi.GOEXPERIMENT(); goexperiment != "" {
+                       fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment)
                }
 
                // The linker writes source file paths that say GOROOT_FINAL, but
index 30c9a2b7cca440a03680e1b13697862bfcb4f7ef..1e1494998a96c196bb38ebe5155175096fd47f5c 100644 (file)
@@ -18,7 +18,6 @@ import (
        "os"
        "path/filepath"
        "runtime"
-       "strings"
 )
 
 func BuildInit() {
@@ -53,7 +52,7 @@ func BuildInit() {
        // used for compiling alternative files for the experiment. This allows
        // changes for the experiment, like extra struct fields in the runtime,
        // without affecting the base non-experiment code at all.
-       for _, expt := range strings.Split(objabi.GOEXPERIMENT, ",") {
+       for _, expt := range objabi.EnabledExperiments() {
                cfg.BuildContext.BuildTags = append(cfg.BuildContext.BuildTags, "goexperiment."+expt)
        }
 }
diff --git a/src/cmd/internal/objabi/exp.go b/src/cmd/internal/objabi/exp.go
new file mode 100644 (file)
index 0000000..21a70d5
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package objabi
+
+import (
+       "fmt"
+       "os"
+       "reflect"
+       "strings"
+
+       "internal/goexperiment"
+)
+
+// Experiment contains the toolchain experiments enabled for the
+// current build.
+//
+// (This is not necessarily the set of experiments the compiler itself
+// was built with.)
+var Experiment goexperiment.Flags
+
+var defaultExpstring string // Set by package init
+
+// FramePointerEnabled enables the use of platform conventions for
+// saving frame pointers.
+//
+// This used to be an experiment, but now it's always enabled on
+// platforms that support it.
+//
+// Note: must agree with runtime.framepointer_enabled.
+var FramePointerEnabled = GOARCH == "amd64" || GOARCH == "arm64"
+
+func init() {
+       // Capture "default" experiments.
+       defaultExpstring = Expstring()
+
+       goexperiment := envOr("GOEXPERIMENT", defaultGOEXPERIMENT)
+
+       // GOEXPERIMENT=none overrides all experiments enabled at dist time.
+       if goexperiment != "none" {
+               // Create a map of known experiment names.
+               names := make(map[string]reflect.Value)
+               rv := reflect.ValueOf(&Experiment).Elem()
+               rt := rv.Type()
+               for i := 0; i < rt.NumField(); i++ {
+                       field := rv.Field(i)
+                       names[strings.ToLower(rt.Field(i).Name)] = field
+               }
+
+               // Parse names.
+               for _, f := range strings.Split(goexperiment, ",") {
+                       if f == "" {
+                               continue
+                       }
+                       val := true
+                       if strings.HasPrefix(f, "no") {
+                               f, val = f[2:], false
+                       }
+                       field, ok := names[f]
+                       if !ok {
+                               fmt.Printf("unknown experiment %s\n", f)
+                               os.Exit(2)
+                       }
+                       field.SetBool(val)
+               }
+       }
+
+       // regabi is only supported on amd64.
+       if GOARCH != "amd64" {
+               Experiment.Regabi = false
+               Experiment.RegabiWrappers = false
+               Experiment.RegabiG = false
+               Experiment.RegabiReflect = false
+               Experiment.RegabiDefer = false
+               Experiment.RegabiArgs = false
+       }
+       // Setting regabi sets working sub-experiments.
+       if Experiment.Regabi {
+               Experiment.RegabiWrappers = true
+               Experiment.RegabiG = true
+               Experiment.RegabiReflect = true
+               Experiment.RegabiDefer = true
+               // Not ready yet:
+               //Experiment.RegabiArgs = true
+       }
+       // Check regabi dependencies.
+       if Experiment.RegabiG && !Experiment.RegabiWrappers {
+               panic("GOEXPERIMENT regabig requires regabiwrappers")
+       }
+       if Experiment.RegabiArgs && !(Experiment.RegabiWrappers && Experiment.RegabiG && Experiment.RegabiReflect && Experiment.RegabiDefer) {
+               panic("GOEXPERIMENT regabiargs requires regabiwrappers,regabig,regabireflect,regabidefer")
+       }
+}
+
+// expList returns the list of enabled GOEXPERIMENTs names.
+func expList(flags *goexperiment.Flags) []string {
+       var list []string
+       rv := reflect.ValueOf(&Experiment).Elem()
+       rt := rv.Type()
+       for i := 0; i < rt.NumField(); i++ {
+               val := rv.Field(i).Bool()
+               if val {
+                       field := rt.Field(i)
+                       list = append(list, strings.ToLower(field.Name))
+               }
+       }
+       return list
+}
+
+// Expstring returns the GOEXPERIMENT string that should appear in Go
+// version signatures. This always starts with "X:".
+func Expstring() string {
+       list := expList(&Experiment)
+       if len(list) == 0 {
+               return "X:none"
+       }
+       return "X:" + strings.Join(list, ",")
+}
+
+// GOEXPERIMENT returns a comma-separated list of enabled experiments.
+// This is derived from the GOEXPERIMENT environment variable if set,
+// or the value of GOEXPERIMENT when make.bash was run if not.
+func GOEXPERIMENT() string {
+       return strings.Join(expList(&Experiment), ",")
+}
+
+// EnabledExperiments returns a list of enabled experiments, as
+// lower-cased experiment names.
+func EnabledExperiments() []string {
+       return expList(&Experiment)
+}
index ca3d3fc1a3269acffb177c879e9c5b210231d770..76c56dab275532d70644e6754e91cc9b115e849f 100644 (file)
@@ -5,7 +5,6 @@
 package objabi
 
 import (
-       "fmt"
        "log"
        "os"
        "strings"
@@ -32,12 +31,6 @@ var (
        GOWASM   = gowasm()
        GO_LDSO  = defaultGO_LDSO
        Version  = version
-
-       // GOEXPERIMENT is a comma-separated list of enabled
-       // experiments. This is derived from the GOEXPERIMENT
-       // environment variable if set, or the value of GOEXPERIMENT
-       // when make.bash was run if not.
-       GOEXPERIMENT string // Set by package init
 )
 
 const (
@@ -128,175 +121,3 @@ func gowasm() (f gowasmFeatures) {
 func Getgoextlinkenabled() string {
        return envOr("GO_EXTLINK_ENABLED", defaultGO_EXTLINK_ENABLED)
 }
-
-func init() {
-       // Capture "default" experiments.
-       defaultExpstring = Expstring()
-
-       goexperiment := envOr("GOEXPERIMENT", defaultGOEXPERIMENT)
-
-       // GOEXPERIMENT=none overrides all experiments enabled at dist time.
-       if goexperiment != "none" {
-               for _, f := range strings.Split(goexperiment, ",") {
-                       if f != "" {
-                               addexp(f)
-                       }
-               }
-       }
-
-       // regabi is only supported on amd64.
-       if GOARCH != "amd64" {
-               Experiment.regabi = false
-               Experiment.RegabiWrappers = false
-               Experiment.RegabiG = false
-               Experiment.RegabiReflect = false
-               Experiment.RegabiDefer = false
-               Experiment.RegabiArgs = false
-       }
-       // Setting regabi sets working sub-experiments.
-       if Experiment.regabi {
-               Experiment.RegabiWrappers = true
-               Experiment.RegabiG = true
-               Experiment.RegabiReflect = true
-               Experiment.RegabiDefer = true
-               // Not ready yet:
-               //Experiment.RegabiArgs = true
-       }
-       // Check regabi dependencies.
-       if Experiment.RegabiG && !Experiment.RegabiWrappers {
-               panic("GOEXPERIMENT regabig requires regabiwrappers")
-       }
-       if Experiment.RegabiArgs && !(Experiment.RegabiWrappers && Experiment.RegabiG && Experiment.RegabiReflect && Experiment.RegabiDefer) {
-               panic("GOEXPERIMENT regabiargs requires regabiwrappers,regabig,regabireflect,regabidefer")
-       }
-
-       // Set GOEXPERIMENT to the parsed and canonicalized set of experiments.
-       GOEXPERIMENT = expList()
-}
-
-// FramePointerEnabled enables the use of platform conventions for
-// saving frame pointers.
-//
-// This used to be an experiment, but now it's always enabled on
-// platforms that support it.
-//
-// Note: must agree with runtime.framepointer_enabled.
-var FramePointerEnabled = GOARCH == "amd64" || GOARCH == "arm64"
-
-func addexp(s string) {
-       // We could do general integer parsing here, but there's no need yet.
-       v, vb := 1, true
-       name := s
-       if len(name) > 2 && name[:2] == "no" {
-               v, vb = 0, false
-               name = name[2:]
-       }
-       for i := 0; i < len(exper); i++ {
-               if exper[i].name == name {
-                       switch val := exper[i].val.(type) {
-                       case *int:
-                               *val = v
-                       case *bool:
-                               *val = vb
-                       default:
-                               panic("bad GOEXPERIMENT type for " + s)
-                       }
-                       return
-               }
-       }
-
-       fmt.Printf("unknown experiment %s\n", s)
-       os.Exit(2)
-}
-
-// Experiment contains flags for GOEXPERIMENTs.
-var Experiment = ExpFlags{}
-
-type ExpFlags struct {
-       FieldTrack        bool
-       PreemptibleLoops  bool
-       StaticLockRanking bool
-
-       // regabi is split into several sub-experiments that can be
-       // enabled individually. GOEXPERIMENT=regabi implies the
-       // subset that are currently "working". Not all combinations work.
-       regabi bool
-       // RegabiWrappers enables ABI wrappers for calling between
-       // ABI0 and ABIInternal functions. Without this, the ABIs are
-       // assumed to be identical so cross-ABI calls are direct.
-       RegabiWrappers bool
-       // RegabiG enables dedicated G and zero registers in
-       // ABIInternal.
-       //
-       // Requires wrappers because it makes the ABIs incompatible.
-       RegabiG bool
-       // RegabiReflect enables the register-passing paths in
-       // reflection calls. This is also gated by intArgRegs in
-       // reflect and runtime (which are disabled by default) so it
-       // can be used in targeted tests.
-       RegabiReflect bool
-       // RegabiDefer enables desugaring defer and go calls
-       // into argument-less closures.
-       RegabiDefer bool
-       // RegabiArgs enables register arguments/results in all
-       // compiled Go functions.
-       //
-       // Requires wrappers (to do ABI translation), g (because
-       // runtime assembly that's been ported to ABIInternal uses the
-       // G register), reflect (so reflection calls use registers),
-       // and defer (because the runtime doesn't support passing
-       // register arguments to defer/go).
-       RegabiArgs bool
-}
-
-// Toolchain experiments.
-// These are controlled by the GOEXPERIMENT environment
-// variable recorded when the toolchain is built.
-var exper = []struct {
-       name string
-       val  interface{} // Must be *int or *bool
-}{
-       {"fieldtrack", &Experiment.FieldTrack},
-       {"preemptibleloops", &Experiment.PreemptibleLoops},
-       {"staticlockranking", &Experiment.StaticLockRanking},
-       {"regabi", &Experiment.regabi},
-       {"regabiwrappers", &Experiment.RegabiWrappers},
-       {"regabig", &Experiment.RegabiG},
-       {"regabireflect", &Experiment.RegabiReflect},
-       {"regabidefer", &Experiment.RegabiDefer},
-       {"regabiargs", &Experiment.RegabiArgs},
-}
-
-var defaultExpstring string
-
-// expList returns the list of enabled GOEXPERIMENTS as a
-// commas-separated list.
-func expList() string {
-       buf := ""
-       for i := range exper {
-               switch val := exper[i].val.(type) {
-               case *int:
-                       if *val != 0 {
-                               buf += "," + exper[i].name
-                       }
-               case *bool:
-                       if *val {
-                               buf += "," + exper[i].name
-                       }
-               }
-       }
-       if len(buf) == 0 {
-               return ""
-       }
-       return buf[1:]
-}
-
-// Expstring returns the GOEXPERIMENT string that should appear in Go
-// version signatures. This always starts with "X:".
-func Expstring() string {
-       list := expList()
-       if list == "" {
-               return "X:none"
-       }
-       return "X:" + list
-}
diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go
new file mode 100644 (file)
index 0000000..46800b4
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package goexperiment implements support for toolchain experiments.
+//
+// Toolchain experiments are controlled by the GOEXPERIMENT
+// environment variable. GOEXPERIMENT is a comma-separated list of
+// experiment names. GOEXPERIMENT can be set at make.bash time, which
+// sets the default experiments for binaries built with the tool
+// chain; or it can be set at build time. GOEXPERIMENT can also be set
+// to "none", which disables any experiments that were enabled at
+// make.bash time.
+//
+// Experiments are exposed to the build in the following ways:
+//
+// - Build tag goexperiment.x is set if experiment x (lower case) is
+// enabled.
+//
+// - In runtime assembly, the macro GOEXPERIMENT_x is defined if
+// experiment x (lower case) is enabled.
+//
+// - TODO(austin): More to come.
+//
+// In the toolchain, the set of experiments enabled for the current
+// build should be accessed via objabi.Experiment.
+//
+// For the set of experiments supported by the current toolchain, see
+// go doc internal/experiment.Flags.
+package goexperiment
+
+// Flags is the set of experiments that can be enabled or disabled in
+// the current toolchain.
+//
+// When specified in the GOEXPERIMENT environment variable or as build
+// tags, experiments use the strings.ToLower of their field name.
+type Flags struct {
+       FieldTrack        bool
+       PreemptibleLoops  bool
+       StaticLockRanking bool
+
+       // Regabi is split into several sub-experiments that can be
+       // enabled individually. GOEXPERIMENT=regabi implies the
+       // subset that are currently "working". Not all combinations work.
+       Regabi bool
+       // RegabiWrappers enables ABI wrappers for calling between
+       // ABI0 and ABIInternal functions. Without this, the ABIs are
+       // assumed to be identical so cross-ABI calls are direct.
+       RegabiWrappers bool
+       // RegabiG enables dedicated G and zero registers in
+       // ABIInternal.
+       //
+       // Requires wrappers because it makes the ABIs incompatible.
+       RegabiG bool
+       // RegabiReflect enables the register-passing paths in
+       // reflection calls. This is also gated by intArgRegs in
+       // reflect and runtime (which are disabled by default) so it
+       // can be used in targeted tests.
+       RegabiReflect bool
+       // RegabiDefer enables desugaring defer and go calls
+       // into argument-less closures.
+       RegabiDefer bool
+       // RegabiArgs enables register arguments/results in all
+       // compiled Go functions.
+       //
+       // Requires wrappers (to do ABI translation), g (because
+       // runtime assembly that's been ported to ABIInternal uses the
+       // G register), reflect (so reflection calls use registers),
+       // and defer (because the runtime doesn't support passing
+       // register arguments to defer/go).
+       RegabiArgs bool
+}