makepad/tools/run/fixture/diff/entry_key_move.rs

102 lines
3.8 KiB
Rust

// diff_fast corpus program: HashMap::entry with a by-value struct key whose Drop reads every field.
// DFREE (Sandbox on 166, double free in glibc): the inlined rustc_entry moved the key into the
// Occupied variant one word above where it lay (same frame storage), with a forward copy, so the
// key the occupied path drops had its words shifted (len = ptr, the live-edit ext word = the tag
// word's stale stack bytes) and its drop freed a stack address. rustc --test -O and `makepad test`
// (all tiers) must print identical D lines.
use std::collections::HashMap;
macro_rules! d { ($($a:tt)*) => { println!("D {}", format!($($a)*)) } }
fn rounds(f: fn()) {
let ms: u64 = std::env::var("DIFF_SLEEP_MS").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
for r in 0..4 { f(); if r < 3 && ms > 0 { std::thread::sleep(std::time::Duration::from_millis(ms)); } }
}
#[test]
fn run() {
rounds(work);
}
static DROPS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
#[derive(PartialEq, Eq, Hash)]
struct Key {
name: String,
kind: u8,
tail: u64,
}
impl Drop for Key {
fn drop(&mut self) {
// every word of the key: a shifted copy shows here (len, tail)
let h = self.name.len() as u64 ^ (self.kind as u64) << 8 ^ self.tail.rotate_left(17) ^ self.name.bytes().map(|b| b as u64).sum::<u64>() << 20;
DROPS.fetch_add(h, std::sync::atomic::Ordering::Relaxed);
}
}
/// The shape of the Sandbox function: an owned key from a &str and a u8, the entry's counters.
#[inline(never)]
fn hit(map: &mut HashMap<Key, (u32, u32)>, name: &str, kind: u8, tail: u64, force: bool) -> bool {
let e = map.entry(Key { name: name.to_string(), kind, tail }).or_insert((0, 0));
e.1 += 1;
if e.0 >= 6 && !force {
return false;
}
if e.1 < 60 {
return false;
}
e.1 = 0;
if !force {
e.0 += 1;
}
true
}
/// The key handed in by value (an argument slot, not a temporary of the caller).
#[inline(never)]
fn hit_key(map: &mut HashMap<Key, (u32, u32)>, k: Key) -> u32 {
let e = map.entry(k).or_insert((0, 0));
e.1 += 1;
e.1
}
/// `Option<Key>` taken through entry (the occupied variant carries `Some(key)`).
#[inline(never)]
fn hit_opt(map: &mut HashMap<Key, u64>, k: Option<Key>) -> u64 {
match k {
Some(k) => {
let v = map.entry(k).or_default();
*v += 3;
*v
}
None => 0,
}
}
fn work() {
DROPS.store(0, std::sync::atomic::Ordering::Relaxed);
let names = ["Microsoft Xbox Controller", "sandbox/games/classic-doom", "pad", "gen/characters/real-goat", "x"];
let mut map: HashMap<Key, (u32, u32)> = HashMap::new();
let mut fired = 0u64;
let mut sum = 0u64;
for i in 0..2000u64 {
let n = names[(i % 5) as usize];
if hit(&mut map, n, (i % 3) as u8, i % 7, i % 11 == 0) {
fired += 1 + i;
}
sum = sum.wrapping_add(hit_key(&mut map, Key { name: format!("{n}#{}", i % 13), kind: (i % 2) as u8, tail: i % 5 }) as u64);
}
let mut m2: HashMap<Key, u64> = HashMap::new();
let mut s2 = 0u64;
for i in 0..500u64 {
let k = if i % 9 == 0 { None } else { Some(Key { name: names[(i % 4) as usize].to_string(), kind: (i % 3) as u8, tail: i % 4 }) };
s2 = s2.wrapping_add(hit_opt(&mut m2, k));
}
let mut ks: Vec<(String, u8, u64, u32, u32)> = map.iter().map(|(k, v)| (k.name.clone(), k.kind, k.tail, v.0, v.1)).collect();
ks.sort();
let mut h = 0u64;
for (n, k, t, a, b) in &ks {
h = h.wrapping_mul(31).wrapping_add(n.len() as u64 ^ (*k as u64) << 8 ^ t << 16 ^ (*a as u64) << 24 ^ (*b as u64) << 40);
}
d!("entries {} fired {} sum {} s2 {} table {}", ks.len(), fired, sum, s2, h);
drop(map);
drop(m2);
d!("drops {}", DROPS.load(std::sync::atomic::Ordering::Relaxed));
}