diff --git a/README.md b/README.md index 263e7da..7f8871b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The reference compilers live in era containers built from `provision/eras` and p `rk config-diff`, `rk probes`, `rk flags-diff`, `rk demands` and `rk asm-inventory` read build directories and write markdown tables: the `.config` differences, the compiler probes the two compilers answered differently, the flags each unit was compiled with on one side only (read from the `.cmd` files kbuild writes), the failed units by error, and the instructions the kernel writes itself. `rk config-diff --why` also names the Kconfig expressions behind each difference and the probe that decided them, and needs the kernel tree, which it finds through `build.json` or `--source`. `rk syntax --build DIR --cc rucc` replays every unit of a reference build through rucc's front end with the reference's own command lines, `-fsyntax-only` for C and `-E` for assembly, and writes the failing units grouped by their first error. Units listed with an issue in `syntax-known.toml` are known failures, so the command fails only on new ones. -`rk sections-diff`, `rk symvers-diff`, `rk vec-audit`, `rk modules-audit` and `rk objtool-report` look at what the compilers produced. `rk sections-diff` compares the section names of every object two builds share, the size and relocation count of each kernel table (exports, jump labels, alternatives, exception fixups, initcalls and the rest), the call-site lists per function, and the `.modinfo` and `__ksymtab_strings` strings. It lists the first 60 differences, and every one with `--all`. `rk symvers-diff` compares `Module.symvers`, CRCs and namespaces included, and the global symbols of `System.map`. `rk vec-audit` decodes the x86-64 code of every unit built with `-mno-sse` or `-mgeneral-regs-only` and lists the vector and x87 instructions it finds, only those above the reference's count when given `--reference`. `rk modules-audit` fails on any module relocation the loader rejects, a wrong `vermagic` or an imported CRC that does not match `Module.symvers`. `rk objtool-report` buckets the objtool warnings of two build logs by message and function shape. `rk frames` compares the stack frame of every function in two builds made with `rk build --stack-usage`, which passes `KCFLAGS=-fstack-usage` so that each compiler writes a `.su` file next to every object. It fails on a function over `CONFIG_FRAME_WARN` that the reference keeps under it, and on a run time stack high water mark more than 10% above the reference's when both build directories have a `boot.log` from a kernel with `CONFIG_DEBUG_STACK_USAGE`. `rk sections-diff`, `rk vec-audit` and `rk frames` take `--save FILE` to keep what they read as JSON, and accept such a file in place of a build directory, which is how CI compares builds without moving their objects. +`rk sections-diff`, `rk symvers-diff`, `rk vec-audit`, `rk modules-audit` and `rk objtool-report` look at what the compilers produced. `rk sections-diff` compares the section names of every object two builds share, the size and relocation count of each kernel table (exports, jump labels, alternatives, exception fixups, initcalls and the rest), the call-site lists per function, and the `.modinfo` and `__ksymtab_strings` strings. A difference that `sections-divergences.toml` explains, such as a constant pool only gcc builds or a WARN check only one compiler could prove dead, is listed apart with its reason and does not fail the run. It lists the first 60 differences of each kind, and every one with `--all`. `rk symvers-diff` compares `Module.symvers`, CRCs and namespaces included, and the global symbols of `System.map`. `rk vec-audit` decodes the x86-64 code of every unit built with `-mno-sse` or `-mgeneral-regs-only` and lists the vector and x87 instructions it finds, only those above the reference's count when given `--reference`. `rk modules-audit` fails on any module relocation the loader rejects, a wrong `vermagic` or an imported CRC that does not match `Module.symvers`. `rk objtool-report` buckets the objtool warnings of two build logs by message and function shape. `rk frames` compares the stack frame of every function in two builds made with `rk build --stack-usage`, which passes `KCFLAGS=-fstack-usage` so that each compiler writes a `.su` file next to every object. It fails on a function over `CONFIG_FRAME_WARN` that the reference keeps under it, and on a run time stack high water mark more than 10% above the reference's when both build directories have a `boot.log` from a kernel with `CONFIG_DEBUG_STACK_USAGE`. `rk sections-diff`, `rk vec-audit` and `rk frames` take `--save FILE` to keep what they read as JSON, and accept such a file in place of a build directory, which is how CI compares builds without moving their objects. The nightly workflow builds every pin in the current set on X64 with gcc-14 and with rucc, from `tinyconfig` and the test fragment, and runs `rk test` on boot, smoke and KUnit. When the rucc kernel fails a unit, `rk mixed` runs on the first one in the same job. The run's summary has a line per version, green or red, and what `rk mixed` found. A manual run takes a branch or commit of tamnd/rucc and another configuration target. diff --git a/crates/rk/src/main.rs b/crates/rk/src/main.rs index 27a08bb..31d2917 100644 --- a/crates/rk/src/main.rs +++ b/crates/rk/src/main.rs @@ -62,7 +62,7 @@ fn main() -> ExitCode { "probes" => probes_command(&args), "flags-diff" => flags_diff(&repo, &args), "syntax" => syntax_command(&repo, &args), - "sections-diff" => sections_diff(&args), + "sections-diff" => sections_diff(&repo, &args), "symvers-diff" => symvers_diff(&args), "vec-audit" => vec_audit(&args), "modules-audit" => modules_audit(&args), @@ -487,7 +487,7 @@ fn verdict(clean: bool) -> ExitCode { } /// Compare the sections and kernel tables of every object in two builds. -fn sections_diff(args: &Args) -> Result { +fn sections_diff(repo: &Repo, args: &Args) -> Result { let reference = std::path::Path::new( args.get("reference") .ok_or("rk sections-diff needs --reference")?, @@ -508,7 +508,8 @@ fn sections_diff(args: &Args) -> Result { return Err("the reference has no objects; was it built?".to_string()); } let other = objects::load_or_scan(std::path::Path::new(other), sections::scan)?; - let comparison = sections::compare(&reference, &other); + let mut comparison = sections::compare(&reference, &other); + sections::Divergences::load(&repo.file("sections-divergences.toml"))?.explain(&mut comparison); print!("{}", sections::report(&comparison, args.has("all"))); Ok(verdict(comparison.clean())) } diff --git a/crates/rk/src/sections.rs b/crates/rk/src/sections.rs index 5eee205..205ba6b 100644 --- a/crates/rk/src/sections.rs +++ b/crates/rk/src/sections.rs @@ -19,6 +19,11 @@ //! writes, are compared per function, for the functions both objects have. The strings of //! `.modinfo` and `__ksymtab_strings` are the same. //! +//! Some differences are choices each optimizer makes for itself, such as a constant pool, a cold +//! section or a check one compiler proves can never fire. `sections-divergences.toml` lists them +//! with a reason, and a difference whose every item it explains is reported apart and does not +//! fail the run. +//! //! A build directory is read by walking its objects. `--save FILE` keeps what was found as JSON, //! so that CI can compare two builds without carrying their objects between jobs. @@ -448,6 +453,12 @@ pub struct Difference { pub reference: String, /// The other side. pub other: String, + /// The names or sites only the reference has, when the difference is between two sets. + pub reference_items: Vec, + /// The names or sites only the other build has, when the difference is between two sets. + pub other_items: Vec, + /// Why, when `sections-divergences.toml` explains every item on both sides. + pub reason: Option, } /// The comparison of two builds. @@ -464,15 +475,116 @@ pub struct Comparison { } impl Comparison { - /// Whether nothing differs. + /// Whether nothing differs that `sections-divergences.toml` does not explain. #[must_use] pub fn clean(&self) -> bool { - self.differences.is_empty() && self.only_other.is_empty() + self.differences.iter().all(|d| d.reason.is_some()) && self.only_other.is_empty() } } -fn set_difference(a: &BTreeSet, b: &BTreeSet) -> String { - a.difference(b).cloned().collect::>().join(", ") +/// Which build has the item a rule explains. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Only { + /// Only the reference. + Reference, + /// Only the other build. + Other, + /// Either one. + Either, +} + +/// `sections-divergences.toml`. +#[derive(Debug, Clone, Default, Deserialize)] +pub struct Divergences { + /// Every rule. + #[serde(default, rename = "divergence")] + pub divergences: Vec, +} + +/// A section name or table site that may be in one build and not the other, and why. +#[derive(Debug, Clone, Deserialize)] +pub struct Divergence { + /// `sections` or the table, such as `__bug_table`. + pub what: String, + /// The object, when the rule is for one. A trailing `*` matches any object with that prefix. + #[serde(default)] + pub object: Option, + /// Which build has the item. + pub only: Only, + /// The section name or site as the report prints it. A trailing `*` matches any with that + /// prefix, so `*` alone matches every one. + pub item: String, + /// Why it differs. + pub reason: String, +} + +fn matches(pattern: &str, s: &str) -> bool { + match pattern.strip_suffix('*') { + Some(prefix) => s.starts_with(prefix), + None => pattern == s, + } +} + +impl Divergences { + /// Read `sections-divergences.toml`. + pub fn load(path: &Path) -> Result { + let text = std::fs::read_to_string(path) + .map_err(|e| format!("reading {}: {e}", path.display()))?; + Self::parse(&text).map_err(|e| format!("{}: {e}", path.display())) + } + + /// Read the text of `sections-divergences.toml`. + pub fn parse(text: &str) -> Result { + let divergences: Self = toml::from_str(text).map_err(|e| e.to_string())?; + for d in &divergences.divergences { + if d.reason.trim().is_empty() { + return Err(format!("{} {} has no reason", d.what, d.item)); + } + } + Ok(divergences) + } + + /// The rule that explains one item of a difference, if there is one. + fn rule(&self, d: &Difference, only: Only, item: &str) -> Option<&Divergence> { + self.divergences.iter().find(|r| { + r.what == d.what + && (r.only == only || r.only == Only::Either) + && r.object.as_deref().is_none_or(|o| matches(o, &d.object)) + && matches(&r.item, item) + }) + } + + /// Gives every difference whose items the rules all explain its reasons. + pub fn explain(&self, c: &mut Comparison) { + for d in &mut c.differences { + if d.reference_items.is_empty() && d.other_items.is_empty() { + continue; + } + let sides = [ + (Only::Reference, &d.reference_items), + (Only::Other, &d.other_items), + ]; + let mut reasons: Vec<&str> = Vec::new(); + let mut all = true; + for (only, items) in sides { + for item in items { + match self.rule(d, only, item) { + Some(r) if !reasons.contains(&r.reason.as_str()) => reasons.push(&r.reason), + Some(_) => {} + None => all = false, + } + } + } + if all { + d.reason = Some(reasons.join(". ")); + } + } + } +} + +fn set_difference(a: &BTreeSet, b: &BTreeSet) -> Vec { + a.difference(b).cloned().collect() } fn list_difference(a: &[String], b: &[String]) -> String { @@ -484,22 +596,38 @@ fn list_difference(a: &[String], b: &[String]) -> String { .join(" ") } +/// A difference with no items, for a comparison that is not between two sets. +fn plain(object: &str, what: &str, reference: String, other: String) -> Difference { + Difference { + object: object.to_string(), + what: what.to_string(), + reference, + other, + reference_items: Vec::new(), + other_items: Vec::new(), + reason: None, + } +} + +/// A difference between two sets, with the items each side has alone. +fn between(object: &str, what: &str, a: &BTreeSet, b: &BTreeSet) -> Difference { + let reference_items = set_difference(a, b); + let other_items = set_difference(b, a); + Difference { + object: object.to_string(), + what: what.to_string(), + reference: reference_items.join(", "), + other: other_items.join(", "), + reference_items, + other_items, + reason: None, + } +} + fn compare_object(name: &str, r: &Object, o: &Object, out: &mut Vec) { #![allow(clippy::many_single_char_names)] - let mut push = |what: &str, reference: String, other: String| { - out.push(Difference { - object: name.to_string(), - what: what.to_string(), - reference, - other, - }); - }; if r.sections != o.sections { - push( - "sections", - set_difference(&r.sections, &o.sections), - set_difference(&o.sections, &r.sections), - ); + out.push(between(name, "sections", &r.sections, &o.sections)); } let tables: BTreeSet<&String> = r.tables.keys().chain(o.tables.keys()).collect(); let empty = BTreeSet::new(); @@ -508,18 +636,19 @@ fn compare_object(name: &str, r: &Object, o: &Object, out: &mut Vec) let a = r.distinct.get(table).unwrap_or(&empty); let b = o.distinct.get(table).unwrap_or(&empty); if a != b { - push(table, set_difference(a, b), set_difference(b, a)); + out.push(between(name, table, a, b)); } continue; } let a = r.tables.get(table).copied().unwrap_or_default(); let b = o.tables.get(table).copied().unwrap_or_default(); if a != b { - push( + out.push(plain( + name, table, format!("{} bytes, {} relocs", a.size, a.relocs), format!("{} bytes, {} relocs", b.size, b.relocs), - ); + )); } } let lists: BTreeSet<&String> = r.sites.keys().chain(o.sites.keys()).collect(); @@ -530,27 +659,30 @@ fn compare_object(name: &str, r: &Object, o: &Object, out: &mut Vec) for (function, x) in a { let Some(y) = b.get(function) else { continue }; if x != y { - push( + out.push(plain( + name, &format!("{list} in {function}"), x.to_string(), y.to_string(), - ); + )); } } } if r.modinfo != o.modinfo { - push( + out.push(plain( + name, ".modinfo", list_difference(&r.modinfo, &o.modinfo), list_difference(&o.modinfo, &r.modinfo), - ); + )); } if r.ksymtab_strings != o.ksymtab_strings { - push( + out.push(plain( + name, "__ksymtab_strings", list_difference(&r.ksymtab_strings, &o.ksymtab_strings), list_difference(&o.ksymtab_strings, &r.ksymtab_strings), - ); + )); } } @@ -592,31 +724,50 @@ fn cell(s: &str) -> String { } } -/// The comparison as markdown, with the first 60 differences or, with `all`, every one. +/// The comparison as markdown, unexplained differences first, with the first 60 of each kind or, +/// with `all`, every one. #[must_use] pub fn report(c: &Comparison, all: bool) -> String { - let rows = if all { c.differences.len() } else { ROWS }; + let rows = if all { usize::MAX } else { ROWS }; + let (explained, unexplained): (Vec<&Difference>, Vec<&Difference>) = + c.differences.iter().partition(|d| d.reason.is_some()); let mut s = String::from("### Sections and kernel tables\n\n"); let _ = writeln!( s, - "{} objects compared, {} only in the reference, {} only in the other build, {} differences.\n", + "{} objects compared, {} only in the reference, {} only in the other build, {} differences, {} explained and {} not.\n", c.shared, c.only_reference.len(), c.only_other.len(), - c.differences.len() + c.differences.len(), + explained.len(), + unexplained.len() ); - let mut by_what: BTreeMap<&str, usize> = BTreeMap::new(); + let mut by_what: BTreeMap<&str, (usize, usize)> = BTreeMap::new(); for d in &c.differences { let what = d.what.split(" in ").next().unwrap_or(&d.what); - *by_what.entry(what).or_default() += 1; + let counts = by_what.entry(what).or_default(); + if d.reason.is_some() { + counts.1 += 1; + } else { + counts.0 += 1; + } } if !by_what.is_empty() { - s.push_str("| what | objects or functions |\n|---|---|\n"); - for (what, n) in &by_what { - let _ = writeln!(s, "| `{what}` | {n} |"); + s.push_str("| what | not explained | explained |\n|---|---|---|\n"); + for (what, (not, yes)) in &by_what { + let _ = writeln!(s, "| `{what}` | {not} | {yes} |"); } - s.push_str("\n| object | what | reference only | other only |\n|---|---|---|---|\n"); - for d in c.differences.iter().take(rows) { + s.push('\n'); + } + let more = |s: &mut String, n: usize| { + if n > rows { + let _ = writeln!(s, "\nand {} more, which `--all` lists.", n - rows); + } + s.push('\n'); + }; + if !unexplained.is_empty() { + s.push_str("| object | what | reference only | other only |\n|---|---|---|---|\n"); + for d in unexplained.iter().take(rows) { let _ = writeln!( s, "| {} | {} | {} | {} |", @@ -626,14 +777,22 @@ pub fn report(c: &Comparison, all: bool) -> String { cell(&d.other) ); } - if c.differences.len() > rows { + more(&mut s, unexplained.len()); + } + if !explained.is_empty() { + s.push_str("Explained:\n\n| object | what | reference only | other only | reason |\n|---|---|---|---|---|\n"); + for d in explained.iter().take(rows) { let _ = writeln!( s, - "\nand {} more, which `--all` lists.", - c.differences.len() - rows + "| {} | {} | {} | {} | {} |", + d.object, + cell(&d.what), + cell(&d.reference), + cell(&d.other), + d.reason.as_deref().unwrap_or_default().replace('|', "\\|") ); } - s.push('\n'); + more(&mut s, explained.len()); } if !c.only_reference.is_empty() { let shown: Vec<&str> = c @@ -761,6 +920,70 @@ mod tests { assert!(compare(&reference, &other).clean()); } + #[test] + fn a_difference_is_explained_only_when_every_item_is() { + let names = + |n: &[&str]| -> BTreeSet { n.iter().map(ToString::to_string).collect() }; + let mut c = Comparison { + shared: 2, + differences: vec![ + between("a.o", "sections", &names(&[".rodata.cst16"]), &names(&[])), + between( + "b.o", + "sections", + &names(&[".rodata.cst16"]), + &names(&[".data.rucc"]), + ), + between("c.o", "__bug_table", &names(&[]), &names(&["8=\"c.c\" 01"])), + between("c.o", "__jump_table", &names(&["8=key"]), &names(&[])), + ], + ..Comparison::default() + }; + let rules = Divergences::parse( + r#" +[[divergence]] +what = "sections" +only = "reference" +item = ".rodata.cst*" +reason = "a pool" + +[[divergence]] +what = "__bug_table" +only = "other" +item = "*" +reason = "a kept check" + +[[divergence]] +what = "__jump_table" +object = "d.o" +only = "reference" +item = "8=key" +reason = "another object" +"#, + ) + .unwrap(); + rules.explain(&mut c); + let reasons: Vec> = + c.differences.iter().map(|d| d.reason.as_deref()).collect(); + assert_eq!(reasons, [Some("a pool"), None, Some("a kept check"), None]); + assert!(!c.clean()); + let text = report(&c, false); + assert!( + text.contains("4 differences, 2 explained and 2 not."), + "{text}" + ); + assert!(text.contains("| `sections` | 1 | 1 |"), "{text}"); + c.differences.retain(|d| d.reason.is_some()); + assert!(c.clean()); + } + + #[test] + fn the_divergences_file_parses_and_every_rule_has_a_reason() { + let rules = Divergences::parse(include_str!("../../../sections-divergences.toml")).unwrap(); + assert!(!rules.divergences.is_empty()); + assert!(Divergences::parse("[[divergence]]\nwhat = \"sections\"\nonly = \"either\"\nitem = \".x\"\nreason = \" \"\n").is_err()); + } + #[test] fn an_object_the_other_build_lacks_is_listed_but_not_a_difference() { let reference: Inventory = [ diff --git a/sections-divergences.toml b/sections-divergences.toml new file mode 100644 index 0000000..a11e577 --- /dev/null +++ b/sections-divergences.toml @@ -0,0 +1,138 @@ +# Differences in sections and kernel tables between the reference's objects and rucc's that are +# expected, and why. +# +# rk sections-diff reads this file. A difference is explained when a rule here covers every +# section name or site on both sides of it. Anything else is a finding: either rucc lost a table +# entry or made a section it should not, or the difference is fine and belongs here with a reason. +# +# what is "sections" or the table, such as "__bug_table". only says which build has the item: +# "reference", "other" or "either". item is the section name or site as the report prints it, and +# object, when given, limits the rule to one object. A trailing star in item or object matches +# anything with that prefix. Every rule must say why. A rule for one object should say what was +# checked, since a site the reference has and rucc does not is exactly what a lost asm goto or +# pushsection looks like. + +# Sections each optimizer makes for itself. The kernel's linker script places all of them, so +# none is an orphan in either build. + +[[divergence]] +what = "sections" +only = "reference" +item = ".rodata.cst*" +reason = "a constant pool gcc builds for its own instruction choices, such as a vector mask, a floating point constant or a wide immediate, where rucc writes the value into the instruction" + +[[divergence]] +what = "sections" +only = "either" +item = ".rodata" +reason = "a switch turned into a lookup table, or a constant local array kept in memory, which follows each compiler's own switch conversion and scalar replacement" + +[[divergence]] +what = "sections" +only = "either" +item = ".rodata.str*" +reason = "a string only one compiler keeps, because the other proved the code that used it dead or built the string with immediate stores" + +[[divergence]] +what = "sections" +only = "either" +item = ".text.unlikely" +reason = "cold code, which each compiler finds with its own branch prediction and call graph" + +# Sites. Every inlined copy of a WARN_ON or a static branch writes an entry, and so does every +# copy the optimizer could not remove. A site only rucc has is a check gcc proved can never fire +# and removed, which costs an entry and loses nothing. + +[[divergence]] +what = "__bug_table" +only = "other" +item = "*" +reason = "a WARN or BUG check rucc keeps where gcc proved the condition never holds and removed it" + +# Sites only the reference has. Each one was read against the source and the callers in that +# object, and in each rucc proved the check false after inlining while gcc kept an out of line +# copy or did not carry the range that far. + +[[divergence]] +what = "__bug_table" +object = "arch/x86/kernel/alternative.o" +only = "reference" +item = '4="" 8="arch/x86/include/asm/text-patching.h" 61000000' +reason = "BUG_ON(size < text_opcode_size(opcode)) in __text_gen_insn, where every caller passes sizeof the instruction union, which is at least the largest opcode size of 5" + +[[divergence]] +what = "__bug_table" +object = "drivers/char/random.o" +only = "reference" +item = '4="" 8="drivers/char/random.c" 37010000' +reason = "BUG_ON(random_data_len > 32) in crng_fast_key_erasure, where every length comes from min_t(size_t, 32, len) or a sizeof no larger than 32" + +[[divergence]] +what = "__bug_table" +object = "drivers/char/random.o" +only = "reference" +item = '4="" 8="drivers/char/random.c" 4e010000' +reason = "BUG_ON(random_data_len > 32) in crng_make_state, with the same callers and bound as crng_fast_key_erasure" + +[[divergence]] +what = "__bug_table" +object = "drivers/net/ethernet/intel/e1000/e1000_main.o" +only = "reference" +item = '4="" 8="include/net/netmem.h" a5000309' +reason = "WARN_ON_ONCE(netmem_is_net_iov(netmem)) in netmem_to_page, where the driver only passes a netmem made from a struct page, so the net_iov bit is clear" + +[[divergence]] +what = "__bug_table" +object = "drivers/net/ethernet/intel/e1000e/netdev.o" +only = "reference" +item = '4="" 8="include/net/netmem.h" a5000309' +reason = "WARN_ON_ONCE(netmem_is_net_iov(netmem)) in netmem_to_page, where the driver only passes a netmem made from a struct page, so the net_iov bit is clear" + +[[divergence]] +what = "__bug_table" +object = "drivers/net/ethernet/nvidia/forcedeth.o" +only = "reference" +item = '4="" 8="include/net/netmem.h" a5000309' +reason = "WARN_ON_ONCE(netmem_is_net_iov(netmem)) in netmem_to_page, where the driver only passes a netmem made from a struct page, so the net_iov bit is clear" + +[[divergence]] +what = "__bug_table" +object = "net/ipv4/tcp_output.o" +only = "reference" +item = '4="" 8="include/net/netmem.h" a5000309' +reason = "WARN_ON_ONCE(netmem_is_net_iov(netmem)) in netmem_to_page, reached here only with a netmem made from a struct page, so the net_iov bit is clear" + +[[divergence]] +what = "__bug_table" +object = "fs/quota/quota.o" +only = "reference" +item = '4="" 8="include/linux/quota.h" 72000000' +reason = "the BUG() default of make_kqid, where the type is the low byte of the command and quota.c rejects MAXQUOTAS or more before it gets there, so the switch covers every value left" + +[[divergence]] +what = "sections" +object = "fs/quota/quota.o" +only = "reference" +item = "__bug_table" +reason = "the make_kqid BUG() above was the only site in the object, so rucc has no table at all" + +[[divergence]] +what = "sections" +object = "fs/quota/quota.o" +only = "reference" +item = ".discard.annotate_*" +reason = "the objtool annotations the make_kqid BUG() writes, which go with it" + +[[divergence]] +what = "__bug_table" +object = "kernel/sched/build_utility.o" +only = "reference" +item = '4="" 8="kernel/sched/cpupri.c" 98000309' +reason = "WARN_ON_ONCE(task_pri >= CPUPRI_NR_PRIORITIES) in cpupri_find_fitness, where convert_prio has no default case and every value it defines is between -1 and 100" + +[[divergence]] +what = "__bug_table" +object = "kernel/signal.o" +only = "reference" +item = '4="" 8="include/linux/ucopysize.h" 39000309' +reason = "WARN_ON_ONCE(bytes > INT_MAX) in check_copy_size, where every copy in signal.c has a sizeof or a length bounded by one"