Source file src/internal/buildcfg/exp.go

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package buildcfg
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  
    12  	"internal/goexperiment"
    13  )
    14  
    15  // Experiment contains the toolchain experiments enabled for the
    16  // current build.
    17  //
    18  // (This is not necessarily the set of experiments the compiler itself
    19  // was built with.)
    20  //
    21  // experimentBaseline specifies the experiment flags that are enabled by
    22  // default in the current toolchain. This is, in effect, the "control"
    23  // configuration and any variation from this is an experiment.
    24  var Experiment, experimentBaseline = func() (goexperiment.Flags, goexperiment.Flags) {
    25  	flags, baseline, err := ParseGOEXPERIMENT(GOOS, GOARCH, envOr("GOEXPERIMENT", defaultGOEXPERIMENT))
    26  	if err != nil {
    27  		Error = err
    28  	}
    29  	return flags, baseline
    30  }()
    31  
    32  const DefaultGOEXPERIMENT = defaultGOEXPERIMENT
    33  
    34  // FramePointerEnabled enables the use of platform conventions for
    35  // saving frame pointers.
    36  //
    37  // This used to be an experiment, but now it's always enabled on
    38  // platforms that support it.
    39  //
    40  // Note: must agree with runtime.framepointer_enabled.
    41  var FramePointerEnabled = GOARCH == "amd64" || GOARCH == "arm64"
    42  
    43  // ParseGOEXPERIMENT parses a (GOOS, GOARCH, GOEXPERIMENT)
    44  // configuration tuple and returns the enabled and baseline experiment
    45  // flag sets.
    46  //
    47  // TODO(mdempsky): Move to internal/goexperiment.
    48  func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment.Flags, err error) {
    49  	regabiSupported := false
    50  	switch goarch {
    51  	case "amd64", "arm64", "ppc64le", "ppc64":
    52  		regabiSupported = true
    53  	}
    54  
    55  	baseline = goexperiment.Flags{
    56  		RegabiWrappers: regabiSupported,
    57  		RegabiReflect:  regabiSupported,
    58  		RegabiArgs:     regabiSupported,
    59  		PacerRedesign:  true,
    60  	}
    61  
    62  	// Start with the statically enabled set of experiments.
    63  	flags = baseline
    64  
    65  	// Pick up any changes to the baseline configuration from the
    66  	// GOEXPERIMENT environment. This can be set at make.bash time
    67  	// and overridden at build time.
    68  	if goexp != "" {
    69  		// Create a map of known experiment names.
    70  		names := make(map[string]func(bool))
    71  		rv := reflect.ValueOf(&flags).Elem()
    72  		rt := rv.Type()
    73  		for i := 0; i < rt.NumField(); i++ {
    74  			field := rv.Field(i)
    75  			names[strings.ToLower(rt.Field(i).Name)] = field.SetBool
    76  		}
    77  
    78  		// "regabi" is an alias for all working regabi
    79  		// subexperiments, and not an experiment itself. Doing
    80  		// this as an alias make both "regabi" and "noregabi"
    81  		// do the right thing.
    82  		names["regabi"] = func(v bool) {
    83  			flags.RegabiWrappers = v
    84  			flags.RegabiReflect = v
    85  			flags.RegabiArgs = v
    86  		}
    87  
    88  		// Parse names.
    89  		for _, f := range strings.Split(goexp, ",") {
    90  			if f == "" {
    91  				continue
    92  			}
    93  			if f == "none" {
    94  				// GOEXPERIMENT=none disables all experiment flags.
    95  				// This is used by cmd/dist, which doesn't know how
    96  				// to build with any experiment flags.
    97  				flags = goexperiment.Flags{}
    98  				continue
    99  			}
   100  			val := true
   101  			if strings.HasPrefix(f, "no") {
   102  				f, val = f[2:], false
   103  			}
   104  			set, ok := names[f]
   105  			if !ok {
   106  				err = fmt.Errorf("unknown GOEXPERIMENT %s", f)
   107  				return
   108  			}
   109  			set(val)
   110  		}
   111  	}
   112  
   113  	// regabi is always enabled on amd64.
   114  	if goarch == "amd64" {
   115  		flags.RegabiWrappers = true
   116  		flags.RegabiReflect = true
   117  		flags.RegabiArgs = true
   118  	}
   119  	// regabi is only supported on amd64, arm64, ppc64 and ppc64le.
   120  	if !regabiSupported {
   121  		flags.RegabiReflect = false
   122  		flags.RegabiArgs = false
   123  	}
   124  	// Check regabi dependencies.
   125  	if flags.RegabiArgs && !(flags.RegabiWrappers && flags.RegabiReflect) {
   126  		err = fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers,regabireflect")
   127  	}
   128  	return
   129  }
   130  
   131  // expList returns the list of lower-cased experiment names for
   132  // experiments that differ from base. base may be nil to indicate no
   133  // experiments. If all is true, then include all experiment flags,
   134  // regardless of base.
   135  func expList(exp, base *goexperiment.Flags, all bool) []string {
   136  	var list []string
   137  	rv := reflect.ValueOf(exp).Elem()
   138  	var rBase reflect.Value
   139  	if base != nil {
   140  		rBase = reflect.ValueOf(base).Elem()
   141  	}
   142  	rt := rv.Type()
   143  	for i := 0; i < rt.NumField(); i++ {
   144  		name := strings.ToLower(rt.Field(i).Name)
   145  		val := rv.Field(i).Bool()
   146  		baseVal := false
   147  		if base != nil {
   148  			baseVal = rBase.Field(i).Bool()
   149  		}
   150  		if all || val != baseVal {
   151  			if val {
   152  				list = append(list, name)
   153  			} else {
   154  				list = append(list, "no"+name)
   155  			}
   156  		}
   157  	}
   158  	return list
   159  }
   160  
   161  // GOEXPERIMENT is a comma-separated list of enabled or disabled
   162  // experiments that differ from the baseline experiment configuration.
   163  // GOEXPERIMENT is exactly what a user would set on the command line
   164  // to get the set of enabled experiments.
   165  func GOEXPERIMENT() string {
   166  	goexp := strings.Join(expList(&Experiment, &experimentBaseline, false), ",")
   167  	if goexp == "" && DefaultGOEXPERIMENT != "" {
   168  		goexp = "," // non-empty to override DefaultGOEXPERIMENT
   169  	}
   170  	return goexp
   171  }
   172  
   173  // EnabledExperiments returns a list of enabled experiments, as
   174  // lower-cased experiment names.
   175  func EnabledExperiments() []string {
   176  	return expList(&Experiment, nil, false)
   177  }
   178  
   179  // AllExperiments returns a list of all experiment settings.
   180  // Disabled experiments appear in the list prefixed by "no".
   181  func AllExperiments() []string {
   182  	return expList(&Experiment, nil, true)
   183  }
   184  
   185  // UpdateExperiments updates the Experiment global based on a new GOARCH value.
   186  // This is only required for cmd/go, which can change GOARCH after
   187  // program startup due to use of "go env -w".
   188  func UpdateExperiments(goos, goarch, goexperiment string) {
   189  	var err error
   190  	Experiment, experimentBaseline, err = ParseGOEXPERIMENT(goos, goarch, goexperiment)
   191  	if err != nil {
   192  		Error = err
   193  	}
   194  }
   195  

View as plain text