1
0
mirror of https://github.com/golang/go synced 2024-11-26 07:17:59 -07:00

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 <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Austin Clements 2021-04-06 08:25:01 -04:00
parent 0c4a08cb74
commit 6304b401e4
7 changed files with 215 additions and 203 deletions

View 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 {

View File

@ -58,6 +58,7 @@ var bootstrapDirs = []string{
"debug/macho",
"debug/pe",
"go/constant",
"internal/goexperiment",
"internal/goversion",
"internal/race",
"internal/unsafeheader",

View 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

View 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)
}
}

View File

@ -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)
}

View 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
}

View File

@ -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
}