Skip to main content

scx_cargo/
clang_info.rs

1use std::{collections::HashMap, env, process::Command};
2
3use anyhow::{Context, Result, anyhow, bail};
4use sscanf::sscanf;
5
6lazy_static::lazy_static! {
7    // Map clang archs to the __TARGET_ARCH list in
8    // tools/lib/bpf/bpf_tracing.h in the kernel tree.
9    static ref ARCH_MAP: HashMap<&'static str, &'static str> = vec![
10    ("x86", "x86"),
11    ("x86_64", "x86"),
12    ("s390", "s390"),
13    ("s390x", "s390"),
14    ("arm", "arm"),
15    ("aarch64", "arm64"),
16    ("mips", "mips"),
17    ("mips64", "mips"),
18    ("ppc32", "powerpc"),
19    ("ppc64", "powerpc"),
20    ("ppc64le", "powerpc"),
21    ("powerpc64le", "powerpc"),
22    ("sparc", "sparc"),
23    ("sparcv9", "sparc"),
24    ("riscv32", "riscv"),
25    ("riscv64", "riscv"),
26    ("riscv64gc", "riscv"),
27    ("arc", "arc"),			// unsure this is supported
28    ("loongarch64", "loongarch"),	// ditto
29    ].into_iter().collect();
30}
31#[derive(Debug)]
32#[allow(dead_code)]
33pub struct ClangInfo {
34    pub clang: String,
35    pub ver: String,
36    pub arch: String,
37}
38
39impl ClangInfo {
40    pub fn new() -> Result<ClangInfo> {
41        let mut clang_args = vec!["--version".to_string()];
42
43        if let Ok(target) = env::var("TARGET") {
44            clang_args.push(format!("--target={target}"));
45        }
46
47        let clang = env::var("BPF_CLANG").unwrap_or("clang".into());
48        let output = Command::new(&clang)
49            .args(clang_args)
50            .output()
51            .with_context(|| format!("Failed to run \"{} --version\"", clang))?;
52
53        let stdout = String::from_utf8(output.stdout)?;
54        let (mut ver, mut arch) = (None, None);
55        for line in stdout.lines() {
56            if let Some(v) = sscanf!(
57                Self::skip_clang_version_prefix(line),
58                "clang version {String}"
59            ) {
60                // Version could be followed by (URL SHA1). Only take
61                // the first word.
62                ver = Some(v.split_whitespace().next().unwrap().to_string());
63                continue;
64            }
65            if let Some(v) = sscanf!(line, "Target: {String}") {
66                arch = Some(v.split('-').next().unwrap().to_string());
67                continue;
68            }
69        }
70
71        let (ver, arch) = (
72            ver.ok_or(anyhow!("Failed to read clang version"))?,
73            arch.ok_or(anyhow!("Failed to read clang target arch"))?,
74        );
75
76        if version_compare::compare(&ver, "18") == Ok(version_compare::Cmp::Lt) {
77            bail!(
78                "clang < 18 does not support -mcpu=v4 for BPF ({:?} ver={:?})",
79                clang,
80                ver
81            );
82        }
83
84        Ok(ClangInfo { clang, ver, arch })
85    }
86
87    fn skip_clang_version_prefix(line: &str) -> &str {
88        if let Some(index) = line.find("clang version") {
89            &line[index..]
90        } else {
91            line
92        }
93    }
94
95    pub fn kernel_target(&self) -> Result<String> {
96        // Determine kernel target arch.
97        match ARCH_MAP.get(self.arch.as_str()) {
98            Some(v) => Ok(v.to_string()),
99            None => Err(anyhow!("CPU arch {} not found in ARCH_MAP", self.arch)),
100        }
101    }
102
103    #[allow(dead_code)] // for it is not used during build script execution
104    pub fn determine_base_cflags(&self) -> Result<Vec<String>> {
105        let kernel_target = self.kernel_target()?;
106
107        // Determine system includes.
108        let output = Command::new(&self.clang)
109            .args(["-v", "-E", "-"])
110            .output()
111            .with_context(|| format!("Failed to run \"{} -v -E - < /dev/null", self.clang))?;
112        let stderr = String::from_utf8(output.stderr)?;
113
114        let mut sys_incls = None;
115        for line in stderr.lines() {
116            if line == "#include <...> search starts here:" {
117                sys_incls = Some(vec![]);
118                continue;
119            }
120            if sys_incls.is_none() {
121                continue;
122            }
123            if line == "End of search list." {
124                break;
125            }
126
127            sys_incls.as_mut().unwrap().push(line.trim());
128        }
129        let sys_incls = match sys_incls {
130            Some(v) => v,
131            None => bail!("Failed to find system includes from {:?}", self.clang),
132        };
133
134        // Determine endian.
135        let output = Command::new(&self.clang)
136            .args(["-dM", "-E", "-"])
137            .output()
138            .with_context(|| format!("Failed to run \"{} -dM E - < /dev/null", self.clang))?;
139        let stdout = String::from_utf8(output.stdout)?;
140
141        let mut endian = None;
142        for line in stdout.lines() {
143            if let Some(v) = sscanf!(line, "#define __BYTE_ORDER__ {str}") {
144                endian = Some(match v {
145                    "__ORDER_LITTLE_ENDIAN__" => "little",
146                    "__ORDER_BIG_ENDIAN__" => "big",
147                    v => bail!("Unknown __BYTE_ORDER__ {:?}", v),
148                });
149                break;
150            }
151        }
152        let endian = match endian {
153            Some(v) => v,
154            None => bail!("Failed to find __BYTE_ORDER__ from {:?}", self.clang),
155        };
156
157        // Assemble cflags.
158        let mut cflags: Vec<String> = ["-g", "-O2", "-Wall", "-Wno-compare-distinct-pointer-types"]
159            .into_iter()
160            .map(|x| x.into())
161            .collect();
162        cflags.push(format!("-D__TARGET_ARCH_{}", kernel_target));
163        cflags.push("-mcpu=v4".into());
164        // Two v4 lowerings outrun the kernel baseline and stay off until the
165        // minimum supported kernel reaches the release that added them.
166        //
167        // Sign-extending loads (ldsx) from arena memory need JIT support,
168        // which x86 and arm64 gained in v6.18. Drop -disable-ldsx once the
169        // minimum kernel is v6.18.
170        cflags.extend(["-mllvm".into(), "-disable-ldsx".into()]);
171        // clang 22+ lowers large switches to jump tables and gotox, which
172        // x86 and arm64 gained in v6.19. Drop -disable-gotox once the
173        // minimum kernel is v6.19.
174        if matches!(
175            version_compare::compare(&self.ver, "22"),
176            Ok(version_compare::Cmp::Eq | version_compare::Cmp::Gt)
177        ) {
178            cflags.extend(["-mllvm".into(), "-disable-gotox".into()]);
179        }
180        cflags.push(format!("-m{endian}-endian"));
181        cflags.append(
182            &mut sys_incls
183                .into_iter()
184                .flat_map(|x| ["-idirafter".into(), x.into()])
185                .collect(),
186        );
187        Ok(cflags)
188    }
189}