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  // ExperimentFlags represents a set of GOEXPERIMENT flags relative to a baseline
    16  // (platform-default) experiment configuration.
    17  type ExperimentFlags struct {
    18  	goexperiment.Flags
    19  	baseline goexperiment.Flags
    20  }
    21  
    22  // Experiment contains the toolchain experiments enabled for the
    23  // current build.
    24  //
    25  // (This is not necessarily the set of experiments the compiler itself
    26  // was built with.)
    27  //
    28  // experimentBaseline specifies the experiment flags that are enabled by
    29  // default in the current toolchain. This is, in effect, the "control"
    30  // configuration and any variation from this is an experiment.
    31  var Experiment ExperimentFlags = func() ExperimentFlags {
    32  	flags, err := ParseGOEXPERIMENT(GOOS, GOARCH, envOr("GOEXPERIMENT", defaultGOEXPERIMENT))
    33  	if err != nil {
    34  		Error = err
    35  		return ExperimentFlags{}
    36  	}
    37  	return *flags
    38  }()
    39  
    40  // DefaultGOEXPERIMENT is the embedded default GOEXPERIMENT string.
    41  // It is not guaranteed to be canonical.
    42  const DefaultGOEXPERIMENT = defaultGOEXPERIMENT
    43  
    44  // FramePointerEnabled enables the use of platform conventions for
    45  // saving frame pointers.
    46  //
    47  // This used to be an experiment, but now it's always enabled on
    48  // platforms that support it.
    49  //
    50  // Note: must agree with runtime.framepointer_enabled.
    51  var FramePointerEnabled = GOARCH == "amd64" || GOARCH == "arm64"
    52  
    53  // ParseGOEXPERIMENT parses a (GOOS, GOARCH, GOEXPERIMENT)
    54  // configuration tuple and returns the enabled and baseline experiment
    55  // flag sets.
    56  //
    57  // TODO(mdempsky): Move to internal/goexperiment.
    58  func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
    59  	// regabiSupported is set to true on platforms where register ABI is
    60  	// supported and enabled by default.
    61  	// regabiAlwaysOn is set to true on platforms where register ABI is
    62  	// always on.
    63  	var regabiSupported, regabiAlwaysOn bool
    64  	switch goarch {
    65  	case "amd64", "arm64", "loong64", "ppc64le", "ppc64", "riscv64":
    66  		regabiAlwaysOn = true
    67  		regabiSupported = true
    68  	}
    69  
    70  	haveThreads := goarch != "wasm"
    71  
    72  	// Older versions (anything before V16) of dsymutil don't handle
    73  	// the .debug_rnglists section in DWARF5. See
    74  	// https://github.com/golang/go/issues/26379#issuecomment-2677068742
    75  	// for more context. This disables all DWARF5 on mac, which is not
    76  	// ideal (would be better to disable just for cases where we know
    77  	// the build will use external linking). In the GOOS=aix case, the
    78  	// XCOFF format (as far as can be determined) doesn't seem to
    79  	// support the necessary section subtypes for DWARF-specific
    80  	// things like .debug_addr (needed for DWARF 5).
    81  	dwarf5Supported := (goos != "darwin" && goos != "ios" && goos != "aix")
    82  
    83  	baseline := goexperiment.Flags{
    84  		RegabiWrappers:  regabiSupported,
    85  		RegabiArgs:      regabiSupported,
    86  		AliasTypeParams: true,
    87  		SwissMap:        true,
    88  		SpinbitMutex:    haveThreads,
    89  		SyncHashTrieMap: true,
    90  		Dwarf5:          dwarf5Supported,
    91  	}
    92  
    93  	// Start with the statically enabled set of experiments.
    94  	flags := &ExperimentFlags{
    95  		Flags:    baseline,
    96  		baseline: baseline,
    97  	}
    98  
    99  	// Pick up any changes to the baseline configuration from the
   100  	// GOEXPERIMENT environment. This can be set at make.bash time
   101  	// and overridden at build time.
   102  	if goexp != "" {
   103  		// Create a map of known experiment names.
   104  		names := make(map[string]func(bool))
   105  		rv := reflect.ValueOf(&flags.Flags).Elem()
   106  		rt := rv.Type()
   107  		for i := 0; i < rt.NumField(); i++ {
   108  			field := rv.Field(i)
   109  			names[strings.ToLower(rt.Field(i).Name)] = field.SetBool
   110  		}
   111  
   112  		// "regabi" is an alias for all working regabi
   113  		// subexperiments, and not an experiment itself. Doing
   114  		// this as an alias make both "regabi" and "noregabi"
   115  		// do the right thing.
   116  		names["regabi"] = func(v bool) {
   117  			flags.RegabiWrappers = v
   118  			flags.RegabiArgs = v
   119  		}
   120  
   121  		// Parse names.
   122  		for _, f := range strings.Split(goexp, ",") {
   123  			if f == "" {
   124  				continue
   125  			}
   126  			if f == "none" {
   127  				// GOEXPERIMENT=none disables all experiment flags.
   128  				// This is used by cmd/dist, which doesn't know how
   129  				// to build with any experiment flags.
   130  				flags.Flags = goexperiment.Flags{}
   131  				continue
   132  			}
   133  			val := true
   134  			if strings.HasPrefix(f, "no") {
   135  				f, val = f[2:], false
   136  			}
   137  			set, ok := names[f]
   138  			if !ok {
   139  				return nil, fmt.Errorf("unknown GOEXPERIMENT %s", f)
   140  			}
   141  			set(val)
   142  		}
   143  	}
   144  
   145  	if regabiAlwaysOn {
   146  		flags.RegabiWrappers = true
   147  		flags.RegabiArgs = true
   148  	}
   149  	// regabi is only supported on amd64, arm64, loong64, riscv64, ppc64 and ppc64le.
   150  	if !regabiSupported {
   151  		flags.RegabiWrappers = false
   152  		flags.RegabiArgs = false
   153  	}
   154  	// Check regabi dependencies.
   155  	if flags.RegabiArgs && !flags.RegabiWrappers {
   156  		return nil, fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers")
   157  	}
   158  	return flags, nil
   159  }
   160  
   161  // String returns the canonical GOEXPERIMENT string to enable this experiment
   162  // configuration. (Experiments in the same state as in the baseline are elided.)
   163  func (exp *ExperimentFlags) String() string {
   164  	return strings.Join(expList(&exp.Flags, &exp.baseline, false), ",")
   165  }
   166  
   167  // expList returns the list of lower-cased experiment names for
   168  // experiments that differ from base. base may be nil to indicate no
   169  // experiments. If all is true, then include all experiment flags,
   170  // regardless of base.
   171  func expList(exp, base *goexperiment.Flags, all bool) []string {
   172  	var list []string
   173  	rv := reflect.ValueOf(exp).Elem()
   174  	var rBase reflect.Value
   175  	if base != nil {
   176  		rBase = reflect.ValueOf(base).Elem()
   177  	}
   178  	rt := rv.Type()
   179  	for i := 0; i < rt.NumField(); i++ {
   180  		name := strings.ToLower(rt.Field(i).Name)
   181  		val := rv.Field(i).Bool()
   182  		baseVal := false
   183  		if base != nil {
   184  			baseVal = rBase.Field(i).Bool()
   185  		}
   186  		if all || val != baseVal {
   187  			if val {
   188  				list = append(list, name)
   189  			} else {
   190  				list = append(list, "no"+name)
   191  			}
   192  		}
   193  	}
   194  	return list
   195  }
   196  
   197  // Enabled returns a list of enabled experiments, as
   198  // lower-cased experiment names.
   199  func (exp *ExperimentFlags) Enabled() []string {
   200  	return expList(&exp.Flags, nil, false)
   201  }
   202  
   203  // All returns a list of all experiment settings.
   204  // Disabled experiments appear in the list prefixed by "no".
   205  func (exp *ExperimentFlags) All() []string {
   206  	return expList(&exp.Flags, nil, true)
   207  }
   208  

View as plain text