Skip to main content

ab_riscv_interpreter/v/zvexx/
mask.rs

1//! ZveXx mask instructions
2
3#[cfg(test)]
4mod tests;
5pub mod zvexx_mask_helpers;
6
7use crate::v::vector_registers::VectorRegistersExt;
8use crate::v::zvexx::zvexx_helpers;
9use crate::{
10    ExecutableInstruction, ExecutableInstructionCsr, ExecutableInstructionOperands, ExecutionError,
11    ProgramCounter, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands, VirtualMemory,
12};
13use ab_riscv_macros::instruction_execution;
14use ab_riscv_primitives::prelude::*;
15use core::fmt;
16use core::ops::ControlFlow;
17
18#[instruction_execution]
19impl<Reg> ExecutableInstructionOperands for ZveXxMaskInstruction<Reg> where Reg: Register {}
20
21#[instruction_execution]
22impl<Reg, ExtState, CustomError> ExecutableInstructionCsr<ExtState, CustomError>
23    for ZveXxMaskInstruction<Reg>
24where
25    Reg: Register,
26{
27}
28
29#[instruction_execution]
30impl<Reg, Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
31    ExecutableInstruction<Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
32    for ZveXxMaskInstruction<Reg>
33where
34    Reg: Register,
35    Regs: RegisterFile<Reg>,
36    ExtState: VectorRegistersExt<Reg, CustomError>,
37    [(); ExtState::ELEN as usize]:,
38    [(); ExtState::VLEN as usize]:,
39    [(); ExtState::VLENB as usize]:,
40    Memory: VirtualMemory,
41    PC: ProgramCounter<Reg::Type, Memory, CustomError>,
42    CustomError: fmt::Debug,
43{
44    #[inline(always)]
45    fn execute(
46        self,
47        Rs1Rs2OperandValues {
48            rs1_value: _,
49            rs2_value: _,
50        }: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
51        _regs: &mut Regs,
52        ext_state: &mut ExtState,
53        _memory: &mut Memory,
54        program_counter: &mut PC,
55        _system_instruction_handler: &mut InstructionHandler,
56    ) -> Result<
57        ControlFlow<(), (Self::Reg, <Self::Reg as Register>::Type)>,
58        ExecutionError<Reg::Type, CustomError>,
59    > {
60        match self {
61            // Mask-register logical instructions (§16.1).
62            // These compute the body elements [vstart, vl); prestart bits [0, vstart) are
63            // undisturbed and the tail (past vl) is tail-agnostic (realised here as
64            // undisturbed). They still require vtype to be valid (vill=0); any vector
65            // instruction must be rejected when vill is set, regardless of whether it uses
66            // SEW or vl.
67            Self::Vmandn { vd, vs2, vs1 } => {
68                if !ext_state.vector_instructions_allowed() {
69                    ::core::hint::cold_path();
70                    return Err(ExecutionError::IllegalInstruction {
71                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
72                    });
73                }
74                if ext_state.vtype().is_none() {
75                    ::core::hint::cold_path();
76                    return Err(ExecutionError::IllegalInstruction {
77                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
78                    });
79                }
80                // SAFETY: all VReg values are valid indices < 32; `vl <= VLEN` and
81                // `vstart <= vl` are architectural invariants; snapshot-before-write inside
82                // the helper means vd may overlap vs2 or vs1 safely.
83                unsafe {
84                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
85                        a && !b
86                    });
87                }
88            }
89            Self::Vmand { vd, vs2, vs1 } => {
90                if !ext_state.vector_instructions_allowed() {
91                    ::core::hint::cold_path();
92                    return Err(ExecutionError::IllegalInstruction {
93                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
94                    });
95                }
96                if ext_state.vtype().is_none() {
97                    ::core::hint::cold_path();
98                    return Err(ExecutionError::IllegalInstruction {
99                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
100                    });
101                }
102                // SAFETY: see `Vmandn`
103                unsafe {
104                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
105                        a & b
106                    });
107                }
108            }
109            Self::Vmor { vd, vs2, vs1 } => {
110                if !ext_state.vector_instructions_allowed() {
111                    ::core::hint::cold_path();
112                    return Err(ExecutionError::IllegalInstruction {
113                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
114                    });
115                }
116                if ext_state.vtype().is_none() {
117                    ::core::hint::cold_path();
118                    return Err(ExecutionError::IllegalInstruction {
119                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
120                    });
121                }
122                // SAFETY: see `Vmandn`
123                unsafe {
124                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
125                        a | b
126                    });
127                }
128            }
129            Self::Vmxor { vd, vs2, vs1 } => {
130                if !ext_state.vector_instructions_allowed() {
131                    ::core::hint::cold_path();
132                    return Err(ExecutionError::IllegalInstruction {
133                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
134                    });
135                }
136                if ext_state.vtype().is_none() {
137                    ::core::hint::cold_path();
138                    return Err(ExecutionError::IllegalInstruction {
139                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
140                    });
141                }
142                // SAFETY: see `Vmandn`
143                unsafe {
144                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
145                        a ^ b
146                    });
147                }
148            }
149            Self::Vmorn { vd, vs2, vs1 } => {
150                if !ext_state.vector_instructions_allowed() {
151                    ::core::hint::cold_path();
152                    return Err(ExecutionError::IllegalInstruction {
153                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
154                    });
155                }
156                if ext_state.vtype().is_none() {
157                    ::core::hint::cold_path();
158                    return Err(ExecutionError::IllegalInstruction {
159                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
160                    });
161                }
162                // SAFETY: see `Vmandn`
163                unsafe {
164                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
165                        a || !b
166                    });
167                }
168            }
169            Self::Vmnand { vd, vs2, vs1 } => {
170                if !ext_state.vector_instructions_allowed() {
171                    ::core::hint::cold_path();
172                    return Err(ExecutionError::IllegalInstruction {
173                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
174                    });
175                }
176                if ext_state.vtype().is_none() {
177                    ::core::hint::cold_path();
178                    return Err(ExecutionError::IllegalInstruction {
179                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
180                    });
181                }
182                // SAFETY: see `Vmandn`
183                unsafe {
184                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
185                        !(a & b)
186                    });
187                }
188            }
189            Self::Vmnor { vd, vs2, vs1 } => {
190                if !ext_state.vector_instructions_allowed() {
191                    ::core::hint::cold_path();
192                    return Err(ExecutionError::IllegalInstruction {
193                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
194                    });
195                }
196                if ext_state.vtype().is_none() {
197                    ::core::hint::cold_path();
198                    return Err(ExecutionError::IllegalInstruction {
199                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
200                    });
201                }
202                // SAFETY: see `Vmandn`
203                unsafe {
204                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
205                        !(a | b)
206                    });
207                }
208            }
209            Self::Vmxnor { vd, vs2, vs1 } => {
210                if !ext_state.vector_instructions_allowed() {
211                    ::core::hint::cold_path();
212                    return Err(ExecutionError::IllegalInstruction {
213                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
214                    });
215                }
216                if ext_state.vtype().is_none() {
217                    ::core::hint::cold_path();
218                    return Err(ExecutionError::IllegalInstruction {
219                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
220                    });
221                }
222                // SAFETY: see `Vmandn`
223                unsafe {
224                    zvexx_mask_helpers::execute_mask_logical_op(ext_state, vd, vs2, vs1, |a, b| {
225                        !(a ^ b)
226                    });
227                }
228            }
229            // vcpop.m (§16.2): count set bits in vs2 over active elements, write to GPR rd.
230            Self::Vcpop { rd, vs2, vm } => {
231                if !ext_state.vector_instructions_allowed() {
232                    ::core::hint::cold_path();
233                    return Err(ExecutionError::IllegalInstruction {
234                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
235                    });
236                }
237                // vcpop/vfirst require a valid vtype to know vl, but do not use SEW.
238                if ext_state.vtype().is_none() {
239                    ::core::hint::cold_path();
240                    return Err(ExecutionError::IllegalInstruction {
241                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
242                    });
243                }
244                // SAFETY: `vl <= VLMAX <= VLEN`, so `vl.div_ceil(8) <= VLENB`; `vstart <= vl`
245                // by spec invariant.
246                let rd_value = unsafe { zvexx_mask_helpers::execute_vcpop(ext_state, vs2, vm) };
247
248                return Ok(ControlFlow::Continue((rd, rd_value)));
249            }
250            // vfirst.m (§16.3): find lowest-numbered active set bit in vs2, write index to rd.
251            Self::Vfirst { rd, vs2, vm } => {
252                if !ext_state.vector_instructions_allowed() {
253                    ::core::hint::cold_path();
254                    return Err(ExecutionError::IllegalInstruction {
255                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
256                    });
257                }
258                if ext_state.vtype().is_none() {
259                    ::core::hint::cold_path();
260                    return Err(ExecutionError::IllegalInstruction {
261                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
262                    });
263                }
264                // SAFETY: same as `Vcpop`
265                let rd_value = unsafe { zvexx_mask_helpers::execute_vfirst(ext_state, vs2, vm) };
266
267                return Ok(ControlFlow::Continue((rd, rd_value)));
268            }
269            // vmsbf.m (§16.4): set-before-first mask bit.
270            // Constraints: vd != vs2 (overlap illegal), vm=false implies vd != v0.
271            Self::Vmsbf { vd, vs2, vm } => {
272                if !ext_state.vector_instructions_allowed() {
273                    ::core::hint::cold_path();
274                    return Err(ExecutionError::IllegalInstruction {
275                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
276                    });
277                }
278                if ext_state.vtype().is_none() {
279                    ::core::hint::cold_path();
280                    return Err(ExecutionError::IllegalInstruction {
281                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
282                    });
283                }
284                // Spec §16.4: vmsbf/vmsif/vmsof with vstart != 0 raise an illegal instruction
285                // exception.
286                if ext_state.vstart() != 0 {
287                    ::core::hint::cold_path();
288                    return Err(ExecutionError::IllegalInstruction {
289                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
290                    });
291                }
292                // Per spec §16.4: vd must not overlap vs2
293                if vd == vs2 {
294                    ::core::hint::cold_path();
295                    return Err(ExecutionError::IllegalInstruction {
296                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
297                    });
298                }
299                if !vm && vd == VReg::V0 {
300                    ::core::hint::cold_path();
301                    return Err(ExecutionError::IllegalInstruction {
302                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
303                    });
304                }
305                let vl = ext_state.vl();
306                // SAFETY: `vd != vs2` checked above; `vd != v0` when masked checked above;
307                // `vstart == 0` checked above; `vl <= VLEN` so `vl.div_ceil(8) <= VLENB`.
308                unsafe {
309                    zvexx_mask_helpers::execute_vmsbf(ext_state, vd, vs2, vm, vl);
310                }
311            }
312            // vmsof.m (§16.5): set-only-first mask bit.
313            // Same overlap constraints as vmsbf.
314            Self::Vmsof { vd, vs2, vm } => {
315                if !ext_state.vector_instructions_allowed() {
316                    ::core::hint::cold_path();
317                    return Err(ExecutionError::IllegalInstruction {
318                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
319                    });
320                }
321                if ext_state.vtype().is_none() {
322                    ::core::hint::cold_path();
323                    return Err(ExecutionError::IllegalInstruction {
324                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
325                    });
326                }
327                // Spec §16.4: vmsbf/vmsif/vmsof with vstart != 0 raise an illegal instruction
328                // exception.
329                if ext_state.vstart() != 0 {
330                    ::core::hint::cold_path();
331                    return Err(ExecutionError::IllegalInstruction {
332                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
333                    });
334                }
335                if vd == vs2 {
336                    ::core::hint::cold_path();
337                    return Err(ExecutionError::IllegalInstruction {
338                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
339                    });
340                }
341                if !vm && vd == VReg::V0 {
342                    ::core::hint::cold_path();
343                    return Err(ExecutionError::IllegalInstruction {
344                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
345                    });
346                }
347                let vl = ext_state.vl();
348                // SAFETY: see `Vmsbf`
349                unsafe {
350                    zvexx_mask_helpers::execute_vmsof(ext_state, vd, vs2, vm, vl);
351                }
352            }
353            // vmsif.m (§16.6): set-including-first mask bit.
354            // Same overlap constraints as vmsbf.
355            Self::Vmsif { vd, vs2, vm } => {
356                if !ext_state.vector_instructions_allowed() {
357                    ::core::hint::cold_path();
358                    return Err(ExecutionError::IllegalInstruction {
359                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
360                    });
361                }
362                if ext_state.vtype().is_none() {
363                    ::core::hint::cold_path();
364                    return Err(ExecutionError::IllegalInstruction {
365                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
366                    });
367                }
368                // Spec §16.4: vmsbf/vmsif/vmsof with vstart != 0 raise an illegal instruction
369                // exception.
370                if ext_state.vstart() != 0 {
371                    ::core::hint::cold_path();
372                    return Err(ExecutionError::IllegalInstruction {
373                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
374                    });
375                }
376                if vd == vs2 {
377                    ::core::hint::cold_path();
378                    return Err(ExecutionError::IllegalInstruction {
379                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
380                    });
381                }
382                if !vm && vd == VReg::V0 {
383                    ::core::hint::cold_path();
384                    return Err(ExecutionError::IllegalInstruction {
385                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
386                    });
387                }
388                let vl = ext_state.vl();
389                // SAFETY: see `Vmsbf`
390                unsafe {
391                    zvexx_mask_helpers::execute_vmsif(ext_state, vd, vs2, vm, vl);
392                }
393            }
394            // viota.m (§16.8): write prefix popcount of vs2 bits as SEW-wide elements into vd.
395            // Constraints: vd must not overlap vs2 or v0 (when masked); vd alignment per LMUL;
396            // vstart must be zero (mandatory trap per spec §16.8). There is no SEW-width
397            // constraint: if SEW is too narrow to hold the prefix count the result simply wraps
398            // (truncates to SEW), matching the spec's "integer operations wrap around on overflow"
399            // rule rather than raising an exception.
400            Self::Viota { vd, vs2, vm } => {
401                if !ext_state.vector_instructions_allowed() {
402                    ::core::hint::cold_path();
403                    return Err(ExecutionError::IllegalInstruction {
404                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
405                    });
406                }
407                let Some(vtype) = ext_state.vtype() else {
408                    ::core::hint::cold_path();
409                    return Err(ExecutionError::IllegalInstruction {
410                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
411                    });
412                };
413                // Spec §16.8: viota.m with vstart != 0 raises an illegal instruction exception.
414                if ext_state.vstart() != 0 {
415                    ::core::hint::cold_path();
416                    return Err(ExecutionError::IllegalInstruction {
417                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
418                    });
419                }
420                let group_regs = vtype.vlmul().register_count();
421                let vd_idx = vd.to_bits();
422                if !vd_idx.is_multiple_of(group_regs) || vd_idx + group_regs > 32 {
423                    ::core::hint::cold_path();
424                    return Err(ExecutionError::IllegalInstruction {
425                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
426                    });
427                }
428                // vd must not overlap vs2; vs2 is always a single mask register (group size 1).
429                let vd_start = u32::from(vd.to_bits());
430                let vs2_start = u32::from(vs2.to_bits());
431                if vd_start < vs2_start + 1 && vs2_start < vd_start + u32::from(group_regs) {
432                    ::core::hint::cold_path();
433                    return Err(ExecutionError::IllegalInstruction {
434                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
435                    });
436                }
437                if !vm && vd == VReg::V0 {
438                    ::core::hint::cold_path();
439                    return Err(ExecutionError::IllegalInstruction {
440                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
441                    });
442                }
443                let sew = vtype.vsew();
444                let vl = ext_state.vl();
445                // SAFETY: vd alignment checked above; vd group does not overlap vs2 checked above;
446                // `vm=false` implies `vd != v0` checked above; vstart == 0 checked above;
447                // `vl <= VLMAX = group_regs * VLENB / sew_bytes`, all element indices valid.
448                unsafe {
449                    zvexx_mask_helpers::execute_viota(ext_state, vd, vs2, vm, vl, sew);
450                }
451            }
452            // vid.v (§16.9): write element index i as SEW-wide integer into vd[i].
453            // Constraints: vm=false implies vd != v0; vd alignment per LMUL.
454            Self::Vid { vd, vm } => {
455                if !ext_state.vector_instructions_allowed() {
456                    ::core::hint::cold_path();
457                    return Err(ExecutionError::IllegalInstruction {
458                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
459                    });
460                }
461                let Some(vtype) = ext_state.vtype() else {
462                    ::core::hint::cold_path();
463                    return Err(ExecutionError::IllegalInstruction {
464                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
465                    });
466                };
467                let group_regs = vtype.vlmul().register_count();
468                let vd_idx = vd.to_bits();
469                if !vd_idx.is_multiple_of(group_regs) || vd_idx + group_regs > 32 {
470                    ::core::hint::cold_path();
471                    return Err(ExecutionError::IllegalInstruction {
472                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
473                    });
474                }
475                if !vm && vd == VReg::V0 {
476                    ::core::hint::cold_path();
477                    return Err(ExecutionError::IllegalInstruction {
478                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
479                    });
480                }
481                let sew = vtype.vsew();
482                // SAFETY: vd alignment checked above; `vm=false` implies `vd != v0` checked above;
483                // `vl <= VLMAX = group_regs * VLENB / sew_bytes`, all element indices valid.
484                unsafe {
485                    zvexx_mask_helpers::execute_vid(ext_state, vd, vm, sew);
486                }
487            }
488        }
489
490        Ok(ControlFlow::Continue(Default::default()))
491    }
492}