Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4326a44

Browse files
committedApr 23, 2025·
transmutability: Mark edges by ranges, not values
In the `Tree` and `Dfa` representations of a type's layout, store byte ranges rather than needing to separately store each byte value. This permits us to, for example, represent a `u8` using a single 0..=255 edge in the DFA rather than using 256 separate edges. This leads to drastic performance improvements. For example, on the author's 2024 MacBook Pro, the time to convert the `Tree` representation of a `u64` to its equivalent DFA representation drops from ~8.5ms to ~1us, a reduction of ~8,500x. See `bench_dfa_from_tree`. Similarly, the time to execute a transmutability query from `u64` to `u64` drops from ~35us to ~1.7us, a reduction of ~20x. See `bench_transmute`.
1 parent be181dd commit 4326a44

File tree

9 files changed

+778
-161
lines changed

9 files changed

+778
-161
lines changed
 

‎Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4562,6 +4562,7 @@ dependencies = [
45624562
"rustc_hir",
45634563
"rustc_middle",
45644564
"rustc_span",
4565+
"smallvec",
45654566
"tracing",
45664567
]
45674568

‎compiler/rustc_transmute/Cargo.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ edition = "2024"
55

66
[dependencies]
77
# tidy-alphabetical-start
8+
itertools = "0.12"
89
rustc_abi = { path = "../rustc_abi", optional = true }
910
rustc_data_structures = { path = "../rustc_data_structures" }
1011
rustc_hir = { path = "../rustc_hir", optional = true }
1112
rustc_middle = { path = "../rustc_middle", optional = true }
1213
rustc_span = { path = "../rustc_span", optional = true }
14+
smallvec = "1.8.1"
1315
tracing = "0.1"
1416
# tidy-alphabetical-end
1517

@@ -20,8 +22,3 @@ rustc = [
2022
"dep:rustc_middle",
2123
"dep:rustc_span",
2224
]
23-
24-
[dev-dependencies]
25-
# tidy-alphabetical-start
26-
itertools = "0.12"
27-
# tidy-alphabetical-end

‎compiler/rustc_transmute/src/layout/dfa.rs

Lines changed: 363 additions & 70 deletions
Large diffs are not rendered by default.

‎compiler/rustc_transmute/src/layout/mod.rs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt::{self, Debug};
22
use std::hash::Hash;
3+
use std::ops::RangeInclusive;
34

45
pub(crate) mod tree;
56
pub(crate) use tree::Tree;
@@ -10,26 +11,64 @@ pub(crate) use dfa::Dfa;
1011
#[derive(Debug)]
1112
pub(crate) struct Uninhabited;
1213

13-
/// An instance of a byte is either initialized to a particular value, or uninitialized.
14-
#[derive(Hash, Eq, PartialEq, Clone, Copy)]
15-
pub(crate) enum Byte {
16-
Uninit,
17-
Init(u8),
14+
/// A range of byte values, or the uninit byte.
15+
#[derive(Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
16+
pub(crate) struct Byte {
17+
// An inclusive-inclusive range. We use this instead of `RangeInclusive`
18+
// because `RangeInclusive: !Copy`.
19+
//
20+
// `None` means uninit.
21+
//
22+
// FIXME(@joshlf): Optimize this representation. Some pairs of values (where
23+
// `lo > hi`) are illegal, and we could use these to represent `None`.
24+
range: Option<(u8, u8)>,
25+
}
26+
27+
impl Byte {
28+
fn new(range: RangeInclusive<u8>) -> Self {
29+
Self { range: Some((*range.start(), *range.end())) }
30+
}
31+
32+
fn from_val(val: u8) -> Self {
33+
Self { range: Some((val, val)) }
34+
}
35+
36+
pub(crate) fn uninit() -> Byte {
37+
Byte { range: None }
38+
}
39+
40+
/// Returns `None` if `self` is the uninit byte.
41+
pub(crate) fn range(&self) -> Option<RangeInclusive<u8>> {
42+
self.range.map(|(lo, hi)| lo..=hi)
43+
}
44+
45+
/// Are any of the values in `self` transmutable into `other`?
46+
///
47+
/// Note two special cases: An uninit byte is only transmutable into another
48+
/// uninit byte. Any byte is transmutable into an uninit byte.
49+
pub(crate) fn transmutable_into(&self, other: &Byte) -> bool {
50+
match (self.range, other.range) {
51+
(None, None) => true,
52+
(None, Some(_)) => false,
53+
(Some(_), None) => true,
54+
(Some((slo, shi)), Some((olo, ohi))) => slo <= ohi && olo <= shi,
55+
}
56+
}
1857
}
1958

2059
impl fmt::Debug for Byte {
2160
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22-
match &self {
23-
Self::Uninit => f.write_str("??u8"),
24-
Self::Init(b) => write!(f, "{b:#04x}u8"),
61+
match self.range {
62+
None => write!(f, "uninit"),
63+
Some((lo, hi)) => write!(f, "{lo}..={hi}"),
2564
}
2665
}
2766
}
2867

2968
#[cfg(test)]
3069
impl From<u8> for Byte {
3170
fn from(src: u8) -> Self {
32-
Self::Init(src)
71+
Self::from_val(src)
3372
}
3473
}
3574

@@ -62,6 +101,21 @@ impl Ref for ! {
62101
}
63102
}
64103

104+
#[cfg(test)]
105+
impl<const N: usize> Ref for [(); N] {
106+
fn min_align(&self) -> usize {
107+
N
108+
}
109+
110+
fn size(&self) -> usize {
111+
N
112+
}
113+
114+
fn is_mutable(&self) -> bool {
115+
false
116+
}
117+
}
118+
65119
#[cfg(feature = "rustc")]
66120
pub mod rustc {
67121
use std::fmt::{self, Write};

‎compiler/rustc_transmute/src/layout/tree.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,22 @@ where
5454

5555
/// A `Tree` containing a single, uninitialized byte.
5656
pub(crate) fn uninit() -> Self {
57-
Self::Byte(Byte::Uninit)
57+
Self::Byte(Byte::uninit())
5858
}
5959

6060
/// A `Tree` representing the layout of `bool`.
6161
pub(crate) fn bool() -> Self {
62-
Self::from_bits(0x00).or(Self::from_bits(0x01))
62+
Self::Byte(Byte::new(0x00..=0x01))
6363
}
6464

6565
/// A `Tree` whose layout matches that of a `u8`.
6666
pub(crate) fn u8() -> Self {
67-
Self::Alt((0u8..=255).map(Self::from_bits).collect())
67+
Self::Byte(Byte::new(0x00..=0xFF))
6868
}
6969

7070
/// A `Tree` whose layout accepts exactly the given bit pattern.
7171
pub(crate) fn from_bits(bits: u8) -> Self {
72-
Self::Byte(Byte::Init(bits))
72+
Self::Byte(Byte::from_val(bits))
7373
}
7474

7575
/// A `Tree` whose layout is a number of the given width.

‎compiler/rustc_transmute/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// tidy-alphabetical-start
2+
#![cfg_attr(test, feature(test))]
23
#![feature(never_type)]
34
// tidy-alphabetical-end
45

5-
pub(crate) use rustc_data_structures::fx::FxIndexMap as Map;
6+
pub(crate) use rustc_data_structures::fx::{FxIndexMap as Map, FxIndexSet as Set};
67

78
pub mod layout;
89
mod maybe_transmutable;

‎compiler/rustc_transmute/src/maybe_transmutable/mod.rs

Lines changed: 153 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
use std::rc::Rc;
2+
use std::{cmp, iter};
3+
4+
use itertools::Either;
15
use tracing::{debug, instrument, trace};
26

37
pub(crate) mod query_context;
48
#[cfg(test)]
59
mod tests;
610

7-
use crate::layout::{self, Byte, Def, Dfa, Ref, Tree, Uninhabited, dfa};
11+
use crate::layout::{self, Byte, Def, Dfa, Ref, Tree, dfa};
812
use crate::maybe_transmutable::query_context::QueryContext;
913
use crate::{Answer, Condition, Map, Reason};
1014

@@ -111,7 +115,7 @@ where
111115
// the `src` type do not exist.
112116
let src = match Dfa::from_tree(src) {
113117
Ok(src) => src,
114-
Err(Uninhabited) => return Answer::Yes,
118+
Err(layout::Uninhabited) => return Answer::Yes,
115119
};
116120

117121
// Convert `dst` from a tree-based representation to an DFA-based
@@ -122,7 +126,7 @@ where
122126
// free of safety invariants.
123127
let dst = match Dfa::from_tree(dst) {
124128
Ok(dst) => dst,
125-
Err(Uninhabited) => return Answer::No(Reason::DstMayHaveSafetyInvariants),
129+
Err(layout::Uninhabited) => return Answer::No(Reason::DstMayHaveSafetyInvariants),
126130
};
127131

128132
MaybeTransmutableQuery { src, dst, assume, context }.answer()
@@ -174,8 +178,8 @@ where
174178
// are able to safely transmute, even with truncation.
175179
Answer::Yes
176180
} else if src_state == self.src.accept {
177-
// extension: `size_of(Src) >= size_of(Dst)`
178-
if let Some(dst_state_prime) = self.dst.byte_from(dst_state, Byte::Uninit) {
181+
// extension: `size_of(Src) <= size_of(Dst)`
182+
if let Some(dst_state_prime) = self.dst.get_uninit_edge_dst(dst_state) {
179183
self.answer_memo(cache, src_state, dst_state_prime)
180184
} else {
181185
Answer::No(Reason::DstIsTooBig)
@@ -193,26 +197,120 @@ where
193197
Quantifier::ForAll
194198
};
195199

200+
let c = &core::cell::RefCell::new(&mut *cache);
196201
let bytes_answer = src_quantifier.apply(
197-
// for each of the byte transitions out of the `src_state`...
198-
self.src.bytes_from(src_state).unwrap_or(&Map::default()).into_iter().map(
199-
|(&src_validity, &src_state_prime)| {
200-
// ...try to find a matching transition out of `dst_state`.
201-
if let Some(dst_state_prime) =
202-
self.dst.byte_from(dst_state, src_validity)
203-
{
204-
self.answer_memo(cache, src_state_prime, dst_state_prime)
205-
} else if let Some(dst_state_prime) =
206-
// otherwise, see if `dst_state` has any outgoing `Uninit` transitions
207-
// (any init byte is a valid uninit byte)
208-
self.dst.byte_from(dst_state, Byte::Uninit)
209-
{
210-
self.answer_memo(cache, src_state_prime, dst_state_prime)
211-
} else {
212-
// otherwise, we've exhausted our options.
213-
// the DFAs, from this point onwards, are bit-incompatible.
214-
Answer::No(Reason::DstIsBitIncompatible)
202+
// for each of the byte set transitions out of the `src_state`...
203+
self.src.bytes_from(src_state).flat_map(
204+
move |(src_validity, src_state_prime)| {
205+
// ...find all matching transitions out of `dst_state`.
206+
207+
let Some(src_validity) = src_validity.range() else {
208+
// NOTE: We construct an iterator here rather
209+
// than just computing the value directly (via
210+
// `self.answer_memo`) so that, if the iterator
211+
// we produce from this branch is
212+
// short-circuited, we don't waste time
213+
// computing `self.answer_memo` unnecessarily.
214+
// That will specifically happen if
215+
// `src_quantifier == Quantifier::ThereExists`,
216+
// since we emit `Answer::Yes` first (before
217+
// chaining `answer_iter`).
218+
let answer_iter = if let Some(dst_state_prime) =
219+
self.dst.get_uninit_edge_dst(dst_state)
220+
{
221+
Either::Left(iter::once_with(move || {
222+
let mut c = c.borrow_mut();
223+
self.answer_memo(&mut *c, src_state_prime, dst_state_prime)
224+
}))
225+
} else {
226+
Either::Right(iter::once(Answer::No(
227+
Reason::DstIsBitIncompatible,
228+
)))
229+
};
230+
231+
// When `answer == Answer::No(...)`, there are
232+
// two cases to consider:
233+
// - If `assume.validity`, then we should
234+
// succeed because the user is responsible for
235+
// ensuring that the *specific* byte value
236+
// appearing at runtime is valid for the
237+
// destination type. When `assume.validity`,
238+
// `src_quantifier ==
239+
// Quantifier::ThereExists`, so adding an
240+
// `Answer::Yes` has the effect of ensuring
241+
// that the "there exists" is always
242+
// satisfied.
243+
// - If `!assume.validity`, then we should fail.
244+
// In this case, `src_quantifier ==
245+
// Quantifier::ForAll`, so adding an
246+
// `Answer::Yes` has no effect.
247+
return Either::Left(iter::once(Answer::Yes).chain(answer_iter));
248+
};
249+
250+
#[derive(Copy, Clone, Debug)]
251+
struct Accum {
252+
// The number of matching byte edges that we
253+
// have found in the destination so far.
254+
sum: usize,
255+
found_uninit: bool,
215256
}
257+
258+
let accum1 = Rc::new(std::cell::Cell::new(Accum {
259+
sum: 0,
260+
found_uninit: false,
261+
}));
262+
let accum2 = Rc::clone(&accum1);
263+
let sv = src_validity.clone();
264+
let update_accum = move |mut accum: Accum, dst_validity: Byte| {
265+
if let Some(dst_validity) = dst_validity.range() {
266+
// Only add the part of `dst_validity` that
267+
// overlaps with `src_validity`.
268+
let start = cmp::max(*sv.start(), *dst_validity.start());
269+
let end = cmp::min(*sv.end(), *dst_validity.end());
270+
271+
// We add 1 here to account for the fact
272+
// that `end` is an inclusive bound.
273+
accum.sum += 1 + usize::from(end.saturating_sub(start));
274+
} else {
275+
accum.found_uninit = true;
276+
}
277+
accum
278+
};
279+
280+
let answers = self
281+
.dst
282+
.states_from(dst_state, src_validity.clone())
283+
.map(move |(dst_validity, dst_state_prime)| {
284+
let mut c = c.borrow_mut();
285+
accum1.set(update_accum(accum1.get(), dst_validity));
286+
let answer =
287+
self.answer_memo(&mut *c, src_state_prime, dst_state_prime);
288+
answer
289+
})
290+
.chain(
291+
iter::once_with(move || {
292+
let src_validity_len = usize::from(*src_validity.end())
293+
- usize::from(*src_validity.start())
294+
+ 1;
295+
let accum = accum2.get();
296+
297+
// If this condition is false, then
298+
// there are some byte values in the
299+
// source which have no corresponding
300+
// transition in the destination DFA. In
301+
// that case, we add a `No` to our list
302+
// of answers. When
303+
// `!self.assume.validity`, this will
304+
// cause the query to fail.
305+
if accum.found_uninit || accum.sum == src_validity_len {
306+
None
307+
} else {
308+
Some(Answer::No(Reason::DstIsBitIncompatible))
309+
}
310+
})
311+
.flatten(),
312+
);
313+
Either::Right(answers)
216314
},
217315
),
218316
);
@@ -235,48 +333,38 @@ where
235333

236334
let refs_answer = src_quantifier.apply(
237335
// for each reference transition out of `src_state`...
238-
self.src.refs_from(src_state).unwrap_or(&Map::default()).into_iter().map(
239-
|(&src_ref, &src_state_prime)| {
240-
// ...there exists a reference transition out of `dst_state`...
241-
Quantifier::ThereExists.apply(
242-
self.dst
243-
.refs_from(dst_state)
244-
.unwrap_or(&Map::default())
245-
.into_iter()
246-
.map(|(&dst_ref, &dst_state_prime)| {
247-
if !src_ref.is_mutable() && dst_ref.is_mutable() {
248-
Answer::No(Reason::DstIsMoreUnique)
249-
} else if !self.assume.alignment
250-
&& src_ref.min_align() < dst_ref.min_align()
251-
{
252-
Answer::No(Reason::DstHasStricterAlignment {
253-
src_min_align: src_ref.min_align(),
254-
dst_min_align: dst_ref.min_align(),
255-
})
256-
} else if dst_ref.size() > src_ref.size() {
257-
Answer::No(Reason::DstRefIsTooBig {
258-
src: src_ref,
259-
dst: dst_ref,
260-
})
261-
} else {
262-
// ...such that `src` is transmutable into `dst`, if
263-
// `src_ref` is transmutability into `dst_ref`.
264-
and(
265-
Answer::If(Condition::IfTransmutable {
266-
src: src_ref,
267-
dst: dst_ref,
268-
}),
269-
self.answer_memo(
270-
cache,
271-
src_state_prime,
272-
dst_state_prime,
273-
),
274-
)
275-
}
276-
}),
277-
)
278-
},
279-
),
336+
self.src.refs_from(src_state).map(|(src_ref, src_state_prime)| {
337+
// ...there exists a reference transition out of `dst_state`...
338+
Quantifier::ThereExists.apply(self.dst.refs_from(dst_state).map(
339+
|(dst_ref, dst_state_prime)| {
340+
if !src_ref.is_mutable() && dst_ref.is_mutable() {
341+
Answer::No(Reason::DstIsMoreUnique)
342+
} else if !self.assume.alignment
343+
&& src_ref.min_align() < dst_ref.min_align()
344+
{
345+
Answer::No(Reason::DstHasStricterAlignment {
346+
src_min_align: src_ref.min_align(),
347+
dst_min_align: dst_ref.min_align(),
348+
})
349+
} else if dst_ref.size() > src_ref.size() {
350+
Answer::No(Reason::DstRefIsTooBig {
351+
src: src_ref,
352+
dst: dst_ref,
353+
})
354+
} else {
355+
// ...such that `src` is transmutable into `dst`, if
356+
// `src_ref` is transmutability into `dst_ref`.
357+
and(
358+
Answer::If(Condition::IfTransmutable {
359+
src: src_ref,
360+
dst: dst_ref,
361+
}),
362+
self.answer_memo(cache, src_state_prime, dst_state_prime),
363+
)
364+
}
365+
},
366+
))
367+
}),
280368
);
281369

282370
if self.assume.validity {

‎compiler/rustc_transmute/src/maybe_transmutable/query_context.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@ pub(crate) trait QueryContext {
88

99
#[cfg(test)]
1010
pub(crate) mod test {
11+
use std::marker::PhantomData;
12+
1113
use super::QueryContext;
1214

13-
pub(crate) struct UltraMinimal;
15+
pub(crate) struct UltraMinimal<R = !>(PhantomData<R>);
16+
17+
impl<R> Default for UltraMinimal<R> {
18+
fn default() -> Self {
19+
Self(PhantomData)
20+
}
21+
}
1422

1523
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
1624
pub(crate) enum Def {
@@ -24,9 +32,9 @@ pub(crate) mod test {
2432
}
2533
}
2634

27-
impl QueryContext for UltraMinimal {
35+
impl<R: crate::layout::Ref> QueryContext for UltraMinimal<R> {
2836
type Def = Def;
29-
type Ref = !;
37+
type Ref = R;
3038
}
3139
}
3240

‎compiler/rustc_transmute/src/maybe_transmutable/tests.rs

Lines changed: 179 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
extern crate test;
2+
13
use itertools::Itertools;
24

35
use super::query_context::test::{Def, UltraMinimal};
@@ -12,15 +14,25 @@ trait Representation {
1214

1315
impl Representation for Tree {
1416
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!> {
15-
crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, UltraMinimal)
16-
.answer()
17+
crate::maybe_transmutable::MaybeTransmutableQuery::new(
18+
src,
19+
dst,
20+
assume,
21+
UltraMinimal::default(),
22+
)
23+
.answer()
1724
}
1825
}
1926

2027
impl Representation for Dfa {
2128
fn is_transmutable(src: Self, dst: Self, assume: Assume) -> Answer<!> {
22-
crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, UltraMinimal)
23-
.answer()
29+
crate::maybe_transmutable::MaybeTransmutableQuery::new(
30+
src,
31+
dst,
32+
assume,
33+
UltraMinimal::default(),
34+
)
35+
.answer()
2436
}
2537
}
2638

@@ -89,6 +101,36 @@ mod safety {
89101
}
90102
}
91103

104+
mod size {
105+
use super::*;
106+
107+
#[test]
108+
fn size() {
109+
let small = Tree::number(1);
110+
let large = Tree::number(2);
111+
112+
for alignment in [false, true] {
113+
for lifetimes in [false, true] {
114+
for safety in [false, true] {
115+
for validity in [false, true] {
116+
let assume = Assume { alignment, lifetimes, safety, validity };
117+
assert_eq!(
118+
is_transmutable(&small, &large, assume),
119+
Answer::No(Reason::DstIsTooBig),
120+
"assume: {assume:?}"
121+
);
122+
assert_eq!(
123+
is_transmutable(&large, &small, assume),
124+
Answer::Yes,
125+
"assume: {assume:?}"
126+
);
127+
}
128+
}
129+
}
130+
}
131+
}
132+
}
133+
92134
mod bool {
93135
use super::*;
94136

@@ -112,6 +154,27 @@ mod bool {
112154
);
113155
}
114156

157+
#[test]
158+
fn transmute_u8() {
159+
let bool = &Tree::bool();
160+
let u8 = &Tree::u8();
161+
for (src, dst, assume_validity, answer) in [
162+
(bool, u8, false, Answer::Yes),
163+
(bool, u8, true, Answer::Yes),
164+
(u8, bool, false, Answer::No(Reason::DstIsBitIncompatible)),
165+
(u8, bool, true, Answer::Yes),
166+
] {
167+
assert_eq!(
168+
is_transmutable(
169+
src,
170+
dst,
171+
Assume { validity: assume_validity, ..Assume::default() }
172+
),
173+
answer
174+
);
175+
}
176+
}
177+
115178
#[test]
116179
fn should_permit_validity_expansion_and_reject_contraction() {
117180
let b0 = layout::Tree::<Def, !>::from_bits(0);
@@ -175,6 +238,62 @@ mod bool {
175238
}
176239
}
177240

241+
mod uninit {
242+
use super::*;
243+
244+
#[test]
245+
fn size() {
246+
let mu = Tree::uninit();
247+
let u8 = Tree::u8();
248+
249+
for alignment in [false, true] {
250+
for lifetimes in [false, true] {
251+
for safety in [false, true] {
252+
for validity in [false, true] {
253+
let assume = Assume { alignment, lifetimes, safety, validity };
254+
255+
let want = if validity {
256+
Answer::Yes
257+
} else {
258+
Answer::No(Reason::DstIsBitIncompatible)
259+
};
260+
261+
assert_eq!(is_transmutable(&mu, &u8, assume), want, "assume: {assume:?}");
262+
assert_eq!(
263+
is_transmutable(&u8, &mu, assume),
264+
Answer::Yes,
265+
"assume: {assume:?}"
266+
);
267+
}
268+
}
269+
}
270+
}
271+
}
272+
}
273+
274+
mod alt {
275+
use super::*;
276+
use crate::Answer;
277+
278+
#[test]
279+
fn should_permit_identity_transmutation() {
280+
type Tree = layout::Tree<Def, !>;
281+
282+
let x = Tree::Seq(vec![Tree::from_bits(0), Tree::from_bits(0)]);
283+
let y = Tree::Seq(vec![Tree::bool(), Tree::from_bits(1)]);
284+
let layout = Tree::Alt(vec![x, y]);
285+
286+
let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new(
287+
layout.clone(),
288+
layout.clone(),
289+
crate::Assume::default(),
290+
UltraMinimal::default(),
291+
)
292+
.answer();
293+
assert_eq!(answer, Answer::Yes, "layout:{:#?}", layout);
294+
}
295+
}
296+
178297
mod union {
179298
use super::*;
180299

@@ -203,3 +322,59 @@ mod union {
203322
assert_eq!(is_transmutable(&t, &u, Assume::default()), Answer::Yes);
204323
}
205324
}
325+
326+
mod r#ref {
327+
use super::*;
328+
329+
#[test]
330+
fn should_permit_identity_transmutation() {
331+
type Tree = crate::layout::Tree<Def, [(); 1]>;
332+
333+
let layout = Tree::Seq(vec![Tree::from_bits(0), Tree::Ref([()])]);
334+
335+
let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new(
336+
layout.clone(),
337+
layout,
338+
Assume::default(),
339+
UltraMinimal::default(),
340+
)
341+
.answer();
342+
assert_eq!(answer, Answer::If(crate::Condition::IfTransmutable { src: [()], dst: [()] }));
343+
}
344+
}
345+
346+
mod benches {
347+
use std::hint::black_box;
348+
349+
use test::Bencher;
350+
351+
use super::*;
352+
353+
#[bench]
354+
fn bench_dfa_from_tree(b: &mut Bencher) {
355+
let num = Tree::number(8).prune(&|_| false);
356+
let num = black_box(num);
357+
358+
b.iter(|| {
359+
let _ = black_box(Dfa::from_tree(num.clone()));
360+
})
361+
}
362+
363+
#[bench]
364+
fn bench_transmute(b: &mut Bencher) {
365+
let num = Tree::number(8).prune(&|_| false);
366+
let dfa = black_box(Dfa::from_tree(num).unwrap());
367+
368+
b.iter(|| {
369+
let answer = crate::maybe_transmutable::MaybeTransmutableQuery::new(
370+
dfa.clone(),
371+
dfa.clone(),
372+
Assume::default(),
373+
UltraMinimal::default(),
374+
)
375+
.answer();
376+
let answer = std::hint::black_box(answer);
377+
assert_eq!(answer, Answer::Yes);
378+
})
379+
}
380+
}

0 commit comments

Comments
 (0)
Please sign in to comment.