use std::marker::PhantomData;
use furiosa_mapping::M;
use furiosa_opt_lower::{VrfOperandInput, config_vrf_operand};
use furiosa_opt_macro::primitive;
use crate::engine::vector::branch::{BinaryBranchedOperand, RfPort, TagGuard, TernaryBranchedOperand};
use crate::engine::vector::op::FpUnaryOp;
use crate::engine::vector::scalar::VeScalar;
use crate::engine::vector::stage::markers::Way;
use crate::engine::vector::stage::state::VeState;
use crate::engine::vector::stash_slot::{Occupied, Spent, StashState};
use crate::engine::vector::tensor::VeTensorShape;
use crate::tensor::Tensor;
use crate::tensor::memory::VrfTensor;
use furiosa_opt_common_ir::{BranchedOperand, ImmSlot, SlotDefect};
#[primitive(op::Stash)]
#[derive(Debug, Clone, Copy)]
pub struct Stash;
mod sealed {
pub trait Sealed {}
pub trait SlotSealed {}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be used as an operand here",
label = "not usable as this op's operand",
note = "if this is `Stash`: a `vector_stash` has to come earlier, only one op may read it, and the \
stream has to carry the same scalar and way it carried at that write (see `StashTransition`)"
)]
pub trait Plain: sealed::Sealed {}
#[diagnostic::on_unimplemented(
message = "the stash cannot be read here",
label = "this operand reads the stash",
note = "the stash keeps the scalar and the way the stream carried at the `vector_stash`, and only a \
stream carrying that same pair can read it: reinterpret (or narrow / widen) back first",
note = "it is written once and read once, so with no earlier `vector_stash` the slot is empty, and a \
second `Stash` operand finds it spent"
)]
pub trait StashTransition<S: StashState, RD: VeScalar, const W: Way> {
type Next: StashState;
fn transition(state: VeState<S>) -> VeState<Self::Next>;
fn stashed<Mapping: M>(state: &VeState<S>) -> Option<Tensor<RD, Mapping>>;
}
impl<S: StashState, RD: VeScalar, T: Plain, const W: Way> StashTransition<S, RD, W> for T {
type Next = S;
fn transition(state: VeState<S>) -> VeState<S> {
state
}
fn stashed<Mapping: M>(_state: &VeState<S>) -> Option<Tensor<RD, Mapping>> {
None
}
}
impl<D: VeScalar, StashMapping: M, const W: Way> StashTransition<Occupied<D, StashMapping, W>, D, W> for Stash {
type Next = Spent;
fn transition(state: VeState<Occupied<D, StashMapping, W>>) -> VeState<Spent> {
state.consume_stash()
}
fn stashed<Mapping: M>(state: &VeState<Occupied<D, StashMapping, W>>) -> Option<Tensor<D, Mapping>> {
Some(state.stash_tensor())
}
}
impl<D: VeScalar, StashMapping: M, const W: Way> StashTransition<Occupied<D, StashMapping, W>, D, W> for (Stash, f32) {
type Next = Spent;
fn transition(state: VeState<Occupied<D, StashMapping, W>>) -> VeState<Spent> {
<Stash as StashTransition<Occupied<D, StashMapping, W>, D, W>>::transition(state)
}
fn stashed<Mapping: M>(state: &VeState<Occupied<D, StashMapping, W>>) -> Option<Tensor<D, Mapping>> {
<Stash as StashTransition<Occupied<D, StashMapping, W>, D, W>>::stashed(state)
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoStash;
#[derive(Debug, Clone, Copy)]
pub struct WithStash;
#[derive(Debug, Clone, Copy)]
pub struct OneReg;
#[derive(Debug, Clone, Copy)]
pub struct TwoRegs;
#[derive(Debug, Clone, Copy)]
pub struct ThreeRegs;
#[derive(Debug, Clone, Copy)]
pub struct AtRf;
#[diagnostic::on_unimplemented(
message = "this VE pass has no immediate register left",
label = "all three immediate registers are already spoken for",
note = "a pass has three immediates and one rf port; drop one of the earlier `imm` calls, or move \
the value onto the rf port with `rf` if it is a register read"
)]
pub trait CanAppendImm {
type Next;
const NEXT_IMM: ImmSlot;
}
impl CanAppendImm for OneReg {
type Next = TwoRegs;
const NEXT_IMM: ImmSlot = ImmSlot::Reg1;
}
impl CanAppendImm for TwoRegs {
type Next = ThreeRegs;
const NEXT_IMM: ImmSlot = ImmSlot::Reg2;
}
#[diagnostic::on_unimplemented(
message = "this VE pass has already filled its register-file port",
label = "the rf port is filled once, and it is the last slot",
note = "a pass reads the register file once, so one `rf` call -- and because the stash is read \
through `rf`, this is also what stops a second stash read"
)]
pub trait CanFillRf {}
impl CanFillRf for OneReg {}
impl CanFillRf for TwoRegs {}
impl CanFillRf for ThreeRegs {}
pub trait RegPayload<D: VeScalar, Mapping: M>: sealed::SlotSealed {
type Port;
}
impl<Mapping: M> RegPayload<i32, Mapping> for i32 {
type Port = RfPort<i32, Mapping>;
}
impl<Mapping: M> RegPayload<f32, Mapping> for f32 {
type Port = RfPort<f32, Mapping>;
}
impl<Mapping: M> RegPayload<f32, Mapping> for (f32, f32) {
type Port = (RfPort<f32, Mapping>, f32);
}
#[derive(Debug)]
pub enum Branched {}
impl Branched {
#[primitive(op::Branched::imm)]
pub fn imm<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>>(
guard: TagGuard,
v: Reg,
) -> VeOperandBuilder<D, Mapping, Reg, NoStash, OneReg> {
fill_reg(BranchedOperand::default(), ImmSlot::Reg0, guard, v)
}
#[primitive(op::Branched::rf)]
pub fn rf<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, A: PortArg<D, Mapping, Reg>>(
guard: TagGuard,
arg: A,
) -> VeOperandBuilder<D, Mapping, Reg, A::Mark, AtRf> {
fill_rf(BranchedOperand::default(), guard, arg)
}
}
fn fill_reg<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, StashMark, Slot>(
mut operand: BranchedOperand<Reg, Reg::Port>,
imm: ImmSlot,
guard: TagGuard,
v: Reg,
) -> VeOperandBuilder<D, Mapping, Reg, StashMark, Slot> {
assert_fill(operand.fill_imm(imm, guard, v));
VeOperandBuilder {
operand,
_mark: PhantomData,
}
}
fn fill_rf<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, A: PortArg<D, Mapping, Reg>>(
mut operand: BranchedOperand<Reg, Reg::Port>,
guard: TagGuard,
arg: A,
) -> VeOperandBuilder<D, Mapping, Reg, A::Mark, AtRf> {
assert_fill(operand.fill_rf(guard, arg.into_port()));
VeOperandBuilder {
operand,
_mark: PhantomData,
}
}
fn assert_fill(filled: Result<(), SlotDefect>) {
if let Err(defect) = filled {
panic!("{defect}");
}
}
#[derive(Debug, Clone)]
pub struct VeOperandBuilder<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, StashMark = NoStash, Slot = OneReg> {
operand: BranchedOperand<Reg, Reg::Port>,
_mark: PhantomData<(D, Mapping, StashMark, Slot)>,
}
impl<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, StashMark, Slot: CanAppendImm>
VeOperandBuilder<D, Mapping, Reg, StashMark, Slot>
{
#[primitive(op::VeOperandBuilder::imm)]
pub fn imm(self, guard: TagGuard, v: Reg) -> VeOperandBuilder<D, Mapping, Reg, StashMark, Slot::Next> {
fill_reg(self.operand, Slot::NEXT_IMM, guard, v)
}
}
impl<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, StashMark, Slot: CanFillRf>
VeOperandBuilder<D, Mapping, Reg, StashMark, Slot>
{
#[primitive(op::VeOperandBuilder::rf)]
pub fn rf<A: PortArg<D, Mapping, Reg>>(
self,
guard: TagGuard,
arg: A,
) -> VeOperandBuilder<D, Mapping, Reg, A::Mark, AtRf> {
fill_rf(self.operand, guard, arg)
}
}
pub trait IntoGuardedUnaryOp: sealed::SlotSealed {
fn into_guarded_unary_op(self) -> (TagGuard, FpUnaryOp);
}
impl IntoGuardedUnaryOp for FpUnaryOp {
fn into_guarded_unary_op(self) -> (TagGuard, FpUnaryOp) {
(TagGuard::all(), self)
}
}
impl IntoGuardedUnaryOp for (TagGuard, FpUnaryOp) {
fn into_guarded_unary_op(self) -> (TagGuard, FpUnaryOp) {
self
}
}
impl sealed::SlotSealed for FpUnaryOp {}
impl sealed::SlotSealed for TagGuard {}
pub trait PortArg<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>>: sealed::SlotSealed {
type Mark;
fn into_port(self) -> Reg::Port;
}
#[track_caller]
fn verify_vrf_operand<Element: M, Time: M, Packet: M>() {
if let Err(error) = config_vrf_operand(VrfOperandInput {
vrf_element: Element::to_value(),
stream_time: Time::to_value(),
stream_packet: Packet::to_value(),
}) {
panic!("{error}");
}
}
#[track_caller]
fn vrf_rf_port<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M, Time: M, Packet: M>(
vrf: &VrfTensor<D, Chip, Cluster, Slice, Element>,
) -> RfPort<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>> {
verify_vrf_operand::<Element, Time, Packet>();
RfPort::External(
vrf.inner
.transpose::<VeTensorShape<Chip, Cluster, Slice, Time, Packet>>(true),
)
}
impl<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M, Time: M, Packet: M>
PortArg<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>, D> for &VrfTensor<D, Chip, Cluster, Slice, Element>
where
D: RegPayload<
D,
VeTensorShape<Chip, Cluster, Slice, Time, Packet>,
Port = RfPort<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>,
>,
{
type Mark = NoStash;
#[track_caller]
fn into_port(self) -> RfPort<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>> {
vrf_rf_port(self)
}
}
impl<D: VeScalar, Mapping: M> PortArg<D, Mapping, D> for Stash
where
D: RegPayload<D, Mapping, Port = RfPort<D, Mapping>>,
{
type Mark = WithStash;
fn into_port(self) -> RfPort<D, Mapping> {
RfPort::Stash
}
}
impl<Chip: M, Cluster: M, Slice: M, Element: M, Time: M, Packet: M>
PortArg<f32, VeTensorShape<Chip, Cluster, Slice, Time, Packet>, (f32, f32)>
for (&VrfTensor<f32, Chip, Cluster, Slice, Element>, f32)
{
type Mark = NoStash;
#[track_caller]
fn into_port(self) -> (RfPort<f32, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>, f32) {
let (vrf, operand1) = self;
(vrf_rf_port(vrf), operand1)
}
}
impl<Mapping: M> PortArg<f32, Mapping, (f32, f32)> for (Stash, f32) {
type Mark = WithStash;
fn into_port(self) -> (RfPort<f32, Mapping>, f32) {
let (_, operand1) = self;
(RfPort::Stash, operand1)
}
}
impl<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, Slot> sealed::Sealed
for VeOperandBuilder<D, Mapping, Reg, NoStash, Slot>
{
}
impl<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, Slot> Plain
for VeOperandBuilder<D, Mapping, Reg, NoStash, Slot>
{
}
impl<D: VeScalar, StashMapping: M, Mapping: M, Reg: RegPayload<D, Mapping>, const W: Way>
StashTransition<Occupied<D, StashMapping, W>, D, W> for VeOperandBuilder<D, Mapping, Reg, WithStash, AtRf>
{
type Next = Spent;
fn transition(state: VeState<Occupied<D, StashMapping, W>>) -> VeState<Spent> {
state.consume_stash()
}
fn stashed<TargetMapping: M>(state: &VeState<Occupied<D, StashMapping, W>>) -> Option<Tensor<D, TargetMapping>> {
<Stash as StashTransition<Occupied<D, StashMapping, W>, D, W>>::stashed(state)
}
}
impl sealed::Sealed for i32 {}
impl Plain for i32 {}
impl sealed::Sealed for f32 {}
impl Plain for f32 {}
impl<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M> sealed::Sealed
for &VrfTensor<D, Chip, Cluster, Slice, Element>
{
}
impl<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M> Plain for &VrfTensor<D, Chip, Cluster, Slice, Element> {}
impl<A: Plain, B: Plain> sealed::Sealed for (A, B) {}
impl<A: Plain, B: Plain> Plain for (A, B) {}
impl sealed::SlotSealed for i32 {}
impl sealed::SlotSealed for f32 {}
impl sealed::SlotSealed for Stash {}
impl sealed::SlotSealed for () {}
impl<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M> sealed::SlotSealed
for &VrfTensor<D, Chip, Cluster, Slice, Element>
{
}
impl<A: sealed::SlotSealed, B: sealed::SlotSealed> sealed::SlotSealed for (A, B) {}
impl<D: VeScalar, Mapping: M, Reg: RegPayload<D, Mapping>, StashMark, Slot> sealed::SlotSealed
for VeOperandBuilder<D, Mapping, Reg, StashMark, Slot>
{
}
impl<Mapping: M> sealed::SlotSealed for GroupTernaryOperand<Mapping> {}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be this op's operand",
label = "not this op's operand shape",
note = "a VRF operand carries the stream's own `Chip` / `Cluster` / `Slice`; when the two describe \
the same slices, restate it with `VrfTensor::reshape`",
note = "a constant operand carries the stream's scalar: `i32` for logic / fxp, `f32` for fp"
)]
pub trait IntoBranchedOperand<D: VeScalar, TargetMapping: M>: Sized + sealed::SlotSealed {
fn fill_slots(self, operand: &mut BinaryBranchedOperand<D, TargetMapping>, guard: TagGuard);
fn into_branched_operand(self) -> BinaryBranchedOperand<D, TargetMapping> {
let mut operand = BinaryBranchedOperand::default();
self.fill_slots(&mut operand, TagGuard::all());
operand
}
}
impl<Mapping: M> IntoBranchedOperand<i32, Mapping> for i32 {
fn fill_slots(self, operand: &mut BinaryBranchedOperand<i32, Mapping>, guard: TagGuard) {
assert_fill(operand.fill_imm(ImmSlot::Reg0, guard, self));
}
}
impl<Mapping: M> IntoBranchedOperand<f32, Mapping> for f32 {
fn fill_slots(self, operand: &mut BinaryBranchedOperand<f32, Mapping>, guard: TagGuard) {
assert_fill(operand.fill_imm(ImmSlot::Reg0, guard, self));
}
}
impl<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M, Time: M, Packet: M>
IntoBranchedOperand<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>
for &VrfTensor<D, Chip, Cluster, Slice, Element>
{
#[track_caller]
fn fill_slots(
self,
operand: &mut BinaryBranchedOperand<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>,
guard: TagGuard,
) {
assert_fill(operand.fill_rf(guard, vrf_rf_port(self)));
}
}
impl<D: VeScalar, Mapping: M> IntoBranchedOperand<D, Mapping> for Stash {
fn fill_slots(self, operand: &mut BinaryBranchedOperand<D, Mapping>, guard: TagGuard) {
assert_fill(operand.fill_rf(guard, RfPort::Stash));
}
}
impl<D: VeScalar, Mapping: M, StashMark, Slot> IntoBranchedOperand<D, Mapping>
for VeOperandBuilder<D, Mapping, D, StashMark, Slot>
where
D: RegPayload<D, Mapping, Port = RfPort<D, Mapping>>,
{
fn fill_slots(self, operand: &mut BinaryBranchedOperand<D, Mapping>, _guard: TagGuard) {
*operand = self.operand;
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be this op's operand",
label = "not this op's operand shape",
note = "a VRF operand carries the stream's own `Chip` / `Cluster` / `Slice`; when the two describe \
the same slices, restate it with `VrfTensor::reshape` (`IntoBranchedOperand` states every \
operand rule)"
)]
pub trait IntoTernaryOperand<D: VeScalar, TargetMapping: M>: Sized + sealed::SlotSealed {
fn fill_ternary_slots(self, operand: &mut TernaryBranchedOperand<D, TargetMapping>, guard: TagGuard);
fn into_ternary_operand(self) -> TernaryBranchedOperand<D, TargetMapping> {
let mut operand = TernaryBranchedOperand::default();
self.fill_ternary_slots(&mut operand, TagGuard::all());
operand
}
}
impl<Mapping: M> IntoTernaryOperand<f32, Mapping> for (f32, f32) {
fn fill_ternary_slots(self, operand: &mut TernaryBranchedOperand<f32, Mapping>, guard: TagGuard) {
assert_fill(operand.fill_imm(ImmSlot::Reg0, guard, self));
}
}
impl<Chip: M, Cluster: M, Slice: M, Element: M, Time: M, Packet: M>
IntoTernaryOperand<f32, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>
for (&VrfTensor<f32, Chip, Cluster, Slice, Element>, f32)
{
#[track_caller]
fn fill_ternary_slots(
self,
operand: &mut TernaryBranchedOperand<f32, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>,
guard: TagGuard,
) {
let (vrf, operand1) = self;
assert_fill(operand.fill_rf(guard, (vrf_rf_port(vrf), operand1)));
}
}
impl<Mapping: M> IntoTernaryOperand<f32, Mapping> for (Stash, f32) {
fn fill_ternary_slots(self, operand: &mut TernaryBranchedOperand<f32, Mapping>, guard: TagGuard) {
let (_, operand1) = self;
assert_fill(operand.fill_rf(guard, (RfPort::Stash, operand1)));
}
}
impl<Mapping: M, StashMark, Slot> IntoTernaryOperand<f32, Mapping>
for VeOperandBuilder<f32, Mapping, (f32, f32), StashMark, Slot>
{
fn fill_ternary_slots(self, operand: &mut TernaryBranchedOperand<f32, Mapping>, _guard: TagGuard) {
*operand = self.operand;
}
}
pub type GroupOperand<D, Mapping> = Option<BinaryBranchedOperand<D, Mapping>>;
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be this op's operand",
label = "not this op's operand shape",
note = "a VRF operand carries the stream's own `Chip` / `Cluster` / `Slice`; when the two describe \
the same slices, restate it with `VrfTensor::reshape` (`IntoBranchedOperand` states every \
operand rule)"
)]
pub trait IntoGroupOperand<D: VeScalar, Mapping: M>: sealed::SlotSealed {
fn into_group_operand(self) -> GroupOperand<D, Mapping>;
}
impl<D: VeScalar, Mapping: M> IntoGroupOperand<D, Mapping> for () {
fn into_group_operand(self) -> GroupOperand<D, Mapping> {
None
}
}
impl<Mapping: M> IntoGroupOperand<i32, Mapping> for i32 {
fn into_group_operand(self) -> GroupOperand<i32, Mapping> {
Some(self.into_branched_operand())
}
}
impl<Mapping: M> IntoGroupOperand<f32, Mapping> for f32 {
fn into_group_operand(self) -> GroupOperand<f32, Mapping> {
Some(self.into_branched_operand())
}
}
impl<D: VeScalar, Chip: M, Cluster: M, Slice: M, Element: M, Time: M, Packet: M>
IntoGroupOperand<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>>
for &VrfTensor<D, Chip, Cluster, Slice, Element>
{
#[track_caller]
fn into_group_operand(self) -> GroupOperand<D, VeTensorShape<Chip, Cluster, Slice, Time, Packet>> {
Some(self.into_branched_operand())
}
}
pub type GroupTernaryOperand<Mapping> = Option<TernaryBranchedOperand<f32, Mapping>>;
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be this op's operand",
label = "not this op's operand shape",
note = "a VRF operand carries the stream's own `Chip` / `Cluster` / `Slice`; when the two describe \
the same slices, restate it with `VrfTensor::reshape` (`IntoBranchedOperand` states every \
operand rule)"
)]
pub trait IntoGroupTernaryOperand<Mapping: M>: sealed::SlotSealed {
fn into_group_ternary_operand(self) -> GroupTernaryOperand<Mapping>;
}
impl<Mapping: M> IntoGroupTernaryOperand<Mapping> for () {
fn into_group_ternary_operand(self) -> GroupTernaryOperand<Mapping> {
None
}
}
impl<Mapping: M> IntoGroupTernaryOperand<Mapping> for GroupTernaryOperand<Mapping> {
fn into_group_ternary_operand(self) -> GroupTernaryOperand<Mapping> {
self
}
}
impl<T, Mapping: M> IntoGroupTernaryOperand<Mapping> for (T, f32)
where
(T, f32): IntoTernaryOperand<f32, Mapping>,
T: Plain,
{
fn into_group_ternary_operand(self) -> GroupTernaryOperand<Mapping> {
Some(self.into_ternary_operand())
}
}
#[cfg(test)]
mod tests {
use furiosa_mapping::Broadcast;
use super::*;
use crate::engine::vector::branch::BitReq::{Ignore, One, Zero};
use crate::engine::vector::branch::{ExecutionId, VeOperandLayout};
use crate::tensor::Tensor;
type Map = Broadcast<1>;
type VrfStream = VeTensorShape<Map, Map, Map, Map, Broadcast<8>>;
type VrfElement = Broadcast<8>;
fn id<const RAW: u8>() -> ExecutionId {
ExecutionId::new::<RAW>()
}
const GROUP0: TagGuard = TagGuard::matches([Ignore, Ignore, Ignore, Zero]);
const GROUP1: TagGuard = TagGuard::matches([Ignore, Ignore, Ignore, One]);
fn slots<Reg: Copy, Port>(operand: &BranchedOperand<Reg, Port>) -> ([Option<(TagGuard, Reg)>; 3], bool) {
let regs = operand
.reg_slots()
.map(|slot| slot.as_ref().map(|(guard, v)| (*guard, *v)));
(regs, operand.rf_slot().is_some())
}
#[test]
fn one_immediate_register_leaves_the_rest_unused() {
let operand: BinaryBranchedOperand<f32, Map> = Branched::imm(TagGuard::all(), 2.0f32).into_branched_operand();
let ([reg0, reg1, reg2], has_rf) = slots(&operand);
assert_eq!(reg0, Some((TagGuard::all(), 2.0f32)));
assert!(reg1.is_none() && reg2.is_none() && !has_rf);
let operand: BinaryBranchedOperand<i32, Map> =
Branched::imm(TagGuard::all(), 0x777f_i32).into_branched_operand();
let ([reg0, reg1, reg2], has_rf) = slots(&operand);
assert_eq!(reg0, Some((TagGuard::all(), 0x777f_i32)));
assert!(reg1.is_none() && reg2.is_none() && !has_rf);
}
#[test]
fn register_fills_the_rf_port_and_leaves_the_rest_unused() {
let vrf: VrfTensor<i32, Map, Map, Map, VrfElement> = VrfTensor::new(Tensor::splat(7));
let operand: BinaryBranchedOperand<i32, VrfStream> =
Branched::rf(TagGuard::all(), &vrf).into_branched_operand();
let (rf_guard, port) = operand.rf_slot().as_ref().unwrap();
let RfPort::External(rf_data) = port else {
panic!("a register read is `External`")
};
assert_eq!(*rf_guard, TagGuard::all());
assert_eq!(rf_data.clone().into_vec(), vec![7; 8]);
assert!(!operand.reads_stash());
assert!(operand.reg_slots().iter().all(|slot| slot.is_none()));
}
#[test]
fn a_bit3_guard_gates_its_slot_on_that_group() {
let operand: BinaryBranchedOperand<f32, Map> = Branched::imm(GROUP0, 2.0f32).into_branched_operand();
let ([reg0, _, _], has_rf) = slots(&operand);
let (reg0_guard, reg0_value) = reg0.unwrap();
assert_eq!(reg0_value, 2.0);
assert!(reg0_guard.admits(id::<0>()) && !reg0_guard.admits(id::<0b1000>()));
assert!(!has_rf);
let vrf: VrfTensor<f32, Map, Map, Map, VrfElement> = VrfTensor::new(Tensor::splat(9.0));
let operand: BinaryBranchedOperand<f32, VrfStream> = Branched::rf(GROUP1, &vrf).into_branched_operand();
let Some((rf_guard, RfPort::External(_))) = operand.rf_slot() else {
panic!("a register read is `RfPort::External`")
};
assert!(rf_guard.admits(id::<0b1000>()) && !rf_guard.admits(id::<0>()));
assert!(operand.reg_slots().iter().all(|slot| slot.is_none()));
}
#[test]
fn a_pair_argument_makes_the_slots_ternary() {
let ternary: TernaryBranchedOperand<f32, Map> =
Branched::imm(TagGuard::all(), (2.0f32, 3.0f32)).into_ternary_operand();
let ([reg0, _, _], has_rf) = slots(&ternary);
let (guard, (operand0, operand1)) = reg0.unwrap();
assert_eq!(guard, TagGuard::all());
assert_eq!((operand0, operand1), (2.0, 3.0));
assert!(!has_rf);
let ternary: TernaryBranchedOperand<f32, Map> =
Branched::rf::<f32, Map, (f32, f32), _>(TagGuard::all(), (Stash, 1.0f32)).into_ternary_operand();
assert!(ternary.reads_stash());
let Some((guard, (RfPort::Stash, operand1))) = ternary.rf_slot() else {
panic!("a stash read is `RfPort::Stash`")
};
assert_eq!(*guard, TagGuard::all());
assert_eq!(*operand1, 1.0);
assert!(ternary.reg_slots().iter().all(|slot| slot.is_none()));
}
#[test]
fn per_branch_ternary_operands_fill_one_register_each() {
let ternary: TernaryBranchedOperand<f32, Map> = Branched::imm(GROUP0, (2.0f32, 1.0f32))
.imm(GROUP1, (3.0f32, 2.0f32))
.into_ternary_operand();
let ([reg0, reg1, reg2], has_rf) = slots(&ternary);
let (group0_guard, (operand0, operand1)) = reg0.unwrap();
assert_eq!((operand0, operand1), (2.0, 1.0));
assert!(group0_guard.admits(id::<0>()) && !group0_guard.admits(id::<0b1000>()));
let (group1_guard, (operand0, operand1)) = reg1.unwrap();
assert_eq!((operand0, operand1), (3.0, 2.0));
assert!(group1_guard.admits(id::<0b1000>()) && !group1_guard.admits(id::<0>()));
assert!(reg2.is_none() && !has_rf);
}
#[test]
fn regs_fill_densely_in_call_order() {
let first = TagGuard::matches([One, Ignore, Ignore, Ignore]);
let second = TagGuard::matches([Zero, One, Ignore, Ignore]);
let operand: BinaryBranchedOperand<f32, Map> =
Branched::imm(first, 1.0f32).imm(second, 2.0f32).into_branched_operand();
let ([reg0, reg1, reg2], has_rf) = slots(&operand);
assert!(
matches!(reg0, Some((g, v)) if g == first && v == 1.0),
"first call, first slot"
);
assert!(
matches!(reg1, Some((g, v)) if g == second && v == 2.0),
"second call, second slot"
);
assert!(reg2.is_none(), "the third is untouched");
assert!(!has_rf);
}
#[test]
fn three_immediates_and_the_rf_port_fill_the_pass() {
let vrf: VrfTensor<f32, Map, Map, Map, VrfElement> = VrfTensor::new(Tensor::splat(9.0));
let operand: BinaryBranchedOperand<f32, VrfStream> =
Branched::imm(TagGuard::matches([One, Ignore, Ignore, Ignore]), 1.0f32)
.imm(TagGuard::matches([Zero, One, Ignore, Ignore]), 2.0f32)
.imm(TagGuard::matches([Zero, Zero, One, Ignore]), 3.0f32)
.rf(TagGuard::matches([Zero, Zero, Zero, One]), &vrf)
.into_branched_operand();
let ([reg0, reg1, reg2], has_rf) = slots(&operand);
assert!(matches!(reg0, Some((_, v)) if v == 1.0));
assert!(matches!(reg1, Some((_, v)) if v == 2.0));
assert!(matches!(reg2, Some((_, v)) if v == 3.0));
assert!(has_rf);
}
#[test]
fn builder_stash_slot_reads_the_stash() {
let operand: BinaryBranchedOperand<f32, Map> =
Branched::imm(TagGuard::not_matches([One, Ignore, Ignore, Ignore]), 1.0f32)
.rf(TagGuard::matches([One, Ignore, Ignore, Ignore]), Stash)
.into_branched_operand();
assert!(operand.reads_stash());
let Some((stash_guard, RfPort::Stash)) = operand.rf_slot() else {
panic!("a stash read is `RfPort::Stash`")
};
assert!(stash_guard.admits(id::<0b0001>()));
}
}