Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ All notable changes are recorded here. The format follows [Keep a Changelog](htt
- A structure passed by value on i386 is copied into the outgoing area a word at a time, so a large one no longer has memcpy's own arguments written over the ones already stored. 00204 of c-testsuite printed garbage for the first of two large structures (#3710).
- `tests/tools/perf.sh` skips a call graph that perf cannot record on the machine, with a line that gives perf's reason, rather than stopping with exit 255. The GitHub arm runner refuses `--call-graph=dwarf` this way (#3711).
- Three tests that failed only on a Windows host hold there: two compared paths as strings and missed one joined with a `\`, and six lane checks in `wasm_simd_lanes` linked for a host that has no sysroot in the test job (#3712).
- A program built with `--coverage` against a snapshot GCC's `libgcov.a`, such as Ubuntu's `gcc-16` package, writes its counts. rucc took the release from the library's version word but always ended the word in `*`, the mark of a release, and libgcov refused a record whose phase did not match its own (#3713).

## 0.30.8

Expand Down
4 changes: 3 additions & 1 deletion crates/rucc-driver/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,10 +1036,12 @@ fn coverage(opts: &Options, target: &TargetInfo, counts: &str) -> rucc_opt::cove
rucc_target::ObjectFormat::MachO => (Some("__DATA,__mod_init_func,mod_init_funcs"), None),
rucc_target::ObjectFormat::Wasm => (None, None),
};
let (major, minor, phase) = opts.gcov.unwrap_or((opts.gnuc.major, opts.gnuc.minor, b'*'));
rucc_opt::coverage::Coverage {
counts: counts.to_owned(),
count: opts.profile_data.arcs,
gnuc: opts.gcov.unwrap_or((opts.gnuc.major, opts.gnuc.minor)),
gnuc: (major, minor),
phase,
ctor: ctor.map(str::to_owned),
dtor: dtor.map(str::to_owned),
}
Expand Down
35 changes: 25 additions & 10 deletions crates/rucc-driver/src/gcov.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,29 @@
//! instructions that compare a record against it and write it to the head of a `.gcda` file, so the
//! word is in the bytes of the archive. The word is four characters, such as `B33*` for 13.3, and
//! it is stored low byte first, which is how every target this links for stores a word.
//!
//! The last character is the phase. `gcc/genversion.cc` writes `*` for a release and a prerelease
//! and the first character of `DEV-PHASE` for anything else, which is `e` for a build of the trunk.
//! The snapshot of GCC 16 that Ubuntu packages as `gcc-16` writes a space there. libgcov compares
//! the whole word, so the phase is taken from the library too.

/// The major and minor GCC release of the `libgcov.a` with these bytes, or `None` when no version
/// word is in it.
/// The major and minor GCC release of the `libgcov.a` with these bytes and the phase that ends its
/// version word, or `None` when no version word is in it.
///
/// The word that comes up most often is the answer. The archive has it several times, and a run of
/// four bytes that only looks like one is not likely to come up as often.
#[must_use]
pub(crate) fn release(archive: &[u8]) -> Option<(u32, u32)> {
let mut seen: Vec<((u32, u32), usize)> = Vec::new();
pub(crate) fn release(archive: &[u8]) -> Option<(u32, u32, u8)> {
let mut seen: Vec<((u32, u32, u8), usize)> = Vec::new();
for quad in archive.windows(4) {
let &[b'*', minor @ b'0'..=b'9', units @ b'0'..=b'9', tens @ b'A'..=b'Z'] = quad else {
let &[phase, minor @ b'0'..=b'9', units @ b'0'..=b'9', tens @ b'A'..=b'Z'] = quad else {
continue;
};
if !matches!(phase, b'*' | b' ' | b'a'..=b'z') {
continue;
}
let major = u32::from(tens - b'A') * 10 + u32::from(units - b'0');
let release = (major, u32::from(minor - b'0'));
let release = (major, u32::from(minor - b'0'), phase);
match seen.iter_mut().find(|(known, _)| *known == release) {
Some((_, count)) => *count += 1,
None => seen.push((release, 1)),
Expand All @@ -40,10 +48,17 @@ mod tests {
#[test]
fn the_release_is_the_version_word_the_library_holds_most() {
// `B33*` low byte first is GCC 13.3, and `A95*` is 9.5.
assert_eq!(release(b"\x0f\x1f*33B\x90\x90*33B..*25B"), Some((13, 3)));
assert_eq!(release(b"cmp *59A"), Some((9, 5)));
// Lower case and a missing star are not a version word.
assert_eq!(release(b"*33b 33B* plain text"), None);
assert_eq!(release(b"\x0f\x1f*33B\x90\x90*33B..*25B"), Some((13, 3, b'*')));
assert_eq!(release(b"cmp *59A"), Some((9, 5, b'*')));
// A lower case letter and a missing phase are not a version word.
assert_eq!(release(b"*33b x3B: plain text"), None);
}

#[test]
fn the_phase_of_a_build_that_is_not_a_release_is_kept() {
// The trunk writes `e`, and Ubuntu's snapshot of GCC 16 writes a space.
assert_eq!(release(b"\x0fe07B\x90e07B"), Some((17, 0, b'e')));
assert_eq!(release(b"\x0f 06B\x90 06B\x90 06B"), Some((16, 0, b' ')));
assert_eq!(release(b""), None);
}
}
12 changes: 10 additions & 2 deletions crates/rucc-opt/src/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub struct Coverage {
/// built for the same version the headers were told about. The kernel's `gcc_4_7.c` picks its
/// own copy of the layout by `__GNUC__`.
pub gnuc: (u32, u32),
/// The last character of the version word: `*` for a release, and whatever the libgcov the
/// link takes was built with otherwise, such as `e` for a build of GCC's trunk.
pub phase: u8,
/// The section the pointer to the constructor goes in, or `None` where the format has none.
pub ctor: Option<String>,
/// The section the pointer to the destructor goes in. Where this is `None` and the constructor
Expand All @@ -60,12 +63,13 @@ impl Coverage {
/// The version word in the record and at the top of the `.gcda` file.
///
/// Four characters, from `gcc/gcov-iov.cc`: the major version in tens as a letter from `A`
/// and in units as a digit, the minor version as a digit, and `*` for a release.
/// and in units as a digit, the minor version as a digit, and the phase.
#[must_use]
pub fn version(&self) -> u32 {
let (major, minor) = self.gnuc;
let letters = [b'A' as u32 + major / 10, b'0' as u32 + major % 10, b'0' as u32 + minor];
letters.iter().fold(0, |word, &letter| (word << 8) | (letter & 0xff)) << 8 | u32::from(b'*')
letters.iter().fold(0, |word, &letter| (word << 8) | (letter & 0xff)) << 8
| u32::from(self.phase)
}

/// How many merge functions the record holds, one per kind of counter the version knows.
Expand Down Expand Up @@ -814,6 +818,7 @@ mod tests {
counts: "a.gcda".to_owned(),
count: true,
gnuc: (major, minor),
phase: b'*',
ctor: None,
dtor: None,
}
Expand All @@ -825,6 +830,9 @@ mod tests {
assert_eq!(claiming(13, 2).version().to_be_bytes(), *b"B32*");
assert_eq!(claiming(16, 0).version().to_be_bytes(), *b"B60*");
assert_eq!(claiming(9, 4).version().to_be_bytes(), *b"A94*");
// A snapshot keeps the phase its libgcov was built with.
let snapshot = Coverage { phase: b' ', ..claiming(16, 0) };
assert_eq!(snapshot.version().to_be_bytes(), *b"B60 ");
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions crates/rucc-session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2670,13 +2670,13 @@ pub struct Options {
/// `-target x86_64-pc-windows-msvc`: the SDK headers take a path for `__GNUC__` that they
/// were never tested on with the MSVC runtime. The flag is the way to ask, as it is in clang.
pub gnuc_given: bool,
/// The GCC release of the `libgcov.a` the link takes, as the major and the minor version, when
/// a coverage build found one.
/// The GCC release of the `libgcov.a` the link takes, as the major and the minor version and
/// the phase that ends its version word, `*` for a release, when a coverage build found one.
///
/// libgcov writes the counts of a record only when the record has its own release, and
/// [`Options::gnuc`] is the newest release while the GCC on the machine is often older. So the
/// record follows this when it is set, and follows [`Options::gnuc`] when it is not.
pub gcov: Option<(u32, u32)>,
pub gcov: Option<(u32, u32, u8)>,
/// The GNU assembler release claimed, from `-fgnu-as-version=`, which is what
/// `-Wa,--version` prints.
pub gnu_as: GasVersion,
Expand Down
Loading