Re: Basic support for AVX512 mask registers

arthur miller <[email protected]>
Newsgroups gmane.lisp.steel-bank.devel
Message-ID <VI1PR09MB2496AECA9A858D25C6F4943A96A32@VI1PR09MB2496.eurprd09.prod.outlook.com>
A patch and a question about CI:

I have pushed a small patch to evex encoder, and I see some CI builds
fail. When I looked closer it does not look like they should have anything
to do with my patch. I also run all tests, not just avx related ones, and
they all pass on my computer. If these failures can be ignored the patch,
is attached, otherwise ignore the patch :).


________________________________
Från: Stas Boukarev <[email protected]>
Skickat: den 20 augusti 2026 09:55
Till: arthur miller <[email protected]>
Kopia: sbcl-devel <[email protected]>
Ämne: Re: Basic support for AVX512 mask registers

Applied. Thanks.

On Thu, Aug 20, 2026 at 4:57 AM arthur miller <[email protected]> wrote:
>
> Hi, I have been trying to add support for avx512 mask registers.
>
> The worktree: https://github.com/amno1/sbcl/tree/avx512-mask-regs
>
> I have also attached a patch if you prefer it over GH. A short glance
> over what is in the patch:
>
> * simd-pack-512-mask as intrinsic type (widetag)
> * associated book-keeping in VM and compiler for it
> * VM support for mask registers (mask-reg SC, SB, defregs, ...)
> * VOPs to compiler backend for construction, extraction
> * VOPs for movement: kregs<->kregs, kregs<->gpr and kregs<->mem
> * support for assembler in insts, avx2-insts and avx512-insts
> * fix evex emitter regarding mask registers
> * add evex printer support for mask related instructions
> * fix some smaller bugs in previous simd-pack-512 support patch
> * add tests for creation, extraction, movements, assembly printing and some internal functions
> * refactor zmm-registers-used-p into avx512-state-used-p and  support for mask regs
>
> Instructions regarding mask regs can be emitted and printed but no
> VOP ntrinsics other than to create, read and move masks are added (job for sb-simd).
>
> There is still more work on evex encodings left: r', v', b' for
> zmm16-zmm31 and on decoding. I'll work on it more, but for now, I think
> this is the minimum needed. I am sure there are bugs, you don't have to
> chase them, but appreciate if you do. However, I hope you can glance
> over it, see if it is OK. I hope I haven't messed up too much 🙂. CI on GH is green for
> all builds: https://github.com/amno1/sbcl/actions, but please check it first.

_______________________________________________
Sbcl-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sbcl-devel
0001-EVEX-add-V-and-improve-5-bit-register-decoding.patch (text/x-patch, 10.6 KB)
From e1cad12f6e81d0ded05f4826fedcda5af23ff993 Mon Sep 17 00:00:00 2001
From: arthur <[email protected]>
Date: Fri, 21 Aug 2026 01:37:09 +0200
Subject: [PATCH] EVEX: add V' and improve 5-bit register decoding
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add +evex+ indicator to distinguish EVEX from VEX/legacy
  even for EVEX L'L=00 forms
* Decode ModRM.reg registers 16–31 using R'
* Stop incorrectly applying R' to ModRM.r/m operands
* Decode EVEX ModRM.r/m register-direct 16–31 using X as B'
* Decode EVEX vvvv registers 16–31 using V'
* Add test for this patch in simd-pack-512.pure.lisp
---
 src/compiler/x86-64/avx2-insts.lisp        | 30 +++++++++++++++--
 src/compiler/x86-64/avx512-insts.lisp      | 28 +++++++++++-----
 src/compiler/x86-64/target-avx2-insts.lisp | 34 ++++++++++++++++++-
 src/compiler/x86-64/target-insts.lisp      |  6 +++-
 tests/simd-pack-512.pure.lisp              | 38 ++++++++++++++++++++++
 5 files changed, 122 insertions(+), 14 deletions(-)

diff --git a/src/compiler/x86-64/avx2-insts.lisp b/src/compiler/x86-64/avx2-insts.lisp
index d07fe886b..555d9a4e8 100644
--- a/src/compiler/x86-64/avx2-insts.lisp
+++ b/src/compiler/x86-64/avx2-insts.lisp
@@ -47,6 +47,12 @@
   :prefilter #'prefilter-xmmreg/mem
   :printer #'print-sized-xmmreg/mem-default-qword)
 
+;;; General indicator that we are decoding an EVEX instruction.
+;;; EVEX has 128-, 256-, and 512-bit forms; L'L=00 is 128-bit and sets
+;;; neither +vex-l+ nor +evex-l1+, so this separate bit is required for
+;;; code that needs to know "EVEX vs VEX/legacy".
+;;; (Bit 14 is currently unused by other dstate properties.)
+(defconstant +evex+ #b100000000000000)
 (defconstant +vex-l+ #b10000000000)
 ;; EVEX L'L=10 (512-bit) sets bit 11; L'L=01 (256-bit) sets bit 10 (=+vex-l+)
 (defconstant +evex-l1+ #b100000000000)
@@ -54,6 +60,10 @@
 (defconstant +evex-r-prime+ #b1000000000000)
 ;; EVEX X bit as B' (r/m bit 4, for registers 16-31 in ModR/M.r/m, reg-direct only)
 (defconstant +evex-b-prime+ #b10000000000000)
+;;; EVEX V' bit (vvvv bit 4, for registers 16-31 in the vvvv field).
+;;; The 4-bit vvvv field in the EVEX prefix is extended with this bit.
+;;; (Bit 15 is currently unused by other dstate properties.)
+(defconstant +evex-v-prime+ #b1000000000000000)
 
 (define-arg-type vex-l
   :prefilter  (lambda (dstate value)
@@ -95,13 +105,21 @@
   :prefilter (lambda (dstate value)
                (dstate-setprop dstate (if (zerop value) +evex-r-prime+ 0))))
 
+;;; Marks that the instruction being decoded uses an EVEX prefix.
+;;; This is set by the EVEX fixed-bit field and is independent of L'L.
+(define-arg-type evex-fixed
+  :prefilter (lambda (dstate value)
+               (declare (ignore value))
+               (dstate-setprop dstate +evex+)))
+
 ;; EVEX V' extends vvvv bit 4 (inverted in prefix)
 ;; V'=0 means bit4=1 (register 16-31 in vvvv)
 ;; Note: the printer for vvvv (print-ymmreg via ymm-vvvv-reg) gets
 ;; a 4-bit value from the invert-4 prefilter. V' provides the 5th bit.
 (define-arg-type evex-v-prime
   :prefilter (lambda (dstate value)
-               (declare (ignore dstate value))))
+               (dstate-setprop dstate
+                               (if (zerop value) +evex-v-prime+ 0))))
 
 ;; EVEX L'L: 2-bit vector length (00=128, 01=256, 10=512)
 ;; Stores into dstate bits 10-11: L'L=01 sets bit 10 (+vex-l+),
@@ -115,6 +133,12 @@
   :prefilter (lambda (dstate value)
                (dstate-setprop dstate (if (plusp value) +rex-w+ 0))))
 
+;;; EVEX vvvv register operand.
+;;; The 4-bit value is inverted and extended with V' by the printer.
+(define-arg-type evex-ymm-vvvv-reg
+  :prefilter #'invert-4
+  :printer #'print-ymmreg-vvvv)
+
 ;; Opmask register k0-k7
 (define-arg-type opmask-reg
   :printer #'print-opmask-register)
@@ -293,8 +317,8 @@
   (mm       :field (byte 2 8))
   ;; Byte 2
   (w          :field (byte 1 23) :type 'evex-w)
-  (vvvv       :field (byte 4 19) :type 'ymm-vvvv-reg)
-  (evex-fixed :field (byte 1 18) :value 1) ; must be 1 for EVEX
+  (vvvv       :field (byte 4 19) :type 'evex-ymm-vvvv-reg)
+  (evex-fixed :field (byte 1 18) :value 1 :type 'evex-fixed) ; must be 1 for EVEX
   (pp         :field (byte 2 16))
   ;; Byte 3
   (z-bit   :field (byte 1 31))
diff --git a/src/compiler/x86-64/avx512-insts.lisp b/src/compiler/x86-64/avx512-insts.lisp
index fe5ca1ddb..a5965dd26 100644
--- a/src/compiler/x86-64/avx512-insts.lisp
+++ b/src/compiler/x86-64/avx512-insts.lisp
@@ -1171,15 +1171,25 @@
 ;;; mask-reg-number is 1-7 (k1-k7; k0 means no masking).
 ;;; Merge-masking: destination elements not selected by mask are preserved.
 (macrolet ((def (name prefix opcode w &optional (opcode-prefix #x0f))
-             `(define-instruction ,name (segment dst src1 src2 mask)
-                ,@(avx512-inst-printer-list 'ymm-ymm/mem prefix opcode
-                                            :opcode-prefix opcode-prefix :w w :nds t)
-                (:emitter
-                 (emit-avx512-inst segment src2 dst ,prefix ,opcode
-                                   :opcode-prefix ,opcode-prefix
-                                   :vvvv src1
-                                   :w ,w
-                                   :aaa mask)))))
+             (let ((mask-printer
+                    '(:name :tab reg ", " vvvv ", " reg/mem
+                      " {" aaa "}")))
+               `(define-instruction ,name (segment dst src1 src2 mask)
+                  ,@(loop for k from 1 to 7
+                          append
+                          (avx512-inst-printer-list
+                           'ymm-ymm/mem prefix opcode
+                           :opcode-prefix opcode-prefix
+                           :w w
+                           :nds t
+                           :more-fields `((aaa ,k))
+                           :printer mask-printer))
+                  (:emitter
+                   (emit-avx512-inst segment src2 dst ,prefix ,opcode
+                                     :opcode-prefix ,opcode-prefix
+                                     :vvvv src1
+                                     :w ,w
+                                     :aaa mask))))))
   ;; Integer arithmetic (qword)
   (def vpaddq-masked  #x66 #xd4 1)
   (def vpsubq-masked  #x66 #xfb 1)
diff --git a/src/compiler/x86-64/target-avx2-insts.lisp b/src/compiler/x86-64/target-avx2-insts.lisp
index 2f8d1e568..0cee9c1aa 100644
--- a/src/compiler/x86-64/target-avx2-insts.lisp
+++ b/src/compiler/x86-64/target-avx2-insts.lisp
@@ -11,6 +11,38 @@
 
 (in-package "SB-X86-64-ASM")
 
+;;; Printer for EVEX/VEX ModRM.r/m register operands.
+;;; Does NOT use EVEX R' — R' belongs only to the ModRM.reg field.
+(defun print-ymmreg-rm (value stream dstate)
+  (let* ((offset (etypecase value
+                   ((unsigned-byte 4) value)
+                   (reg (reg-num value))))
+         (reg (get-fpr (cond ((dstate-getprop dstate +evex-l1+) :zmm)
+                             ((dstate-getprop dstate +vex-l+) :ymm)
+                             (t :xmm))
+                       offset))
+         (name (reg-name reg)))
+    (if stream
+        (write-string name stream)
+        (operand name dstate))))
+
+;;; Uses EVEX V' to form a 5-bit register number.
+(defun print-ymmreg-vvvv (value stream dstate)
+  (let* ((offset (etypecase value
+                   ((unsigned-byte 4) value)
+                   (reg (reg-num value))))
+         (offset (if (dstate-getprop dstate +evex-v-prime+)
+                     (+ offset 16)
+                     offset))
+         (reg (get-fpr (cond ((dstate-getprop dstate +evex-l1+) :zmm)
+                             ((dstate-getprop dstate +vex-l+) :ymm)
+                             (t :xmm))
+                       offset))
+         (name (reg-name reg)))
+    (if stream
+        (write-string name stream)
+        (operand name dstate))))
+
 (defun print-ymmreg (value stream dstate)
   (let* ((offset (etypecase value
                    ((unsigned-byte 4) value)
@@ -47,7 +79,7 @@
 (defun print-ymmreg/mem (value stream dstate)
   (if (machine-ea-p value)
       (print-mem-ref :ref value nil stream dstate)
-      (print-ymmreg value stream dstate)))
+      (print-ymmreg-rm value stream dstate)))
 
 (defun invert-4 (dstate value)
   (declare (ignore dstate))
diff --git a/src/compiler/x86-64/target-insts.lisp b/src/compiler/x86-64/target-insts.lisp
index 1a7fda132..d2caed762 100644
--- a/src/compiler/x86-64/target-insts.lisp
+++ b/src/compiler/x86-64/target-insts.lisp
@@ -365,7 +365,11 @@
       (cond ((= mod #b11) ; register direct mode
              (case regclass
               (gpr (get-gpr :qword full-reg)) ; size is not really known here
-              (fpr (get-fpr :xmm full-reg))))
+              (fpr (get-fpr :xmm
+              (if (and (dstate-getprop dstate +evex+)
+                       (dstate-getprop dstate +rex-x+))
+                  (+ full-reg 16)
+                  full-reg)))))
             ((= r/m #b100) ; SIB byte - rex.b is "don't care"
              (let* ((sib (the (unsigned-byte 8) (read-suffix 8 dstate)))
                     (index-reg (extend +rex-x+ (ldb (byte 3 3) sib)))
diff --git a/tests/simd-pack-512.pure.lisp b/tests/simd-pack-512.pure.lisp
index e6cb3ddea..932fac407 100644
--- a/tests/simd-pack-512.pure.lisp
+++ b/tests/simd-pack-512.pure.lisp
@@ -284,3 +284,41 @@
                    1d0 0f0 0f0 0f0 0f0 0f0 0f0 0f0
                    0f0 0f0 0f0 0f0 0f0 0f0 0f0 0f0)
                   type-error)))
+
+;; evex patch
+(cl:in-package "SB-VM")
+
+(sb-c::defknown %test-evex-high-regs ()
+    (unsigned-byte 64)
+  (sb-c::flushable sb-c::movable))
+
+(defun %test-evex-high-regs ()
+  (error "%test-evex-high-regs stub"))
+
+(define-vop (%test-evex-high-regs)
+  (:translate %test-evex-high-regs)
+  (:policy :fast-safe)
+  (:temporary (:sc single-avx512-reg :offset 16) z16)
+  (:temporary (:sc single-avx512-reg :offset 17) z17)
+  (:temporary (:sc single-avx512-reg :offset 18) z18)
+  (:results (res :scs (unsigned-reg)))
+  (:result-types unsigned-num)
+  (:generator 1
+    (inst vaddps z16 z17 z18)
+    (inst xor :dword res res)))
+
+(cl:in-package :test-util)
+
+(with-test (:name :evex-high-register-disassembly)
+  (let* ((fun (compile nil
+                       '(lambda ()
+                          (sb-vm::%test-evex-high-regs))))
+         (text (with-output-to-string (s)
+                 (disassemble fun :stream s))))
+    ;; These names can only appear if the disassembler correctly
+    ;; handles EVEX R', V', and X-as-B'.
+    (assert (search "ZMM16" text))
+    (assert (search "ZMM17" text))
+    (assert (search "ZMM18" text))
+    ;; Ideally we see a decoded instruction, not raw EVEX bytes.
+    (assert (search "VADDPS" text))))
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.