Skip to content

Commit 57ef719

Browse files
Add pgo support for go 1.20 (#3641)
* Add pgo support for go 1.20 * Pull request feedback from @fmeum * Add test for building a go_binary with and without a pgoprofile * Add //go/config:pgoprofile to _common_reset_transition_dict * Apply suggestions from code review Co-authored-by: Fabian Meumertzheim <[email protected]> * Use `cquery --output=files` to simplify `pgo_test` --------- Co-authored-by: Fabian Meumertzheim <[email protected]>
1 parent 10b7bb0 commit 57ef719

File tree

15 files changed

+228
-4
lines changed

15 files changed

+228
-4
lines changed

BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ go_config(
123123
gotags = "//go/config:tags",
124124
linkmode = "//go/config:linkmode",
125125
msan = "//go/config:msan",
126+
pgoprofile = "//go/config:pgoprofile",
126127
pure = "//go/config:pure",
127128
race = "//go/config:race",
128129
stamp = select({

docs/go/core/rules.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Rules
126126
<pre>
127127
go_binary(<a href="#go_binary-name">name</a>, <a href="#go_binary-basename">basename</a>, <a href="#go_binary-cdeps">cdeps</a>, <a href="#go_binary-cgo">cgo</a>, <a href="#go_binary-clinkopts">clinkopts</a>, <a href="#go_binary-copts">copts</a>, <a href="#go_binary-cppopts">cppopts</a>, <a href="#go_binary-cxxopts">cxxopts</a>, <a href="#go_binary-data">data</a>, <a href="#go_binary-deps">deps</a>, <a href="#go_binary-embed">embed</a>,
128128
<a href="#go_binary-embedsrcs">embedsrcs</a>, <a href="#go_binary-env">env</a>, <a href="#go_binary-gc_goopts">gc_goopts</a>, <a href="#go_binary-gc_linkopts">gc_linkopts</a>, <a href="#go_binary-goarch">goarch</a>, <a href="#go_binary-goos">goos</a>, <a href="#go_binary-gotags">gotags</a>, <a href="#go_binary-importpath">importpath</a>, <a href="#go_binary-linkmode">linkmode</a>, <a href="#go_binary-msan">msan</a>,
129-
<a href="#go_binary-out">out</a>, <a href="#go_binary-pure">pure</a>, <a href="#go_binary-race">race</a>, <a href="#go_binary-srcs">srcs</a>, <a href="#go_binary-static">static</a>, <a href="#go_binary-x_defs">x_defs</a>)
129+
<a href="#go_binary-out">out</a>, <a href="#go_binary-pgoprofile">pgoprofile</a>, <a href="#go_binary-pure">pure</a>, <a href="#go_binary-race">race</a>, <a href="#go_binary-srcs">srcs</a>, <a href="#go_binary-static">static</a>, <a href="#go_binary-x_defs">x_defs</a>)
130130
</pre>
131131

132132
This builds an executable from a set of source files,
@@ -168,6 +168,7 @@ This builds an executable from a set of source files,
168168
| <a id="go_binary-linkmode"></a>linkmode | Determines how the binary should be built and linked. This accepts some of the same values as `go build -buildmode` and works the same way. <br><br> <ul> <li>`auto` (default): Controlled by `//go/config:linkmode`, which defaults to `normal`.</li> <li>`normal`: Builds a normal executable with position-dependent code.</li> <li>`pie`: Builds a position-independent executable.</li> <li>`plugin`: Builds a shared library that can be loaded as a Go plugin. Only supported on platforms that support plugins.</li> <li>`c-shared`: Builds a shared library that can be linked into a C program.</li> <li>`c-archive`: Builds an archive that can be linked into a C program.</li> </ul> | String | optional | "auto" |
169169
| <a id="go_binary-msan"></a>msan | Controls whether code is instrumented for memory sanitization. May be one of <code>on</code>, <code>off</code>, or <code>auto</code>. Not available when cgo is disabled. In most cases, it's better to control this on the command line with <code>--@io_bazel_rules_go//go/config:msan</code>. See [mode attributes], specifically [msan]. | String | optional | "auto" |
170170
| <a id="go_binary-out"></a>out | Sets the output filename for the generated executable. When set, <code>go_binary</code> will write this file without mode-specific directory prefixes, without linkmode-specific prefixes like "lib", and without platform-specific suffixes like ".exe". Note that without a mode-specific directory prefix, the output file (but not its dependencies) will be invalidated in Bazel's cache when changing configurations. | String | optional | "" |
171+
| <a id="go_binary-pgoprofile"></a>pgoprofile | Provides a pprof file to be used for profile guided optimization when compiling go targets. A pprof file can also be provided via <code>--@io_bazel_rules_go//go/config:pgoprofile=&lt;label of a pprof file&gt;</code>. Profile guided optimization is only supported on go 1.20+. See https://go.dev/doc/pgo for more information. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | //go/config:empty |
171172
| <a id="go_binary-pure"></a>pure | Controls whether cgo source code and dependencies are compiled and linked, similar to setting <code>CGO_ENABLED</code>. May be one of <code>on</code>, <code>off</code>, or <code>auto</code>. If <code>auto</code>, pure mode is enabled when no C/C++ toolchain is configured or when cross-compiling. It's usually better to control this on the command line with <code>--@io_bazel_rules_go//go/config:pure</code>. See [mode attributes], specifically [pure]. | String | optional | "auto" |
172173
| <a id="go_binary-race"></a>race | Controls whether code is instrumented for race detection. May be one of <code>on</code>, <code>off</code>, or <code>auto</code>. Not available when cgo is disabled. In most cases, it's better to control this on the command line with <code>--@io_bazel_rules_go//go/config:race</code>. See [mode attributes], specifically [race]. | String | optional | "auto" |
173174
| <a id="go_binary-srcs"></a>srcs | The list of Go source files that are compiled to create the package. Only <code>.go</code> and <code>.s</code> files are permitted, unless the <code>cgo</code> attribute is set, in which case, <code>.c .cc .cpp .cxx .h .hh .hpp .hxx .inc .m .mm</code> files are also permitted. Files may be filtered at build time using Go [build constraints]. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | [] |

go/config/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,14 @@ string_list_flag(
7979
build_setting_default = [],
8080
visibility = ["//visibility:public"],
8181
)
82+
83+
label_flag(
84+
name = "pgoprofile",
85+
build_setting_default = ":empty",
86+
visibility = ["//visibility:public"],
87+
)
88+
89+
filegroup(
90+
name = "empty",
91+
visibility = ["//visibility:public"],
92+
)

go/private/actions/compilepkg.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ def emit_compilepkg(
151151
if clinkopts:
152152
args.add("-ldflags", quote_opts(clinkopts))
153153

154+
if go.mode.pgoprofile:
155+
args.add("-pgoprofile", go.mode.pgoprofile)
156+
inputs.append(go.mode.pgoprofile)
157+
154158
go.actions.run(
155159
inputs = inputs,
156160
outputs = outputs,

go/private/actions/stdlib.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ def _build_stdlib(go):
136136
go.sdk.tools +
137137
[go.sdk.go, go.sdk.package_list, go.sdk.root_file] +
138138
go.crosstool)
139+
140+
if go.mode.pgoprofile:
141+
args.add("-pgoprofile", go.mode.pgoprofile)
142+
inputs.append(go.mode.pgoprofile)
143+
139144
outputs = [pkg]
140145
go.actions.run(
141146
inputs = inputs,

go/private/context.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def _library_to_source(go, attr, library, coverage_instrumented):
269269
"cgo_deps": [],
270270
"cgo_exports": [],
271271
"cc_info": None,
272+
"pgoprofile": getattr(attr, "pgoprofile", None),
272273
}
273274
if coverage_instrumented:
274275
source["cover"] = attr_srcs
@@ -530,6 +531,7 @@ def go_context(ctx, attr = None):
530531
stamp = mode.stamp,
531532
label = ctx.label,
532533
cover_format = mode.cover_format,
534+
pgoprofile = mode.pgoprofile,
533535
# Action generators
534536
archive = toolchain.actions.archive,
535537
binary = toolchain.actions.binary,
@@ -836,6 +838,7 @@ def _go_config_impl(ctx):
836838
cover_format = ctx.attr.cover_format[BuildSettingInfo].value,
837839
gc_goopts = ctx.attr.gc_goopts[BuildSettingInfo].value,
838840
amd64 = ctx.attr.amd64,
841+
pgoprofile = ctx.attr.pgoprofile,
839842
)]
840843

841844
go_config = rule(
@@ -884,6 +887,10 @@ go_config = rule(
884887
providers = [BuildSettingInfo],
885888
),
886889
"amd64": attr.string(),
890+
"pgoprofile": attr.label(
891+
mandatory = True,
892+
allow_files = True,
893+
),
887894
},
888895
provides = [GoConfigInfo],
889896
doc = """Collects information about build settings in the current

go/private/mode.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
9393
goos = go_toolchain.default_goos if getattr(ctx.attr, "goos", "auto") == "auto" else ctx.attr.goos
9494
goarch = go_toolchain.default_goarch if getattr(ctx.attr, "goarch", "auto") == "auto" else ctx.attr.goarch
9595
gc_goopts = go_config_info.gc_goopts if go_config_info else []
96+
pgoprofile = None
97+
if go_config_info:
98+
if len(go_config_info.pgoprofile.files.to_list()) > 2:
99+
fail("providing more than one pprof file to pgoprofile is not supported")
100+
elif len(go_config_info.pgoprofile.files.to_list()) == 1:
101+
pgoprofile = go_config_info.pgoprofile.files.to_list()[0]
96102

97103
# TODO(jayconrod): check for more invalid and contradictory settings.
98104
if pure and race:
@@ -130,6 +136,7 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
130136
cover_format = cover_format,
131137
amd64 = amd64,
132138
gc_goopts = gc_goopts,
139+
pgoprofile = pgoprofile,
133140
)
134141

135142
def installsuffix(mode):

go/private/rules/binary.bzl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,15 @@ _go_binary_kwargs = {
400400
</ul>
401401
""",
402402
),
403+
"pgoprofile": attr.label(
404+
allow_files = True,
405+
doc = """Provides a pprof file to be used for profile guided optimization when compiling go targets.
406+
A pprof file can also be provided via `--@io_bazel_rules_go//go/config:pgoprofile=<label of a pprof file>`.
407+
Profile guided optimization is only supported on go 1.20+.
408+
See https://go.dev/doc/pgo for more information.
409+
""",
410+
default = "//go/config:empty",
411+
),
403412
"_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
404413
"_allowlist_function_transition": attr.label(
405414
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",

go/private/rules/transition.bzl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ TRANSITIONED_GO_SETTING_KEYS = [
4343
"//go/config:pure",
4444
"//go/config:linkmode",
4545
"//go/config:tags",
46+
"//go/config:pgoprofile",
4647
]
4748

4849
def _deduped_and_sorted(strs):
@@ -117,6 +118,10 @@ def _go_transition_impl(settings, attr):
117118
fail("linkmode: invalid mode {}; want one of {}".format(linkmode, ", ".join(LINKMODES)))
118119
settings["//go/config:linkmode"] = linkmode
119120

121+
pgoprofile = getattr(attr, "pgoprofile", "auto")
122+
if pgoprofile != "auto":
123+
settings["//go/config:pgoprofile"] = pgoprofile
124+
120125
for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items():
121126
old_value = original_settings[key]
122127
value = settings[key]
@@ -132,6 +137,9 @@ def _go_transition_impl(settings, attr):
132137
# original setting wasn't set explicitly (empty string) or was set
133138
# explicitly to its default (always a non-empty string with JSON
134139
# encoding, e.g. "\"\"" or "[]").
140+
if type(old_value) == "Label":
141+
# Label is not JSON serializable, so we need to convert it to a string.
142+
old_value = str(old_value)
135143
settings[original_key] = json.encode(old_value)
136144
else:
137145
settings[original_key] = ""
@@ -177,6 +185,7 @@ _common_reset_transition_dict = dict({
177185
"//go/config:debug": False,
178186
"//go/config:linkmode": LINKMODE_NORMAL,
179187
"//go/config:tags": [],
188+
"//go/config:pgoprofile": Label("//go/config:empty"),
180189
}, **{setting: "" for setting in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values()})
181190

182191
_reset_transition_dict = dict(_common_reset_transition_dict, **{
@@ -191,6 +200,7 @@ _stdlib_keep_keys = sorted([
191200
"//go/config:pure",
192201
"//go/config:linkmode",
193202
"//go/config:tags",
203+
"//go/config:pgoprofile",
194204
])
195205

196206
def _go_tool_transition_impl(settings, _attr):

go/tools/builders/compilepkg.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func compilePkg(args []string) error {
5656
var testFilter string
5757
var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag
5858
var coverFormat string
59+
var pgoprofile string
5960
fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled")
6061
fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)")
6162
fs.Var(&embedSrcs, "embedsrc", "file that may be compiled into the package with a //go:embed directive")
@@ -81,6 +82,7 @@ func compilePkg(args []string) error {
8182
fs.StringVar(&testFilter, "testfilter", "off", "Controls test package filtering")
8283
fs.StringVar(&coverFormat, "cover_format", "", "Emit source file paths in coverage instrumentation suitable for the specified coverage format")
8384
fs.Var(&recompileInternalDeps, "recompile_internal_deps", "The import path of the direct dependencies that needs to be recompiled.")
85+
fs.StringVar(&pgoprofile, "pgoprofile", "", "The pprof profile to consider for profile guided optimization.")
8486
if err := fs.Parse(args); err != nil {
8587
return err
8688
}
@@ -99,6 +101,9 @@ func compilePkg(args []string) error {
99101
for i := range embedSrcs {
100102
embedSrcs[i] = abs(embedSrcs[i])
101103
}
104+
if pgoprofile != "" {
105+
pgoprofile = abs(pgoprofile)
106+
}
102107

103108
// Filter sources.
104109
srcs, err := filterAndSplitFiles(unfilteredSrcs)
@@ -158,7 +163,8 @@ func compilePkg(args []string) error {
158163
outFactsPath,
159164
cgoExportHPath,
160165
coverFormat,
161-
recompileInternalDeps)
166+
recompileInternalDeps,
167+
pgoprofile)
162168
}
163169

164170
func compileArchive(
@@ -189,6 +195,7 @@ func compileArchive(
189195
cgoExportHPath string,
190196
coverFormat string,
191197
recompileInternalDeps []string,
198+
pgoprofile string,
192199
) error {
193200
workDir, cleanup, err := goenv.workDir()
194201
if err != nil {
@@ -467,7 +474,7 @@ func compileArchive(
467474
}
468475

469476
// Compile the filtered .go files.
470-
if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath, gcFlags, outPath); err != nil {
477+
if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath, gcFlags, pgoprofile, outPath); err != nil {
471478
return err
472479
}
473480

@@ -537,7 +544,7 @@ func compileArchive(
537544
return appendFiles(goenv, outXPath, []string{pkgDefPath})
538545
}
539546

540-
func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath string, gcFlags []string, outPath string) error {
547+
func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath string, gcFlags []string, pgoprofile string, outPath string) error {
541548
args := goenv.goTool("compile")
542549
args = append(args, "-p", packagePath, "-importcfg", importcfgPath, "-pack")
543550
if embedcfgPath != "" {
@@ -549,6 +556,9 @@ func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPa
549556
if symabisPath != "" {
550557
args = append(args, "-symabis", symabisPath)
551558
}
559+
if pgoprofile != "" {
560+
args = append(args, "-pgoprofile", pgoprofile)
561+
}
552562
args = append(args, gcFlags...)
553563
args = append(args, "-o", outPath)
554564
args = append(args, "--")

go/tools/builders/stdlib.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func stdlib(args []string) error {
3333
race := flags.Bool("race", false, "Build in race mode")
3434
shared := flags.Bool("shared", false, "Build in shared mode")
3535
dynlink := flags.Bool("dynlink", false, "Build in dynlink mode")
36+
pgoprofile := flags.String("pgoprofile", "", "Build with pgo using the given pprof file")
3637
var packages multiFlag
3738
flags.Var(&packages, "package", "Packages to build")
3839
var gcflags quoteMultiFlag
@@ -130,6 +131,9 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`)
130131
if *race {
131132
installArgs = append(installArgs, "-race")
132133
}
134+
if *pgoprofile != "" {
135+
installArgs = append(installArgs, "-pgo", abs(*pgoprofile))
136+
}
133137
if *shared {
134138
gcflags = append(gcflags, "-shared")
135139
ldflags = append(ldflags, "-shared")

tests/core/go_binary/BUILD.bazel

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,17 @@ go_bazel_test(
216216
name = "non_executable_test",
217217
srcs = ["non_executable_test.go"],
218218
)
219+
220+
exports_files(["pgo.pprof"])
221+
222+
go_binary(
223+
name = "pgo",
224+
srcs = ["pgo.go"],
225+
pgoprofile = "pgo.pprof",
226+
)
227+
228+
go_bazel_test(
229+
name = "pgo_test",
230+
srcs = ["pgo_test.go"],
231+
embedsrcs = ["pgo.pprof"],
232+
)

tests/core/go_binary/pgo.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"log"
7+
"net/http"
8+
_ "net/http/pprof"
9+
)
10+
11+
func render(w http.ResponseWriter, r *http.Request) {
12+
if r.Method != "POST" {
13+
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
14+
return
15+
}
16+
17+
src, err := io.ReadAll(r.Body)
18+
if err != nil {
19+
log.Printf("error reading body: %v", err)
20+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
21+
return
22+
}
23+
24+
var buf bytes.Buffer
25+
if _, err := buf.Write(src); err != nil {
26+
log.Printf("error writing response: %v", err)
27+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
28+
return
29+
}
30+
31+
if _, err := io.Copy(w, &buf); err != nil {
32+
log.Printf("error writing response: %v", err)
33+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
34+
return
35+
}
36+
}
37+
38+
func main() {
39+
http.HandleFunc("/render", render)
40+
log.Printf("Serving on port 8080...")
41+
log.Fatal(http.ListenAndServe(":8080", nil))
42+
}

tests/core/go_binary/pgo.pprof

39.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)