Skip to content

Commit 8006cb5

Browse files
committed
S3 LTO Script
1 parent d885677 commit 8006cb5

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

pio-scripts/enable_lto_s3.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Enable Link Time Optimization (LTO) for PlatformIO / Xtensa ESP32-S3 builds.
4+
5+
Simply adding -flto to build_flags is not sufficient: LTO also requires the
6+
linker to receive -flto and the archiver/ranlib to be the GCC-LTO-aware
7+
variants (gcc-ar / gcc-ranlib). This script wires all three up so that the
8+
flag is consistently applied and the static-library archives are built in a
9+
way that LTO can see through them.
10+
"""
11+
## This script was created with the help of an AI, reviewed by @troyhacks
12+
13+
Import("env")
14+
import os
15+
import shutil
16+
17+
def _find_tool(name, cc_dir):
18+
"""Locate a toolchain binary: check cc_dir first, then PATH."""
19+
if cc_dir:
20+
for suffix in ("", ".exe"):
21+
candidate = os.path.join(cc_dir, name + suffix)
22+
if os.path.isfile(candidate):
23+
return candidate
24+
# Fall back to searching PATH
25+
return shutil.which(name)
26+
27+
def enable_lto(env):
28+
# -flto: LTO itself.
29+
# -fipa-pta: interprocedural pointer analysis — requires whole-program IR, only useful with LTO.
30+
# -ffunction-sections / -fdata-sections / -Wl,--gc-sections: linker dead-code elimination;
31+
# far more effective with LTO because the linker has cross-TU visibility.
32+
LTO_CCFLAGS = ["-flto", "-fipa-pta", "-ffunction-sections", "-fdata-sections"]
33+
LTO_LDFLAGS = ["-flto", "-Wl,--gc-sections"]
34+
35+
for flaglist, new_flags in (("CCFLAGS", LTO_CCFLAGS),
36+
("CXXFLAGS", LTO_CCFLAGS),
37+
("LINKFLAGS", LTO_LDFLAGS)):
38+
existing = env.get(flaglist, [])
39+
to_add = [f for f in new_flags if f not in existing]
40+
if to_add:
41+
env.Append(**{flaglist: to_add})
42+
43+
# Swap ar / ranlib for the LTO-aware GCC wrappers so that static
44+
# library archives carry IR that the linker can optimise across.
45+
cc = str(env.get("CC", ""))
46+
if cc:
47+
toolchain_prefix = cc.replace("gcc", "").replace("g++", "")
48+
# toolchain_prefix is something like "xtensa-esp32s3-elf-"
49+
new_ar = toolchain_prefix + "gcc-ar"
50+
new_ranlib = toolchain_prefix + "gcc-ranlib"
51+
52+
# Resolve CC to its real path so we can search the same directory
53+
cc_resolved = shutil.which(cc) or cc
54+
cc_dir = os.path.dirname(cc_resolved)
55+
56+
ar_path = _find_tool(new_ar, cc_dir)
57+
if ar_path:
58+
env.Replace(AR=ar_path)
59+
print(f"enable_lto: AR -> {ar_path}")
60+
else:
61+
print(f"enable_lto: gcc-ar '{new_ar}' not found, keeping default AR")
62+
63+
ranlib_path = _find_tool(new_ranlib, cc_dir)
64+
if ranlib_path:
65+
env.Replace(RANLIB=ranlib_path)
66+
print(f"enable_lto: RANLIB -> {ranlib_path}")
67+
else:
68+
print(f"enable_lto: gcc-ranlib '{new_ranlib}' not found, keeping default RANLIB")
69+
70+
print("enable_lto: -flto added to CCFLAGS / CXXFLAGS / LINKFLAGS")
71+
72+
enable_lto(env)

0 commit comments

Comments
 (0)