ab_riscv_interpreter/zvbb/zvkb/zvkb_helpers.rs
1//! Opaque helpers for Zvkb extension
2
3use crate::v::vector_registers::VectorRegistersExt;
4pub use crate::v::zvexx::arith::zvexx_arith_helpers::{OpSrc, check_vreg_group_alignment};
5use crate::v::zvexx::arith::zvexx_arith_helpers::{read_element_u64, sew_mask, write_element_u64};
6use crate::v::zvexx::load::zvexx_load_helpers::mask_bit;
7use ab_riscv_primitives::prelude::*;
8
9/// Execute element-wise and-not over `vstart..vl`, writing SEW-wide results into `vd`.
10///
11/// For each active element i: `vd[i] = ~src[i] & vs2[i]`.
12///
13/// When `vm=true` all elements are active. When `vm=false` the mask register `v0` gates each
14/// element; masked-off elements are left undisturbed (undisturbed policy).
15///
16/// # Safety
17/// - `vd.to_bits() % group_regs == 0` and `vd.to_bits() + group_regs <= 32`
18/// - `vs2.to_bits() % group_regs == 0` and `vs2.to_bits() + group_regs <= 32`
19/// - `src` register (if `Vreg`) satisfies the same alignment as `vs2`
20/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
21#[inline(always)]
22#[doc(hidden)]
23#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
24pub unsafe fn execute_vandn<Reg, Env>(
25 env: &mut Env,
26 vd: VReg,
27 vs2: VReg,
28 src: OpSrc,
29 sew: Vsew,
30 vm: bool,
31) where
32 Reg: Register,
33 Env: VectorRegistersExt<Reg>,
34 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
35{
36 let vl = env.vl();
37 let vstart = env.vstart();
38 for i in vstart.range_to(vl) {
39 if !vm && !mask_bit(env.read_vregs().get(VReg::V0), i) {
40 continue;
41 }
42 // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32` (caller precondition);
43 // `i < vl <= group_regs * elems_per_reg`, so
44 // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
45 let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
46 let b = match src {
47 OpSrc::Vreg(vs1_base) => {
48 // SAFETY: caller verified that the vs1 register group satisfies the same alignment
49 // constraint as vs2; the index argument is identical, so the same bound holds
50 unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) }
51 }
52 OpSrc::Scalar(val) => val,
53 };
54 // `a` is zero-extended to SEW bits by `read_element_u64`; `!b` may have high bits set, but
55 // AND with `a` (whose upper bits are zero) zeros them out naturally
56 let result = !b & a;
57 // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32` (caller precondition);
58 // `i < vl <= group_regs * elems_per_reg`, so
59 // `vd + i / elems_per_reg < vd + group_regs <= 32`
60 unsafe {
61 write_element_u64(env.write_vregs(), vd, i, sew, result);
62 }
63 }
64 env.mark_vs_dirty();
65 env.reset_vstart();
66}
67
68/// Execute element-wise bit-reversal within bytes over `vstart..vl`, writing results into `vd`.
69///
70/// For each active element i: the bits within each byte of `vs2[i]` are reversed. The byte order
71/// within the element is preserved; only the bit order within each byte changes.
72///
73/// When `vm=false`, masked-off elements are left undisturbed.
74///
75/// # Safety
76/// Same register-group constraints as [`execute_vandn`], minus the `src` constraint.
77#[inline(always)]
78#[doc(hidden)]
79#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
80pub unsafe fn execute_vbrev8<Reg, Env>(env: &mut Env, vd: VReg, vs2: VReg, sew: Vsew, vm: bool)
81where
82 Reg: Register,
83 Env: VectorRegistersExt<Reg>,
84 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
85{
86 let vl = env.vl();
87 let vstart = env.vstart();
88 let sew_bytes = u32::from(sew.bytes_width());
89 for i in vstart.range_to(vl) {
90 if !vm && !mask_bit(env.read_vregs().get(VReg::V0), i) {
91 continue;
92 }
93 // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32`; `i < vl`
94 let elem = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
95 // Decompose into bytes (LE = index 0 is least-significant), reverse bits within each active
96 // byte, then reassemble; bytes beyond sew_bytes are already zero because `read_element_u64`
97 // zero-extends to u64
98 let mut bytes = elem.to_le_bytes();
99 for byte in &mut bytes[..sew_bytes as usize] {
100 *byte = byte.reverse_bits();
101 }
102 let result = u64::from_le_bytes(bytes);
103 // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32`; `i < vl`
104 unsafe {
105 write_element_u64(env.write_vregs(), vd, i, sew, result);
106 }
107 }
108 env.mark_vs_dirty();
109 env.reset_vstart();
110}
111
112/// Execute element-wise byte reversal over `vstart..vl`, writing results into `vd`.
113///
114/// For each active element i: the bytes within `vs2[i]` are reversed.
115///
116/// When `vm=false`, masked-off elements are left undisturbed.
117///
118/// # Safety
119/// Same register-group constraints as [`execute_vandn`], minus the `src` constraint.
120#[inline(always)]
121#[doc(hidden)]
122#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
123pub unsafe fn execute_vrev8<Reg, Env>(env: &mut Env, vd: VReg, vs2: VReg, sew: Vsew, vm: bool)
124where
125 Reg: Register,
126 Env: VectorRegistersExt<Reg>,
127 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
128{
129 let vl = env.vl();
130 let vstart = env.vstart();
131 let sew_bytes = u32::from(sew.bytes_width());
132 for i in vstart.range_to(vl) {
133 if !vm && !mask_bit(env.read_vregs().get(VReg::V0), i) {
134 continue;
135 }
136 // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32`; `i < vl`
137 let elem = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
138 // Reverse the byte slice covering exactly the SEW-wide element; bytes beyond sew_bytes are
139 // zero (from zero-extension) and are left untouched
140 let mut bytes = elem.to_le_bytes();
141 bytes[..sew_bytes as usize].reverse();
142 let result = u64::from_le_bytes(bytes);
143 // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32`; `i < vl`
144 unsafe {
145 write_element_u64(env.write_vregs(), vd, i, sew, result);
146 }
147 }
148 env.mark_vs_dirty();
149 env.reset_vstart();
150}
151
152/// Execute element-wise rotate-left over `vstart..vl`, writing SEW-wide results into `vd`.
153///
154/// For each active element i: `vd[i] = rotate_left(vs2[i], src[i] % SEW)`.
155///
156/// When `vm=false`, masked-off elements are left undisturbed.
157///
158/// # Safety
159/// Same register-group constraints as [`execute_vandn`].
160#[inline(always)]
161#[doc(hidden)]
162#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
163pub unsafe fn execute_vrol<Reg, Env>(
164 env: &mut Env,
165 vd: VReg,
166 vs2: VReg,
167 src: OpSrc,
168 sew: Vsew,
169 vm: bool,
170) where
171 Reg: Register,
172 Env: VectorRegistersExt<Reg>,
173 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
174{
175 let vl = env.vl();
176 let vstart = env.vstart();
177 let sew_bits = u64::from(sew.bits_width());
178 let mask = sew_mask(sew);
179 for i in vstart.range_to(vl) {
180 if !vm && !mask_bit(env.read_vregs().get(VReg::V0), i) {
181 continue;
182 }
183 // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32`; `i < vl`
184 let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
185 let amount = match src {
186 OpSrc::Vreg(vs1_base) => {
187 // SAFETY: same alignment constraint as vs2; same index bound
188 unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) }
189 }
190 OpSrc::Scalar(val) => val,
191 };
192 // `shift < sew_bits`, so `a << shift` never shifts by >= 64 and is safe.
193 // When shift == 0, `sew_bits - shift` == sew_bits; `unbounded_shr` defines
194 // shifts >= bit-width as 0, which is correct: a zero rotation contributes no low bits.
195 let shift = (amount % sew_bits) as u32;
196 let hi = (a << shift) & mask;
197 let lo = a.unbounded_shr(sew_bits as u32 - shift);
198 let result = hi | lo;
199 // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32`; `i < vl`
200 unsafe {
201 write_element_u64(env.write_vregs(), vd, i, sew, result);
202 }
203 }
204 env.mark_vs_dirty();
205 env.reset_vstart();
206}
207
208/// Execute element-wise rotate-right over `vstart..vl`, writing SEW-wide results into `vd`.
209///
210/// For each active element i: `vd[i] = rotate_right(vs2[i], src[i] % SEW)`.
211///
212/// Pass `vm=true` for `vror.vi` (bit[25] is consumed as imm[5]; no mask bit exists).
213///
214/// When `vm=false`, masked-off elements are left undisturbed.
215///
216/// # Safety
217/// Same register-group constraints as [`execute_vandn`].
218#[inline(always)]
219#[doc(hidden)]
220#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
221pub unsafe fn execute_vror<Reg, Env>(
222 env: &mut Env,
223 vd: VReg,
224 vs2: VReg,
225 src: OpSrc,
226 sew: Vsew,
227 vm: bool,
228) where
229 Reg: Register,
230 Env: VectorRegistersExt<Reg>,
231 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
232{
233 let vl = env.vl();
234 let vstart = env.vstart();
235 let sew_bits = u64::from(sew.bits_width());
236 let mask = sew_mask(sew);
237 for i in vstart.range_to(vl) {
238 if !vm && !mask_bit(env.read_vregs().get(VReg::V0), i) {
239 continue;
240 }
241 // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32`; `i < vl`
242 let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
243 let amount = match src {
244 OpSrc::Vreg(vs1_base) => {
245 // SAFETY: same alignment constraint as vs2; same index bound
246 unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) }
247 }
248 OpSrc::Scalar(val) => val,
249 };
250 // `shift < sew_bits`, so `a >> shift` never shifts by >= 64 and is safe.
251 // When shift == 0, `sew_bits - shift` == sew_bits; `unbounded_shl` defines
252 // shifts >= bit-width as 0, which is correct: a zero rotation contributes no high bits.
253 let shift = (amount % sew_bits) as u32;
254 let lo = a >> shift;
255 let hi = a.unbounded_shl(sew_bits as u32 - shift) & mask;
256 let result = lo | hi;
257 // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32`; `i < vl`
258 unsafe {
259 write_element_u64(env.write_vregs(), vd, i, sew, result);
260 }
261 }
262 env.mark_vs_dirty();
263 env.reset_vstart();
264}