WIP adding register allocator
All checks were successful
/ test (push) Successful in 46m24s

This commit is contained in:
Jacob Lifshay 2024-10-14 21:19:45 -07:00
parent d0aa86e335
commit cb5855589f
Signed by: programmerjake
SSH key fingerprint: SHA256:B1iRVvUJkvd7upMIiMqn6OyxvD2SgJkAH3ZnUOj6z+c
9 changed files with 570 additions and 100 deletions

8
Cargo.lock generated
View file

@ -279,7 +279,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]] [[package]]
name = "fayalite" name = "fayalite"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"blake3", "blake3",
@ -300,7 +300,7 @@ dependencies = [
[[package]] [[package]]
name = "fayalite-proc-macros" name = "fayalite-proc-macros"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571"
dependencies = [ dependencies = [
"fayalite-proc-macros-impl", "fayalite-proc-macros-impl",
] ]
@ -308,7 +308,7 @@ dependencies = [
[[package]] [[package]]
name = "fayalite-proc-macros-impl" name = "fayalite-proc-macros-impl"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"num-bigint", "num-bigint",
@ -323,7 +323,7 @@ dependencies = [
[[package]] [[package]]
name = "fayalite-visit-gen" name = "fayalite-visit-gen"
version = "0.2.0" version = "0.2.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"prettyplease", "prettyplease",

View file

@ -1,18 +1,21 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::instruction::{PRegNum, UnitKind, UnitNum}; use crate::{
instruction::{PRegNum, UnitNum, CONST_ZERO_UNIT_NUM},
unit::UnitKind,
};
use fayalite::prelude::*; use fayalite::prelude::*;
#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[non_exhaustive] #[non_exhaustive]
pub struct CpuConfig { pub struct CpuConfig {
pub units: Vec<UnitKind>, pub unit_kinds: Vec<UnitKind>,
pub out_reg_num_width: usize, pub out_reg_num_width: usize,
} }
impl CpuConfig { impl CpuConfig {
pub fn unit_num_width(&self) -> usize { pub fn unit_num_width(&self) -> usize {
UInt::range(0..self.units.len()).width() UInt::range((CONST_ZERO_UNIT_NUM + 1)..self.unit_kinds.len()).width()
} }
pub fn unit_num(&self) -> UnitNum<DynSize> { pub fn unit_num(&self) -> UnitNum<DynSize> {
UnitNum[self.unit_num_width()] UnitNum[self.unit_num_width()]

View file

@ -1,102 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::config::CpuConfig; use crate::unit::UnitMOp;
use fayalite::prelude::*; use fayalite::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
pub mod power_isa; pub mod power_isa;
macro_rules! all_units {
(
#[hdl_unit_kind = $HdlUnitKind:ident]
#[unit_kind = $UnitKind:ident]
#[hdl]
$(#[$enum_meta:meta])*
$vis:vis enum $UnitMOpEnum:ident<$RegWidth:ident: Size> {
$(
$(#[$variant_meta:meta])*
$Unit:ident($Op:ty),
)*
}
) => {
$(#[$enum_meta])*
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
$vis enum $UnitKind {
$(
$(#[$variant_meta])*
$Unit,
)*
}
impl ToExpr for $UnitKind {
type Type = $HdlUnitKind;
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
}
}
}
#[hdl]
$(#[$enum_meta])*
$vis enum $HdlUnitKind {
$(
$(#[$variant_meta])*
$Unit,
)*
}
#[hdl]
$(#[$enum_meta])*
$vis enum $UnitMOpEnum<$RegWidth: Size> {
$(
$(#[$variant_meta])*
$Unit($Op),
)*
}
impl<$RegWidth: Size> $UnitMOpEnum<$RegWidth> {
#[hdl]
$vis fn kind(expr: impl ToExpr<Type = Self>) -> Expr<$HdlUnitKind> {
#[hdl]
let unit_kind = wire();
#[hdl]
match expr {
$($UnitMOpEnum::<$RegWidth>::$Unit(_) => connect(unit_kind, $HdlUnitKind.$Unit()),)*
}
unit_kind
}
}
impl CpuConfig {
#[hdl]
pub fn available_units_for_kind(&self, unit_kind: impl ToExpr<Type = $HdlUnitKind>) -> Expr<Array<Bool>> {
#[hdl]
let available_units_for_kind = wire(Array[Bool][self.units.len()]);
#[hdl]
match unit_kind {
$($HdlUnitKind::$Unit => for (index, &unit) in self.units.iter().enumerate() {
connect(available_units_for_kind[index], unit == $UnitKind::$Unit);
})*
}
available_units_for_kind
}
}
};
}
all_units! {
#[hdl_unit_kind = HdlUnitKind]
#[unit_kind = UnitKind]
#[hdl]
pub enum UnitMOp<RegWidth: Size> {
AluBranch(AluBranchMOp<RegWidth>),
L2RegisterFile(L2RegisterFileMOp<RegWidth>),
LoadStore(LoadStoreMOp<RegWidth>),
}
}
#[hdl] #[hdl]
pub enum OutputIntegerMode { pub enum OutputIntegerMode {
Full64, Full64,
@ -239,11 +148,14 @@ pub enum LoadStoreMOp<RegWidth: Size> {
} }
#[hdl] #[hdl]
/// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind /// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind.
/// zero is used for built-in constants, such as the zero register
pub struct UnitNum<Width: Size> { pub struct UnitNum<Width: Size> {
pub value: UIntType<Width>, pub value: UIntType<Width>,
} }
pub const CONST_ZERO_UNIT_NUM: usize = 0;
#[hdl] #[hdl]
pub struct UnitOutRegNum<Width: Size> { pub struct UnitOutRegNum<Width: Size> {
pub value: UIntType<Width>, pub value: UIntType<Width>,

View file

@ -2,5 +2,7 @@
// See Notices.txt for copyright information // See Notices.txt for copyright information
pub mod config; pub mod config;
pub mod instruction; pub mod instruction;
pub mod reg_alloc;
pub mod register; pub mod register;
pub mod unit;
pub mod util; pub mod util;

View file

@ -0,0 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::config::CpuConfig;
use fayalite::prelude::*;
pub mod unit_free_regs_tracker;
#[hdl_module]
pub fn reg_alloc(config: CpuConfig) {
todo!()
}

View file

@ -0,0 +1,110 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::util::tree_reduce::tree_reduce;
use fayalite::{prelude::*, util::ready_valid::ReadyValid};
use std::{num::NonZeroUsize, ops::Range};
#[hdl_module]
pub fn unit_free_regs_tracker(
free_at_once: NonZeroUsize,
alloc_at_once: NonZeroUsize,
reg_num_width: usize,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let free_in: Array<ReadyValid<UInt>> =
m.input(Array[ReadyValid[UInt[reg_num_width]]][free_at_once.get()]);
#[hdl]
let alloc_out: Array<ReadyValid<UInt>> =
m.input(Array[ReadyValid[UInt[reg_num_width]]][alloc_at_once.get()]);
assert!(
reg_num_width <= isize::MAX.ilog2() as usize,
"too many registers"
);
let reg_count = 1usize << reg_num_width;
#[hdl]
let allocated_reg = reg_builder().clock_domain(cd).reset(vec![false; reg_count]);
for free_index in 0..free_at_once.get() {
connect(free_in[free_index].ready, true);
#[hdl]
if let HdlSome(free_num) = ReadyValid::firing_data(free_in[free_index]) {
connect(allocated_reg[free_num], false);
}
}
struct Summary {
range: Range<usize>,
count: Expr<UInt>,
count_overflowed: Expr<Bool>,
alloc_nums: Expr<Array<UInt>>,
}
let count_ty = UInt::range_inclusive(0..=alloc_at_once.get());
let Some(Summary {
range: _,
count,
count_overflowed,
alloc_nums,
}) = tree_reduce(
(0..reg_count).map(|index| Summary {
range: index..index + 1,
count: allocated_reg[index]
.cast_to_static::<UInt<1>>()
.cast_to(count_ty),
count_overflowed: false.to_expr(),
alloc_nums: repeat(UInt[0].zero(), alloc_at_once.get()),
}),
|l, r| {
let range = l.range.start..r.range.end;
#[hdl]
let reduced_count = wire(count_ty);
connect_any(reduced_count, l.count + r.count);
#[hdl]
let reduced_count_overflowed = wire();
connect(
reduced_count_overflowed,
(l.count + r.count).cmp_ne(reduced_count) | l.count_overflowed | r.count_overflowed,
);
#[hdl]
let reduced_alloc_nums = wire(
Array[UInt[Expr::ty(l.alloc_nums).element().width() + 1]][alloc_at_once.get()],
);
for alloc_index in 0..alloc_at_once.get() {
#[hdl]
if l.count_overflowed | l.count.cmp_ge(alloc_at_once) {
connect(reduced_alloc_nums[alloc_index], l.alloc_nums[alloc_index]);
} else {
connect(reduced_alloc_nums[alloc_index], r.alloc_nums[alloc_index]);
}
}
Summary {
range,
count: reduced_count,
count_overflowed: reduced_count_overflowed,
alloc_nums: reduced_alloc_nums,
}
},
)
else {
unreachable!("reg_count is known to be non-zero");
};
for alloc_index in 0..alloc_at_once.get() {
#[hdl]
if let HdlSome(alloc_num) = ReadyValid::firing_data(alloc_out[alloc_index]) {
connect(allocated_reg[alloc_num], true);
}
#[hdl]
if count_overflowed | count.cmp_ge(alloc_index) {
connect(
alloc_out[alloc_index].data,
HdlSome(alloc_nums[alloc_index]),
);
} else {
connect(
alloc_out[alloc_index].data,
HdlOption[UInt[reg_num_width]].HdlNone(),
);
}
}
}
// TODO: add formal proof of unit_free_regs_tracker

277
crates/cpu/src/unit.rs Normal file
View file

@ -0,0 +1,277 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::CpuConfig,
instruction::{AluBranchMOp, L2RegisterFileMOp, LoadStoreMOp, PRegNum},
register::PRegValue,
};
use fayalite::{
bundle::{Bundle, BundleType},
intern::{Intern, Interned},
prelude::*,
util::ready_valid::ReadyValid,
};
macro_rules! all_units {
(
#[hdl_unit_kind = $HdlUnitKind:ident]
#[unit_kind = $UnitKind:ident]
#[hdl]
$(#[$enum_meta:meta])*
$vis:vis enum $UnitMOpEnum:ident<$RegWidth:ident: Size> {
$(
$(#[$variant_meta:meta])*
$Unit:ident($Op:ty),
)*
}
) => {
$(#[$enum_meta])*
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
$vis enum $UnitKind {
$(
$(#[$variant_meta])*
$Unit,
)*
}
impl ToExpr for $UnitKind {
type Type = $HdlUnitKind;
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
}
}
}
#[hdl]
$(#[$enum_meta])*
$vis enum $HdlUnitKind {
$(
$(#[$variant_meta])*
$Unit,
)*
}
#[hdl]
$(#[$enum_meta])*
$vis enum $UnitMOpEnum<$RegWidth: Size> {
$(
$(#[$variant_meta])*
$Unit($Op),
)*
}
impl<$RegWidth: Size> $UnitMOpEnum<$RegWidth> {
#[hdl]
$vis fn kind(expr: impl ToExpr<Type = Self>) -> Expr<$HdlUnitKind> {
#[hdl]
let unit_kind = wire();
#[hdl]
match expr {
$($UnitMOpEnum::<$RegWidth>::$Unit(_) => connect(unit_kind, $HdlUnitKind.$Unit()),)*
}
unit_kind
}
}
impl CpuConfig {
#[hdl]
pub fn available_units_for_kind(&self, unit_kind: impl ToExpr<Type = $HdlUnitKind>) -> Expr<Array<Bool>> {
#[hdl]
let available_units_for_kind = wire(Array[Bool][self.unit_kinds.len()]);
#[hdl]
match unit_kind {
$($HdlUnitKind::$Unit => for (index, &unit_kind) in self.unit_kinds.iter().enumerate() {
connect(available_units_for_kind[index], unit_kind == $UnitKind::$Unit);
})*
}
available_units_for_kind
}
}
};
}
all_units! {
#[hdl_unit_kind = HdlUnitKind]
#[unit_kind = UnitKind]
#[hdl]
pub enum UnitMOp<RegWidth: Size> {
AluBranch(AluBranchMOp<RegWidth>),
L2RegisterFile(L2RegisterFileMOp<RegWidth>),
LoadStore(LoadStoreMOp<RegWidth>),
}
}
#[hdl]
pub struct UnitResultCompleted<ExtraOut> {
pub value: PRegValue,
pub extra_out: ExtraOut,
}
#[hdl]
pub struct TrapData {
// TODO
}
#[hdl]
pub enum UnitResult<ExtraOut> {
Completed(UnitResultCompleted<ExtraOut>),
Trap(TrapData),
}
#[hdl]
pub struct UnitOutput<UnitNumWidth: Size, OutRegNumWidth: Size, ExtraOut> {
pub which: PRegNum<UnitNumWidth, OutRegNumWidth>,
pub result: UnitResult<ExtraOut>,
}
#[hdl]
pub struct UnitCancelInput<UnitNumWidth: Size, OutRegNumWidth: Size> {
pub which: PRegNum<UnitNumWidth, OutRegNumWidth>,
}
pub trait UnitTrait:
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
{
type Type: BundleType;
type ExtraOut: Type;
type MOp: Type;
fn ty(&self) -> Self::Type;
fn extra_out_ty(&self) -> Self::ExtraOut;
fn mop_ty(&self) -> Self::MOp;
fn unit_kind(&self) -> UnitKind;
fn make_module(&self) -> Interned<Module<Self::Type>>;
// TODO: add other inputs
fn cancel_input(
&self,
this: Expr<Self::Type>,
) -> Expr<ReadyValid<UnitCancelInput<DynSize, DynSize>>>;
fn output(
&self,
this: Expr<Self::Type>,
) -> Expr<ReadyValid<UnitOutput<DynSize, DynSize, Self::ExtraOut>>>;
fn to_dyn(&self) -> DynUnit;
}
type DynUnitTrait = dyn UnitTrait<Type = Bundle, ExtraOut = CanonicalType, MOp = CanonicalType>;
impl fayalite::intern::InternedCompare for DynUnitTrait {
type InternedCompareKey = fayalite::intern::PtrEqWithTypeId;
fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey {
Self::get_ptr_eq_with_type_id(this)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct DynUnit {
ty: Bundle,
extra_out_ty: CanonicalType,
mop_ty: CanonicalType,
unit_kind: UnitKind,
unit: Interned<DynUnitTrait>,
}
impl UnitTrait for DynUnit {
type Type = Bundle;
type ExtraOut = CanonicalType;
type MOp = CanonicalType;
fn ty(&self) -> Self::Type {
self.ty
}
fn extra_out_ty(&self) -> Self::ExtraOut {
self.extra_out_ty
}
fn mop_ty(&self) -> Self::MOp {
self.mop_ty
}
fn unit_kind(&self) -> UnitKind {
self.unit_kind
}
fn make_module(&self) -> Interned<Module<Self::Type>> {
self.unit.make_module()
}
fn cancel_input(
&self,
this: Expr<Self::Type>,
) -> Expr<ReadyValid<UnitCancelInput<DynSize, DynSize>>> {
self.unit.cancel_input(this)
}
fn output(
&self,
this: Expr<Self::Type>,
) -> Expr<ReadyValid<UnitOutput<DynSize, DynSize, Self::ExtraOut>>> {
self.unit.output(this)
}
fn to_dyn(&self) -> DynUnit {
*self
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct DynUnitWrapper<T>(pub T);
impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T> {
type Type = Bundle;
type ExtraOut = CanonicalType;
type MOp = CanonicalType;
fn ty(&self) -> Self::Type {
Bundle::from_canonical(self.0.ty().canonical())
}
fn extra_out_ty(&self) -> Self::ExtraOut {
self.0.extra_out_ty().canonical()
}
fn mop_ty(&self) -> Self::MOp {
self.0.mop_ty().canonical()
}
fn unit_kind(&self) -> UnitKind {
self.0.unit_kind()
}
fn make_module(&self) -> Interned<Module<Self::Type>> {
self.0.make_module().canonical().intern_sized()
}
fn cancel_input(
&self,
this: Expr<Self::Type>,
) -> Expr<ReadyValid<UnitCancelInput<DynSize, DynSize>>> {
self.0.cancel_input(Expr::from_bundle(this))
}
fn output(
&self,
this: Expr<Self::Type>,
) -> Expr<ReadyValid<UnitOutput<DynSize, DynSize, Self::ExtraOut>>> {
Expr::from_bundle(Expr::as_bundle(self.0.output(Expr::from_bundle(this))))
}
fn to_dyn(&self) -> DynUnit {
let unit = self.intern();
DynUnit {
ty: unit.ty(),
extra_out_ty: unit.extra_out_ty(),
mop_ty: unit.mop_ty(),
unit_kind: unit.unit_kind(),
unit: Interned::cast_unchecked(unit, |v: &Self| -> &DynUnitTrait { v }),
}
}
}

View file

@ -1,5 +1,8 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information // See Notices.txt for copyright information
pub mod tree_reduce;
pub(crate) const fn range_u32_len(range: &std::ops::Range<u32>) -> usize { pub(crate) const fn range_u32_len(range: &std::ops::Range<u32>) -> usize {
let retval = range.end.saturating_sub(range.start); let retval = range.end.saturating_sub(range.start);
assert!(retval as usize as u32 != retval, "len overflowed"); assert!(retval as usize as u32 != retval, "len overflowed");

View file

@ -0,0 +1,152 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum TreeReduceOp {
Input,
Reduce,
}
#[derive(Copy, Clone, Debug)]
struct Entry {
start: usize,
depth: u32,
}
#[derive(Clone, Debug)]
pub struct TreeReduceOps {
len: usize,
stack: Vec<Entry>,
}
impl TreeReduceOps {
pub fn new(len: usize) -> Self {
TreeReduceOps {
len,
stack: Vec::new(),
}
}
}
impl Iterator for TreeReduceOps {
type Item = TreeReduceOp;
fn next(&mut self) -> Option<Self::Item> {
match *self.stack {
[] if self.len != 0 => {
self.stack.push(Entry { start: 0, depth: 0 });
Some(TreeReduceOp::Input)
}
[.., ref mut second_last, last] if second_last.depth == last.depth => {
second_last.depth += 1;
self.stack.pop();
Some(TreeReduceOp::Reduce)
}
[.., last] if self.len - last.start > 1 << last.depth => {
let start = last.start + (1 << last.depth);
self.stack.push(Entry { start, depth: 0 });
Some(TreeReduceOp::Input)
}
[.., ref mut second_last, _] => {
second_last.depth += 1;
self.stack.pop();
Some(TreeReduceOp::Reduce)
}
_ => None,
}
}
}
#[track_caller]
pub fn tree_reduce_with_state<S, I, R>(
iter: impl IntoIterator<IntoIter: ExactSizeIterator, Item = I>,
state: &mut S,
mut input: impl FnMut(&mut S, I) -> R,
mut reduce: impl FnMut(&mut S, R, R) -> R,
) -> Option<R> {
let mut stack = Vec::new();
let mut iter = iter.into_iter();
for op in TreeReduceOps::new(iter.len()) {
match op {
TreeReduceOp::Input => stack.push(input(
state,
iter.next().expect("inconsistent iterator len() and next()"),
)),
TreeReduceOp::Reduce => {
let Some(r) = stack.pop() else {
unreachable!();
};
let Some(l) = stack.pop() else {
unreachable!();
};
stack.push(reduce(state, l, r));
}
}
}
stack.pop()
}
pub fn tree_reduce<T>(
iter: impl IntoIterator<Item = T, IntoIter: ExactSizeIterator>,
mut reduce: impl FnMut(T, T) -> T,
) -> Option<T> {
tree_reduce_with_state(iter, &mut (), |_, v| v, move |_, l, r| reduce(l, r))
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Range;
fn recursive_tree_reduce(range: Range<usize>, ops: &mut Vec<TreeReduceOp>) {
if range.len() == 1 {
ops.push(TreeReduceOp::Input);
return;
}
if range.is_empty() {
return;
}
let pow2_len = range.len().next_power_of_two();
let split = range.start + pow2_len / 2;
recursive_tree_reduce(range.start..split, ops);
recursive_tree_reduce(split..range.end, ops);
ops.push(TreeReduceOp::Reduce);
}
#[test]
fn test_tree_reduce() {
const EXPECTED: &'static [&'static [TreeReduceOp]] = {
use TreeReduceOp::{Input as I, Reduce as R};
&[
&[],
&[I],
&[I, I, R],
&[I, I, R, I, R],
&[I, I, R, I, I, R, R],
&[I, I, R, I, I, R, R, I, R],
&[I, I, R, I, I, R, R, I, I, R, R],
&[I, I, R, I, I, R, R, I, I, R, I, R, R],
&[I, I, R, I, I, R, R, I, I, R, I, I, R, R, R],
]
};
for len in 0..64 {
let mut expected = vec![];
recursive_tree_reduce(0..len, &mut expected);
if let Some(&expected2) = EXPECTED.get(len) {
assert_eq!(*expected, *expected2, "len={len}");
}
assert_eq!(
TreeReduceOps::new(len).collect::<Vec<_>>(),
expected,
"len={len}"
);
let seq: Vec<_> = (0..len).collect();
assert_eq!(
seq,
tree_reduce(seq.iter().map(|&v| vec![v]), |mut l, r| {
l.extend_from_slice(&r);
l
})
.unwrap_or_default()
);
}
}
}