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
5package buildcfg
6
7import (
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.
17type 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.
31var 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.
42const 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.
51var 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.
58func 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	baseline := goexperiment.Flags{
71		RegabiWrappers:   regabiSupported,
72		RegabiArgs:       regabiSupported,
73		CoverageRedesign: true,
74	}
75
76	// Start with the statically enabled set of experiments.
77	flags := &ExperimentFlags{
78		Flags:    baseline,
79		baseline: baseline,
80	}
81
82	// Pick up any changes to the baseline configuration from the
83	// GOEXPERIMENT environment. This can be set at make.bash time
84	// and overridden at build time.
85	if goexp != "" {
86		// Create a map of known experiment names.
87		names := make(map[string]func(bool))
88		rv := reflect.ValueOf(&flags.Flags).Elem()
89		rt := rv.Type()
90		for i := 0; i < rt.NumField(); i++ {
91			field := rv.Field(i)
92			names[strings.ToLower(rt.Field(i).Name)] = field.SetBool
93		}
94
95		// "regabi" is an alias for all working regabi
96		// subexperiments, and not an experiment itself. Doing
97		// this as an alias make both "regabi" and "noregabi"
98		// do the right thing.
99		names["regabi"] = func(v bool) {
100			flags.RegabiWrappers = v
101			flags.RegabiArgs = v
102		}
103
104		// Parse names.
105		for _, f := range strings.Split(goexp, ",") {
106			if f == "" {
107				continue
108			}
109			if f == "none" {
110				// GOEXPERIMENT=none disables all experiment flags.
111				// This is used by cmd/dist, which doesn't know how
112				// to build with any experiment flags.
113				flags.Flags = goexperiment.Flags{}
114				continue
115			}
116			val := true
117			if strings.HasPrefix(f, "no") {
118				f, val = f[2:], false
119			}
120			set, ok := names[f]
121			if !ok {
122				return nil, fmt.Errorf("unknown GOEXPERIMENT %s", f)
123			}
124			set(val)
125		}
126	}
127
128	if regabiAlwaysOn {
129		flags.RegabiWrappers = true
130		flags.RegabiArgs = true
131	}
132	// regabi is only supported on amd64, arm64, loong64, riscv64, ppc64 and ppc64le.
133	if !regabiSupported {
134		flags.RegabiWrappers = false
135		flags.RegabiArgs = false
136	}
137	// Check regabi dependencies.
138	if flags.RegabiArgs && !flags.RegabiWrappers {
139		return nil, fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers")
140	}
141	return flags, nil
142}
143
144// String returns the canonical GOEXPERIMENT string to enable this experiment
145// configuration. (Experiments in the same state as in the baseline are elided.)
146func (exp *ExperimentFlags) String() string {
147	return strings.Join(expList(&exp.Flags, &exp.baseline, false), ",")
148}
149
150// expList returns the list of lower-cased experiment names for
151// experiments that differ from base. base may be nil to indicate no
152// experiments. If all is true, then include all experiment flags,
153// regardless of base.
154func expList(exp, base *goexperiment.Flags, all bool) []string {
155	var list []string
156	rv := reflect.ValueOf(exp).Elem()
157	var rBase reflect.Value
158	if base != nil {
159		rBase = reflect.ValueOf(base).Elem()
160	}
161	rt := rv.Type()
162	for i := 0; i < rt.NumField(); i++ {
163		name := strings.ToLower(rt.Field(i).Name)
164		val := rv.Field(i).Bool()
165		baseVal := false
166		if base != nil {
167			baseVal = rBase.Field(i).Bool()
168		}
169		if all || val != baseVal {
170			if val {
171				list = append(list, name)
172			} else {
173				list = append(list, "no"+name)
174			}
175		}
176	}
177	return list
178}
179
180// Enabled returns a list of enabled experiments, as
181// lower-cased experiment names.
182func (exp *ExperimentFlags) Enabled() []string {
183	return expList(&exp.Flags, nil, false)
184}
185
186// All returns a list of all experiment settings.
187// Disabled experiments appear in the list prefixed by "no".
188func (exp *ExperimentFlags) All() []string {
189	return expList(&exp.Flags, nil, true)
190}
191