summaryrefslogtreecommitdiffstats
path: root/build/moz.configure/lto-pgo.configure
blob: 28f41147807a1f8e467b0a1f884846c5d9a08448 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# PGO
# ==============================================================
llvm_profdata = check_prog(
    "LLVM_PROFDATA", ["llvm-profdata"], allow_missing=True, paths=clang_search_path
)

option(
    "--enable-profile-generate",
    env="MOZ_PROFILE_GENERATE",
    nargs="?",
    choices=("cross",),
    help="Build a PGO instrumented binary",
)

imply_option("MOZ_PGO", depends_if("--enable-profile-generate")(lambda _: True))

set_config(
    "MOZ_PROFILE_GENERATE", depends_if("--enable-profile-generate")(lambda _: True)
)

set_define(
    "MOZ_PROFILE_GENERATE", depends_if("--enable-profile-generate")(lambda _: True)
)

add_old_configure_assignment(
    "MOZ_PROFILE_GENERATE", 1, when="--enable-profile-generate"
)

option(
    "--enable-profile-use",
    env="MOZ_PROFILE_USE",
    nargs="?",
    choices=("cross",),
    help="Use a generated profile during the build",
)

option(
    "--with-pgo-profile-path",
    help="Path to the directory with unmerged profile data to use during the build",
    nargs=1,
)

imply_option("MOZ_PGO", depends_if("--enable-profile-use")(lambda _: True))

set_config("MOZ_PROFILE_USE", depends_if("--enable-profile-use")(lambda _: True))


@depends(
    "--with-pgo-profile-path",
    "--enable-profile-use",
    llvm_profdata,
    build_environment,
)
@imports("os")
def pgo_profile_path(path, pgo_use, profdata, build_env):
    topobjdir = build_env.topobjdir
    if topobjdir.endswith("/js/src"):
        topobjdir = topobjdir[:-7]

    if not path:
        return os.path.join(topobjdir, "instrumented", "merged.profdata")
    if path and not pgo_use:
        die("Pass --enable-profile-use to use --with-pgo-profile-path.")
    if path and not profdata:
        die("LLVM_PROFDATA must be set to process the pgo profile.")
    if not os.path.isfile(path[0]):
        die("Argument to --with-pgo-profile-path must be a file.")
    if not os.path.isabs(path[0]):
        die("Argument to --with-pgo-profile-path must be an absolute path.")
    return path[0]


set_config("PGO_PROFILE_PATH", pgo_profile_path)


@depends(c_compiler, pgo_profile_path, target_is_windows)
@imports("multiprocessing")
def pgo_flags(compiler, profdata, target_is_windows):
    if compiler.type == "gcc":
        return namespace(
            gen_cflags=["-fprofile-generate"],
            gen_ldflags=["-fprofile-generate"],
            use_cflags=["-fprofile-use", "-fprofile-correction", "-Wcoverage-mismatch"],
            use_ldflags=["-fprofile-use"],
        )

    if compiler.type in ("clang-cl", "clang"):
        prefix = ""
        if compiler.type == "clang-cl":
            prefix = "/clang:"
            gen_ldflags = None
        else:
            gen_ldflags = ["-fprofile-generate"]

        gen_cflags = [prefix + "-fprofile-generate"]
        if target_is_windows:
            # native llvm-profdata.exe on Windows can't read profile data
            # if name compression is enabled (which cross-compiling enables
            # by default)
            gen_cflags += ["-mllvm", "-enable-name-compression=false"]

        return namespace(
            gen_cflags=gen_cflags,
            gen_ldflags=gen_ldflags,
            use_cflags=[
                prefix + "-fprofile-use=%s" % profdata,
                # Some error messages about mismatched profile data
                # come in via -Wbackend-plugin, so disable those too.
                "-Wno-error=backend-plugin",
            ],
            use_ldflags=[],
        )


set_config("PROFILE_GEN_CFLAGS", pgo_flags.gen_cflags)
set_config("PROFILE_GEN_LDFLAGS", pgo_flags.gen_ldflags)
set_config("PROFILE_USE_CFLAGS", pgo_flags.use_cflags)
set_config("PROFILE_USE_LDFLAGS", pgo_flags.use_ldflags)

option(
    "--with-pgo-jarlog",
    help="Use the provided jarlog file when packaging during a profile-use " "build",
    nargs=1,
)

set_config("PGO_JARLOG_PATH", depends_if("--with-pgo-jarlog")(lambda p: p))


@depends("MOZ_PGO", "--enable-profile-use", "--enable-profile-generate", c_compiler)
def moz_pgo_rust(pgo, profile_use, profile_generate, c_compiler):
    if not pgo:
        return

    # Enabling PGO through MOZ_PGO only and not --enable* flags.
    if not profile_use and not profile_generate:
        return

    if profile_use and profile_generate:
        die("Cannot build with --enable-profile-use and --enable-profile-generate.")

    want_cross = (len(profile_use) and profile_use[0] == "cross") or (
        len(profile_generate) and profile_generate[0] == "cross"
    )

    if not want_cross:
        return

    if c_compiler.type == "gcc":
        die("Cannot use cross-language PGO with GCC.")

    return True


set_config("MOZ_PGO_RUST", moz_pgo_rust)

# LTO
# ==============================================================

option(
    "--enable-lto",
    env="MOZ_LTO",
    nargs="*",
    choices=("full", "thin", "cross"),
    help="Enable LTO",
)

option(
    env="MOZ_LD64_KNOWN_GOOD",
    nargs=1,
    help="Indicate that ld64 is free of symbol aliasing bugs.",
)

imply_option("MOZ_LD64_KNOWN_GOOD", depends_if("MOZ_AUTOMATION")(lambda _: True))


@depends(
    "--enable-lto",
    c_compiler,
    select_linker,
    "MOZ_LD64_KNOWN_GOOD",
    target,
    "--enable-profile-generate",
    pass_manager.enabled,
)
@imports("multiprocessing")
def lto(
    values,
    c_compiler,
    select_linker,
    ld64_known_good,
    target,
    instrumented_build,
    pass_manager,
):
    cflags = []
    ldflags = []
    enabled = None
    rust_lto = False

    if not values:
        return

    # Sanitize LTO modes.
    if "full" in values and "thin" in values:
        die("incompatible --enable-lto choices 'full' and 'thin'")

    # If a value was given to --enable-lto, use that.
    # Otherwise, make the lto mode explicit, using
    # thin with clang/clang-cl and full with gcc.
    if values == () or values == ("cross",):
        if c_compiler.type == "gcc":
            values += ("full",)
        else:
            values += ("thin",)

    if instrumented_build:
        log.warning("Disabling LTO because --enable-profile-generate is specified")
        return

    if c_compiler.type == "gcc":
        if "cross" in values:
            die("Cross-language LTO is not supported with GCC.")
        if "thin" in values:
            die(
                "gcc does not support thin LTO. Use `--enable-lto` "
                "to enable full LTO for gcc."
            )

    if (
        target.kernel == "Darwin"
        and target.os == "OSX"
        and "cross" in values
        and select_linker.KIND == "ld64"
        and not ld64_known_good
    ):
        die(
            "The Mac linker is known to have a bug that affects cross-language "
            "LTO.  If you know that your linker is free from this bug, please "
            "set the environment variable `MOZ_LD64_KNOWN_GOOD=1` and re-run "
            "configure."
        )

    if c_compiler.type == "clang":
        if "full" in values:
            cflags.append("-flto")
            ldflags.append("-flto")
        else:
            cflags.append("-flto=thin")
            ldflags.append("-flto=thin")

        if target.os == "Android" and "cross" in values:
            # Work around https://github.com/rust-lang/rust/issues/90088
            # by enabling the highest level of SSE the rust targets default
            # to.
            # https://github.com/rust-lang/rust/blob/bdfcb88e8b6203ccb46a2fb6649979b773efc8ac/compiler/rustc_target/src/spec/i686_linux_android.rs#L13
            # https://github.com/rust-lang/rust/blob/8d1083e319841624f64400e1524805a40d725439/compiler/rustc_target/src/spec/x86_64_linux_android.rs#L7
            if target.cpu == "x86":
                ldflags.append("-Wl,-plugin-opt=-mattr=+ssse3")
            elif target.cpu == "x86_64":
                ldflags.append("-Wl,-plugin-opt=-mattr=+sse4.2")
    elif c_compiler.type == "clang-cl":
        if "full" in values:
            cflags.append("-flto")
        else:
            cflags.append("-flto=thin")
        # With clang-cl, -flto can only be used with -c or -fuse-ld=lld.
        # AC_TRY_LINKs during configure don't have -c, so pass -fuse-ld=lld.
        cflags.append("-fuse-ld=lld")

        # Explicitly set the CPU to optimize for so the linker doesn't
        # choose a poor default.  Rust compilation by default uses the
        # pentium4 CPU on x86:
        #
        # https://github.com/rust-lang/rust/blob/049a49b91151a88c95fa0d62a53fd0a0ac2c3af9/compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs#L5
        #
        # which specifically supports "long" (multi-byte) nops.  See
        # https://bugzilla.mozilla.org/show_bug.cgi?id=1568450#c8 for details.
        #
        # The pentium4 seems like kind of a weird CPU to optimize for, but
        # it seems to have worked out OK thus far.  LLVM does not seem to
        # specifically schedule code for the pentium4's deep pipeline, so
        # that probably contributes to it being an OK default for our
        # purposes.
        if target.cpu == "x86":
            ldflags.append("-mllvm:-mcpu=pentium4")
        # This is also the CPU that Rust uses.  The LLVM source code
        # recommends this as the "generic 64-bit specific x86 processor model":
        #
        # https://github.com/llvm/llvm-project/blob/e7694f34ab6a12b8bb480cbfcb396d0a64fe965f/llvm/lib/Target/X86/X86.td#L1165-L1187
        if target.cpu == "x86_64":
            ldflags.append("-mllvm:-mcpu=x86-64")
        # We do not need special flags for arm64.  Hooray for fixed-length
        # instruction sets.
    else:
        num_cores = multiprocessing.cpu_count()
        cflags.append("-flto")
        cflags.append("-flifetime-dse=1")

        ldflags.append("-flto=%s" % num_cores)
        ldflags.append("-flifetime-dse=1")

    # Tell LTO not to inline functions above a certain size, to mitigate
    # binary size growth while still getting good performance.
    # (For hot functions, PGO will put a multiplier on this limit.)
    if target.os == "WINNT":
        ldflags.append("-mllvm:-import-instr-limit=10")
    elif target.os == "OSX":
        ldflags.append("-Wl,-mllvm,-import-instr-limit=10")
    elif c_compiler.type == "clang":
        ldflags.append("-Wl,-plugin-opt=-import-instr-limit=10")

    # If we're using the new pass manager, we can also enable the new PM
    # during LTO. Further we can use the resulting size savings to increase
    # the import limit in hot functions.
    if pass_manager:
        if target.os == "WINNT":
            if c_compiler.version >= "12.0.0" and c_compiler.version < "13.0.0":
                ldflags.append("-opt:ltonewpassmanager")
            if c_compiler.version >= "12.0.0":
                ldflags.append("-mllvm:-import-hot-multiplier=30")
        elif target.os == "OSX":
            ldflags.append("-Wl,-mllvm,-import-hot-multiplier=30")
        else:
            if c_compiler.version < "13.0.0":
                ldflags.append("-Wl,-plugin-opt=new-pass-manager")
            ldflags.append("-Wl,-plugin-opt=-import-hot-multiplier=30")

    # Pick Rust LTO mode in case of cross lTO. Thin is the default.
    if "cross" in values:
        rust_lto = "full" if "full" in values else "thin"
    else:
        rust_lto = ""

    return namespace(
        enabled=True,
        cflags=cflags,
        ldflags=ldflags,
        rust_lto=rust_lto,
    )


add_old_configure_assignment("MOZ_LTO", lto.enabled)
set_config("MOZ_LTO", lto.enabled)
set_define("MOZ_LTO", lto.enabled)
set_config("MOZ_LTO_CFLAGS", lto.cflags)
set_config("MOZ_LTO_LDFLAGS", lto.ldflags)
set_config("MOZ_LTO_RUST_CROSS", lto.rust_lto)
add_old_configure_assignment("MOZ_LTO_CFLAGS", lto.cflags)
add_old_configure_assignment("MOZ_LTO_LDFLAGS", lto.ldflags)