From: Austin Clements Date: Tue, 6 Apr 2021 12:25:01 +0000 (-0400) Subject: internal/goexperiment,cmd: consolidate GOEXPERIMENTs into a new package X-Git-Tag: go1.17beta1~789 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=6304b401e4;p=gostls13.git internal/goexperiment,cmd: consolidate GOEXPERIMENTs into a new package 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 Run-TryBot: Austin Clements TryBot-Result: Go Bot Reviewed-by: Matthew Dempsky --- diff --git a/src/cmd/asm/internal/lex/input.go b/src/cmd/asm/internal/lex/input.go index 8aa6becf55..aa03759c7d 100644 --- a/src/cmd/asm/internal/lex/input.go +++ b/src/cmd/asm/internal/lex/input.go @@ -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 { diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 7520b0ef18..44b1886968 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -58,6 +58,7 @@ var bootstrapDirs = []string{ "debug/macho", "debug/pe", "go/constant", + "internal/goexperiment", "internal/goversion", "internal/race", "internal/unsafeheader", diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 11b7360d26..f692f386f5 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -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 diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index 30c9a2b7cc..1e1494998a 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -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 index 0000000000..21a70d5dfe --- /dev/null +++ b/src/cmd/internal/objabi/exp.go @@ -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) +} diff --git a/src/cmd/internal/objabi/util.go b/src/cmd/internal/objabi/util.go index ca3d3fc1a3..76c56dab27 100644 --- a/src/cmd/internal/objabi/util.go +++ b/src/cmd/internal/objabi/util.go @@ -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 index 0000000000..46800b4e0f --- /dev/null +++ b/src/internal/goexperiment/flags.go @@ -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 +}