- matches lower to decision trees; immediates the back ends cannot encode live in constant registers; dense switches are Term::Switch jump tables, whose targets are ordinary successors for dominators and the domain base - x64, settled window: instructions geomean -2.6%, cycles about -7%; rust_tokenize -37%, html_parse -16%, csg_boolean -4.5% - known regression to fix forward: maps_tile_decode +16% (one varint read no longer inlined) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
366 lines
12 KiB
Rust
366 lines
12 KiB
Rust
// Match lowering as a decision tree (`makepad test`; rustc: every test passes). Each case is a
|
|
// match whose arms the tree dispatches (three or more arms over integers, chars, bools, enum
|
|
// variants, tuples of them, references, or-patterns, ranges): the arm taken, the order guards run
|
|
// in (and run again after a failed guard), bindings, and the scrutinee being read once.
|
|
use std::cell::RefCell;
|
|
|
|
thread_local! { static LOG: RefCell<Vec<u32>> = RefCell::new(Vec::new()); }
|
|
fn log(x: u32) -> bool { LOG.with(|l| l.borrow_mut().push(x)); true }
|
|
fn nolog(x: u32) -> bool { LOG.with(|l| l.borrow_mut().push(x)); false }
|
|
fn take() -> Vec<u32> { LOG.with(|l| std::mem::take(&mut *l.borrow_mut())) }
|
|
|
|
fn ints(x: i32) -> u32 {
|
|
match x {
|
|
i32::MIN..=-100 => 1,
|
|
-99..=-1 => 2,
|
|
0 => 3,
|
|
1 | 2 | 3 => 4,
|
|
4..=9 | 20..=29 => 5,
|
|
10..=19 => 6,
|
|
100 => 7,
|
|
_ => 8,
|
|
}
|
|
}
|
|
#[test] fn integer_ranges() {
|
|
let want = |x: i32| -> u32 {
|
|
if x <= -100 { 1 } else if x < 0 { 2 } else if x == 0 { 3 } else if x <= 3 { 4 } else if x <= 9 { 5 } else if x <= 19 { 6 }
|
|
else if x <= 29 { 5 } else if x == 100 { 7 } else { 8 }
|
|
};
|
|
for x in (-300..300).chain([i32::MIN, i32::MAX, i32::MIN + 1, i32::MAX - 1]) {
|
|
assert_eq!(ints(x), want(x), "{x}");
|
|
}
|
|
}
|
|
|
|
fn bytes(c: u8) -> u32 {
|
|
match c {
|
|
b'a'..=b'z' | b'A'..=b'Z' | b'_' => 1,
|
|
b'0'..=b'9' => 2,
|
|
b' ' | b'\n' | b'\t' | b'\r' => 3,
|
|
b'"' => 4,
|
|
b'/' => 5,
|
|
0x80..=0xff => 6,
|
|
_ => 7,
|
|
}
|
|
}
|
|
#[test] fn byte_classes() {
|
|
for c in 0..=255u8 {
|
|
let want = if c.is_ascii_alphabetic() || c == b'_' { 1 } else if c.is_ascii_digit() { 2 } else if matches!(c, b' ' | b'\n' | b'\t' | b'\r') { 3 }
|
|
else if c == b'"' { 4 } else if c == b'/' { 5 } else if c >= 0x80 { 6 } else { 7 };
|
|
assert_eq!(bytes(c), want, "{c}");
|
|
}
|
|
}
|
|
|
|
fn wide(x: u64) -> u32 {
|
|
match x {
|
|
0 => 1,
|
|
1..=0xffff_ffff => 2,
|
|
0x1_0000_0000..=0x7fff_ffff_ffff_ffff => 3,
|
|
0x8000_0000_0000_0000..=0xffff_ffff_ffff_fffe => 4,
|
|
u64::MAX => 5,
|
|
}
|
|
}
|
|
fn signed8(x: i8) -> u32 {
|
|
match x {
|
|
i8::MIN => 1,
|
|
-127..=-1 => 2,
|
|
0 => 3,
|
|
1..=126 => 4,
|
|
i8::MAX => 5,
|
|
}
|
|
}
|
|
#[test] fn extreme_values() {
|
|
for (x, w) in [(0u64, 1), (1, 2), (0xffff_ffff, 2), (0x1_0000_0000, 3), (0x7fff_ffff_ffff_ffff, 3), (0x8000_0000_0000_0000, 4), (u64::MAX - 1, 4), (u64::MAX, 5)] {
|
|
assert_eq!(wide(x), w, "{x}");
|
|
}
|
|
for x in i8::MIN..=i8::MAX {
|
|
let w = if x == i8::MIN { 1 } else if x < 0 { 2 } else if x == 0 { 3 } else if x < 127 { 4 } else { 5 };
|
|
assert_eq!(signed8(x), w, "{x}");
|
|
}
|
|
}
|
|
|
|
fn chars(c: char) -> u32 {
|
|
match c {
|
|
'a'..='z' => 1,
|
|
'A'..='Z' => 2,
|
|
'0'..='9' | '_' => 3,
|
|
'\u{80}'..='\u{10ffff}' => 4,
|
|
'\0' => 5,
|
|
_ => 6,
|
|
}
|
|
}
|
|
#[test] fn char_ranges() {
|
|
for (c, w) in [('a', 1), ('z', 1), ('A', 2), ('_', 3), ('5', 3), ('é', 4), ('\u{10ffff}', 4), ('\0', 5), ('!', 6), ('{', 6)] {
|
|
assert_eq!(chars(c), w, "{c:?}");
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
enum E { A, B(i32), C { x: u8, y: bool }, D(Option<u8>), F }
|
|
|
|
fn en(e: E) -> i64 {
|
|
match e {
|
|
E::A => 1,
|
|
E::B(n) if n < 0 => -1,
|
|
E::B(0) => 2,
|
|
E::B(n) => 100 + n as i64,
|
|
E::C { x: 0, y } => 200 + y as i64,
|
|
E::C { x, y: true } => 300 + x as i64,
|
|
E::C { .. } => 400,
|
|
E::D(None) => 5,
|
|
E::D(Some(v @ 10..=20)) => 500 + v as i64,
|
|
E::D(Some(_)) => 6,
|
|
E::F => 7,
|
|
}
|
|
}
|
|
#[test] fn enum_variants() {
|
|
let cases = [
|
|
(E::A, 1), (E::B(-5), -1), (E::B(0), 2), (E::B(7), 107), (E::C { x: 0, y: false }, 200), (E::C { x: 0, y: true }, 201),
|
|
(E::C { x: 9, y: true }, 309), (E::C { x: 9, y: false }, 400), (E::D(None), 5), (E::D(Some(15)), 515), (E::D(Some(30)), 6), (E::F, 7),
|
|
];
|
|
for (e, w) in cases {
|
|
assert_eq!(en(e), w, "{e:?}");
|
|
}
|
|
}
|
|
|
|
fn pairs(a: Option<i32>, b: Result<u8, char>) -> u32 {
|
|
match (a, b) {
|
|
(None, Ok(0)) => 1,
|
|
(None, Ok(_)) => 2,
|
|
(None, Err('x')) => 3,
|
|
(Some(0), _) => 4,
|
|
(Some(n), Ok(m)) if n as i64 == m as i64 => 5,
|
|
(Some(1..=9), Err(_)) => 6,
|
|
(Some(_), Ok(1 | 2)) => 7,
|
|
(_, Err('a'..='f')) => 8,
|
|
_ => 9,
|
|
}
|
|
}
|
|
#[test] fn tuple_scrutinee() {
|
|
let cases = [
|
|
(None, Ok(0), 1), (None, Ok(9), 2), (None, Err('x'), 3), (Some(0), Ok(3), 4), (Some(0), Err('q'), 4), (Some(4), Ok(4), 5),
|
|
(Some(5), Err('z'), 6), (Some(50), Ok(2), 7), (Some(50), Err('c'), 8), (None, Err('c'), 8), (Some(50), Ok(51), 9), (Some(-5), Err('z'), 9),
|
|
];
|
|
for (a, b, w) in cases {
|
|
assert_eq!(pairs(a, b), w, "{a:?} {b:?}");
|
|
}
|
|
}
|
|
|
|
// guards run in arm order, once per arm, and a failed guard goes on with the arms after it
|
|
fn guards(x: i32) -> u32 {
|
|
match x {
|
|
n if nolog(1) && n > 0 => 1,
|
|
1 | 2 if nolog(2) => 2,
|
|
1 | 3 if log(3) => 3,
|
|
0..=5 if nolog(4) => 4,
|
|
_ if log(5) => 5,
|
|
_ => 6,
|
|
}
|
|
}
|
|
#[test] fn guard_order() {
|
|
take();
|
|
assert_eq!(guards(1), 3); assert_eq!(take(), vec![1, 2, 3]);
|
|
assert_eq!(guards(2), 5); assert_eq!(take(), vec![1, 2, 4, 5]);
|
|
assert_eq!(guards(3), 3); assert_eq!(take(), vec![1, 3]);
|
|
assert_eq!(guards(-7), 5); assert_eq!(take(), vec![1, 5]);
|
|
}
|
|
|
|
// the scrutinee expression runs once
|
|
fn counted(n: &mut u32, x: i32) -> i32 { *n += 1; x }
|
|
fn once(x: i32) -> (u32, u32) {
|
|
let mut n = 0;
|
|
let r = match counted(&mut n, x) { 0 => 10, 1..=4 => 11, 5 | 6 => 12, 7..=100 => 13, _ => 14 };
|
|
(n, r)
|
|
}
|
|
#[test] fn scrutinee_once() {
|
|
for (x, r) in [(0, 10), (3, 11), (6, 12), (50, 13), (-1, 14), (1000, 14)] {
|
|
assert_eq!(once(x), (1, r));
|
|
}
|
|
}
|
|
|
|
// references: match ergonomics peel them, the pointee is read once
|
|
fn refs(o: &Option<(i32, &str)>) -> u32 {
|
|
match o {
|
|
None => 1,
|
|
Some((0, _)) => 2,
|
|
Some((n, "x")) if *n > 5 => 3,
|
|
Some((_, "x")) => 4,
|
|
Some((1..=3, s)) if s.len() == 2 => 5,
|
|
Some(_) => 6,
|
|
}
|
|
}
|
|
#[test] fn through_references() {
|
|
assert_eq!(refs(&None), 1);
|
|
assert_eq!(refs(&Some((0, "q"))), 2);
|
|
assert_eq!(refs(&Some((9, "x"))), 3);
|
|
assert_eq!(refs(&Some((2, "x"))), 4);
|
|
assert_eq!(refs(&Some((2, "ab"))), 5);
|
|
assert_eq!(refs(&Some((2, "abc"))), 6);
|
|
assert_eq!(refs(&Some((7, "ab"))), 6);
|
|
}
|
|
|
|
// bindings: by value, by reference, @ patterns, or-patterns that bind, nested
|
|
fn binds(v: &(Option<u32>, u32)) -> u32 {
|
|
match v {
|
|
(Some(a @ (1 | 2)), b) => a * 10 + b,
|
|
(Some(a), 0) => *a + 1000,
|
|
(Some(a @ 3..=8), b) | (None, a @ (b @ 3..=8)) => *a + *b + 2000,
|
|
(None, c) => *c + 3000,
|
|
(Some(_), b) => *b,
|
|
}
|
|
}
|
|
#[test] fn bindings() {
|
|
assert_eq!(binds(&(Some(1), 5)), 15);
|
|
assert_eq!(binds(&(Some(2), 0)), 20);
|
|
assert_eq!(binds(&(Some(40), 0)), 1040);
|
|
assert_eq!(binds(&(Some(4), 9)), 2013);
|
|
assert_eq!(binds(&(None, 5)), 2010);
|
|
assert_eq!(binds(&(None, 30)), 3030);
|
|
assert_eq!(binds(&(Some(40), 9)), 9);
|
|
}
|
|
|
|
// drops: the moved-out parts and the rest drop where they did
|
|
struct D(u32);
|
|
impl Drop for D { fn drop(&mut self) { log(900 + self.0); } }
|
|
fn drops(e: Result<D, (D, D)>, k: u32) -> u32 {
|
|
match e {
|
|
Ok(d) if k == 0 => d.0,
|
|
Ok(_) if k == 1 => 11,
|
|
Err((a, _)) if k == 2 => a.0,
|
|
Err((_, b)) => b.0 + k,
|
|
Ok(d) => d.0 + 100,
|
|
}
|
|
}
|
|
#[test] fn drop_order() {
|
|
take();
|
|
assert_eq!(drops(Ok(D(1)), 0), 1); assert_eq!(take(), vec![901]);
|
|
assert_eq!(drops(Ok(D(1)), 1), 11); assert_eq!(take(), vec![901]);
|
|
assert_eq!(drops(Ok(D(1)), 5), 101); assert_eq!(take(), vec![901]);
|
|
assert_eq!(drops(Err((D(2), D(3))), 2), 2); assert_eq!(take(), vec![902, 903]);
|
|
assert_eq!(drops(Err((D(2), D(3))), 7), 10); assert_eq!(take(), vec![903, 902]);
|
|
}
|
|
|
|
// bool and nested matches on a state machine (the tokenizer shape)
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
enum St { Start, Ident, Num, Str, Esc, Slash, Line }
|
|
fn step(st: St, c: u8) -> (St, bool) {
|
|
match st {
|
|
St::Start => match c {
|
|
b'a'..=b'z' | b'A'..=b'Z' | b'_' => (St::Ident, false),
|
|
b'0'..=b'9' => (St::Num, false),
|
|
b'"' => (St::Str, false),
|
|
b'/' => (St::Slash, false),
|
|
b' ' | b'\n' => (St::Start, false),
|
|
_ => (St::Start, true),
|
|
},
|
|
St::Ident => match c { b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'0'..=b'9' => (St::Ident, false), _ => (St::Start, true) },
|
|
St::Num => match c { b'0'..=b'9' | b'.' => (St::Num, false), _ => (St::Start, true) },
|
|
St::Str => match c { b'\\' => (St::Esc, false), b'"' => (St::Start, true), _ => (St::Str, false) },
|
|
St::Esc => (St::Str, false),
|
|
St::Slash => match c { b'/' => (St::Line, false), _ => (St::Start, true) },
|
|
St::Line => match c { b'\n' => (St::Start, true), _ => (St::Line, false) },
|
|
}
|
|
}
|
|
#[test] fn state_machine() {
|
|
let src = b"let x_1 = 42.5 + y; // c\n\"s\\\"q\" /";
|
|
let mut st = St::Start;
|
|
let (mut h, mut n) = (0u64, 0u32);
|
|
for &c in src.iter() {
|
|
let (s2, emit) = step(st, c);
|
|
h = h.wrapping_mul(31).wrapping_add(s2 as u64 + 1);
|
|
if emit { n += 1; }
|
|
st = s2;
|
|
}
|
|
assert_eq!((n, st), (8, St::Slash));
|
|
let _ = h;
|
|
}
|
|
|
|
fn flags(a: bool, b: bool, c: Option<bool>) -> u32 {
|
|
match (a, b, c) {
|
|
(true, true, Some(true)) => 1,
|
|
(true, _, None) => 2,
|
|
(false, true, _) => 3,
|
|
(_, false, Some(false)) => 4,
|
|
(true, true, Some(false)) => 5,
|
|
(false, false, _) => 6,
|
|
(true, false, _) => 7,
|
|
}
|
|
}
|
|
#[test] fn booleans() {
|
|
let mut seen = [0u32; 8];
|
|
for a in [false, true] { for b in [false, true] { for c in [None, Some(false), Some(true)] {
|
|
seen[flags(a, b, c) as usize] += 1;
|
|
}}}
|
|
assert_eq!(seen, [0, 1, 2, 3, 2, 1, 2, 1]);
|
|
}
|
|
|
|
// consts, strings and slices in a match with more tree-shaped arms
|
|
const K: u32 = 77;
|
|
fn mixed(x: u32, s: &str, v: &[u8]) -> u32 {
|
|
match (x, s) {
|
|
(K, _) => 1,
|
|
(0, "a") => 2,
|
|
(1..=5, "b" | "c") => 3,
|
|
(_, "zz") => 4,
|
|
(_, _) if v.len() == 3 => 5,
|
|
(9, _) => match v { [] => 6, [1, ..] => 7, [.., 9] => 8, _ => 9 },
|
|
_ => 10,
|
|
}
|
|
}
|
|
#[test] fn consts_strings_slices() {
|
|
assert_eq!(mixed(77, "q", &[]), 1);
|
|
assert_eq!(mixed(0, "a", &[]), 2);
|
|
assert_eq!(mixed(3, "c", &[]), 3);
|
|
assert_eq!(mixed(3, "zz", &[]), 4);
|
|
assert_eq!(mixed(3, "q", &[1, 2, 3]), 5);
|
|
assert_eq!(mixed(9, "q", &[]), 6);
|
|
assert_eq!(mixed(9, "q", &[1, 5]), 7);
|
|
assert_eq!(mixed(9, "q", &[5, 9]), 8);
|
|
assert_eq!(mixed(9, "q", &[5, 8]), 9);
|
|
assert_eq!(mixed(8, "q", &[5, 8]), 10);
|
|
}
|
|
|
|
// constants the back ends cannot encode as 32-bit immediates: ids (hashes with the top bit set),
|
|
// values around the 32-bit limits, negative ones
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
struct Id(u64);
|
|
fn ids(id: Id) -> u32 {
|
|
match id {
|
|
Id(0x8d3c_7a11_9f00_0001) => 1,
|
|
Id(0x0000_133f_1995_ee71) => 2,
|
|
Id(0xffff_ffff) => 3,
|
|
Id(0x1_0000_0000..=0x1_0000_00ff) => 4,
|
|
Id(0x7fff_ffff) | Id(0x8000_0000) => 5,
|
|
Id(u64::MAX) => 6,
|
|
Id(0xffff_ffff_0000_0000..=0xffff_ffff_0000_000f) => 7,
|
|
_ => 8,
|
|
}
|
|
}
|
|
#[test] fn wide_constants() {
|
|
for (v, w) in [
|
|
(0x8d3c_7a11_9f00_0001u64, 1), (0x0000_133f_1995_ee71, 2), (0xffff_ffff, 3), (0x1_0000_0000, 4), (0x1_0000_00ff, 4), (0x1_0000_0100, 8),
|
|
(0x7fff_ffff, 5), (0x8000_0000, 5), (0x8000_0001, 8), (u64::MAX, 6), (u64::MAX - 1, 8), (0xffff_ffff_0000_0000, 7), (0xffff_ffff_0000_000f, 7),
|
|
(0xffff_ffff_0000_0010, 8), (0, 8), (0xfffe_ffff, 8),
|
|
] {
|
|
assert_eq!(ids(Id(v)), w, "{v:#x}");
|
|
}
|
|
}
|
|
fn signed_wide(x: i64) -> u32 {
|
|
match x {
|
|
i64::MIN => 1,
|
|
-0x8000_0001 => 2,
|
|
-0x8000_0000..=-1 => 3,
|
|
0 => 4,
|
|
1..=0x7fff_ffff => 5,
|
|
0x8000_0000 | 0x1_0000_0000 => 6,
|
|
0x8000_0001..=i64::MAX => 7,
|
|
_ => 8,
|
|
}
|
|
}
|
|
#[test] fn wide_signed_constants() {
|
|
for (v, w) in [
|
|
(i64::MIN, 1), (-0x8000_0001, 2), (-0x8000_0000, 3), (-1, 3), (0, 4), (1, 5), (0x7fff_ffff, 5), (0x8000_0000, 6), (0x1_0000_0000, 6),
|
|
(0x8000_0001, 7), (i64::MAX, 7), (i64::MIN + 1, 8), (-0x8000_0002, 8),
|
|
] {
|
|
assert_eq!(signed_wide(v), w, "{v:#x}");
|
|
}
|
|
}
|