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