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