;;;; a bunch of handy macros for x86-64

;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.

(in-package "SB!VM")

;;;; instruction-like macros

;;; This used to be a macro (and still is on the other platforms) but
;;; the support for SC-dependent move instructions needed here makes
;;; that expand into so large an expression that the resulting code
;;; bloat is not justifiable.
(defun move (dst src)
  "Move SRC into DST unless they are location=."
  (unless (location= dst src)
    (sc-case dst
      ((single-reg complex-single-reg)
       (aver (xmm-register-p src))
       (inst movaps dst src))
      ((double-reg complex-double-reg)
       (aver (xmm-register-p src))
       (inst movapd dst src))
      #!+sb-simd-pack
      ((int-sse-reg sse-reg)
       (aver (xmm-register-p src))
       (inst movdqa dst src))
      #!+sb-simd-pack
      ((single-sse-reg double-sse-reg)
       (aver (xmm-register-p src))
       (inst movaps dst src))
      (t
       (inst mov dst src)))))

(defmacro make-ea-for-object-slot (ptr slot lowtag)
  `(make-ea :qword :base ,ptr :disp (- (* ,slot n-word-bytes) ,lowtag)))
(defmacro make-ea-for-object-slot-half (ptr slot lowtag)
  `(make-ea :dword :base ,ptr :disp (- (* ,slot n-word-bytes) ,lowtag)))
(defmacro tls-index-of (sym)
  `(make-ea :dword :base ,sym :disp (+ 4 (- other-pointer-lowtag))))

(defmacro loadw (value ptr &optional (slot 0) (lowtag 0))
  `(inst mov ,value (make-ea-for-object-slot ,ptr ,slot ,lowtag)))

(defun storew (value ptr &optional (slot 0) (lowtag 0))
  (cond ((and (integerp value)
              (not (typep value '(signed-byte 32))))
         (inst mov temp-reg-tn value)
         (inst mov (make-ea-for-object-slot ptr slot lowtag) temp-reg-tn))
        (t
         (inst mov (make-ea-for-object-slot ptr slot lowtag) value))))

(defmacro pushw (ptr &optional (slot 0) (lowtag 0))
  `(inst push (make-ea-for-object-slot ,ptr ,slot ,lowtag)))

(defmacro popw (ptr &optional (slot 0) (lowtag 0))
  `(inst pop (make-ea-for-object-slot ,ptr ,slot ,lowtag)))

(defun call-indirect (offset)
  (typecase offset
    ((signed-byte 32)
     (inst call (make-ea :qword :disp offset)))
    (t
     (inst mov temp-reg-tn offset)
     (inst call (make-ea :qword :base temp-reg-tn)))))

;;;; macros to generate useful values

(defmacro load-symbol (reg symbol)
  `(inst mov ,reg (+ nil-value (static-symbol-offset ,symbol))))

(defmacro make-ea-for-symbol-value (symbol)
  `(make-ea :qword
    :disp (+ nil-value
           (static-symbol-offset ',symbol)
           (ash symbol-value-slot word-shift)
           (- other-pointer-lowtag))))

(defmacro load-symbol-value (reg symbol)
  `(inst mov ,reg (make-ea-for-symbol-value ,symbol)))

(defmacro store-symbol-value (reg symbol)
  `(inst mov (make-ea-for-symbol-value ,symbol) ,reg))

;; Return the effective address of the value slot of static SYMBOL.
(defun static-symbol-value-ea (symbol)
   (make-ea :qword
            :disp (+ nil-value
                     (static-symbol-offset symbol)
                     (ash symbol-value-slot word-shift)
                     (- other-pointer-lowtag))))

#!+sb-thread
(progn
  ;; Return an EA for the TLS of SYMBOL, or die.
  (defun symbol-known-tls-cell (symbol)
    (let ((index (info :variable :wired-tls symbol)))
      (aver (integerp index))
      (make-ea :qword :base thread-base-tn :disp index)))

  ;; LOAD/STORE-TL-SYMBOL-VALUE macros are ad-hoc (ugly) emulations
  ;; of (INFO :VARIABLE :WIRED-TLS) = :ALWAYS-THREAD-LOCAL
  (defmacro load-tl-symbol-value (reg symbol)
    `(inst mov ,reg (symbol-known-tls-cell ',symbol)))

  (defmacro store-tl-symbol-value (reg symbol)
    `(inst mov (symbol-known-tls-cell ',symbol) ,reg)))

#!-sb-thread
(progn
  (defmacro load-tl-symbol-value (reg symbol)
    `(load-symbol-value ,reg ,symbol))
  (defmacro store-tl-symbol-value (reg symbol)
    `(store-symbol-value ,reg ,symbol)))

(defmacro load-binding-stack-pointer (reg)
  #!+sb-thread `(inst mov ,reg (symbol-known-tls-cell '*binding-stack-pointer*))
  #!-sb-thread `(load-symbol-value ,reg *binding-stack-pointer*))

(defmacro store-binding-stack-pointer (reg)
  #!+sb-thread `(inst mov (symbol-known-tls-cell '*binding-stack-pointer*) ,reg)
  #!-sb-thread `(store-symbol-value ,reg *binding-stack-pointer*))

(defmacro load-type (target source &optional (offset 0))
  "Loads the type bits of a pointer into target independent of
   byte-ordering issues."
  (once-only ((n-target target)
              (n-source source)
              (n-offset offset))
    (ecase *backend-byte-order*
      (:little-endian
       `(inst movzx ,n-target
              (make-ea :byte :base ,n-source :disp ,n-offset)))
      (:big-endian
       `(inst movzx ,n-target
              (make-ea :byte :base ,n-source
                             :disp (+ ,n-offset (1- n-word-bytes))))))))

;;;; allocation helpers

;;; All allocation is done by calls to assembler routines that
;;; eventually invoke the C alloc() function.

;;; Emit code to allocate an object with a size in bytes given by
;;; Size. The size may be an integer of a TN. If Inline is a VOP
;;; node-var then it is used to make an appropriate speed vs size
;;; decision.

(defun allocation-dynamic-extent (alloc-tn size lowtag)
  (aver (not (location= alloc-tn rsp-tn)))
  (inst sub rsp-tn size)
  ;; see comment in x86/macros.lisp implementation of this
  ;; However that comment seems inapplicable here because:
  ;; - PAD-DATA-BLOCK quite clearly enforces double-word alignment,
  ;;   contradicting "... unfortunately not enforced by ..."
  ;; - It's not the job of WITH-FIXED-ALLOCATION to realign anything.
  ;; - The real issue is that it's not obvious that the stack is
  ;;   16-byte-aligned at *all* times. Maybe it is, maybe it isn't.
  (inst and rsp-tn #.(lognot lowtag-mask))
  (inst lea alloc-tn (make-ea :byte :base rsp-tn :disp lowtag))
  (values))

;;; This macro should only be used inside a pseudo-atomic section,
;;; which should also cover subsequent initialization of the
;;; object.
(defun allocation-tramp (result-tn size lowtag)
  (cond ((typep size '(and integer (not (signed-byte 32))))
         ;; MOV accepts large immediate operands, PUSH does not
         (inst mov result-tn size)
         (inst push result-tn))
        (t
         (inst push size)))
  ;; This really would be better if it recognized TEMP-REG-TN as the "good" case
  ;; rather than specializing on R11, which just happens to be the temp reg.
  ;; But the assembly routine is hand-written, not generated, and it has to match,
  ;; so there's not much that can be done to generalize it.
  (let* ((to-r11 (location= result-tn r11-tn))
         (fixup (make-fixup (if to-r11
                                'alloc-tramp-r11
                                'alloc-tramp) :assembly-routine)))
    (inst call (cond #!+immobile-code
                     (sb!c::*code-is-immobile* fixup)
                     (t (inst mov result-tn fixup)
                        result-tn)))
    (unless to-r11
      (inst pop result-tn)))

  (when lowtag
    (inst or (reg-in-size result-tn :byte) lowtag))
  (values))

(defun allocation (alloc-tn size &optional ignored dynamic-extent lowtag)
  (declare (ignore ignored))
  (when dynamic-extent
    (allocation-dynamic-extent alloc-tn size lowtag)
    (return-from allocation (values)))
  (aver (and (not (location= alloc-tn temp-reg-tn))
             (or (integerp size) (not (location= size temp-reg-tn)))))

  #!+(and (not sb-thread) sb-dynamic-core)
  ;; We'd need a spare reg in which to load boxed_region from the linkage table.
  ;; Probably could use r12 since it's reserved for the thread base,
  ;; but that seems like the wrong answer, because r12 should be available
  ;; to the register allocator when not using it as the thread base.
  ;; Could push/pop any random register on the stack and own it temporarily,
  ;; but seeing as nobody cared about this, just punt.
  (allocation-tramp alloc-tn size lowtag)

  #!-(and (not sb-thread) sb-dynamic-core)
  ;; Otherwise do the normal inline allocation thing
  (let ((NOT-INLINE (gen-label))
        (DONE (gen-label))
        ;; Yuck.
        (in-elsewhere (eq *elsewhere* sb!assem::**current-segment**))
        ;; thread->alloc_region.free_pointer
        (free-pointer
         #!+sb-thread
         (make-ea :qword :base thread-base-tn
                  :disp (* n-word-bytes thread-alloc-region-slot))
         #!-sb-thread
         (make-ea :qword :disp (make-fixup "gc_alloc_region" :foreign)))
        ;; thread->alloc_region.end_addr
        (end-addr
         #!+sb-thread
         (make-ea :qword :base thread-base-tn
                  :disp (* n-word-bytes (1+ thread-alloc-region-slot)))
         #!-sb-thread
         (make-ea :qword :disp (make-fixup "gc_alloc_region" :foreign 8))))
    (cond ((or in-elsewhere
               ;; large objects will never be made in a per-thread region
               (and (integerp size)
                    (>= size large-object-size)))
           (allocation-tramp alloc-tn size lowtag))
          (t
           (inst mov temp-reg-tn free-pointer)
           (cond ((integerp size)
                  (inst lea alloc-tn (make-ea :qword :base temp-reg-tn :disp size)))
                 ((location= alloc-tn size)
                  (inst add alloc-tn temp-reg-tn))
                 (t
                  (inst lea alloc-tn (make-ea :qword :base temp-reg-tn :index size))))
           (inst cmp alloc-tn end-addr)
           (inst jmp :a NOT-INLINE)
           (inst mov free-pointer alloc-tn)
           (emit-label DONE)
           (if lowtag
               (inst lea alloc-tn (make-ea :byte :base temp-reg-tn :disp lowtag))
               (inst mov alloc-tn temp-reg-tn))
           (assemble (*elsewhere*)
             (emit-label NOT-INLINE)
             (cond ((numberp size)
                    (allocation-tramp temp-reg-tn size nil))
                   (t
                    (inst sub alloc-tn free-pointer)
                    (allocation-tramp temp-reg-tn alloc-tn nil)))
             (inst jmp DONE))))
    (values)))

;;; Allocate an other-pointer object of fixed SIZE with a single word
;;; header having the specified WIDETAG value. The result is placed in
;;; RESULT-TN.
(defmacro with-fixed-allocation ((result-tn widetag size &optional inline stack-allocate-p)
                                 &body forms)
  (unless forms
    (bug "empty &body in WITH-FIXED-ALLOCATION"))
  (once-only ((result-tn result-tn) (size size) (stack-allocate-p stack-allocate-p))
    `(maybe-pseudo-atomic ,stack-allocate-p
      (allocation ,result-tn (pad-data-block ,size) ,inline ,stack-allocate-p
                  other-pointer-lowtag)
      (storew* (logior (ash (1- ,size) n-widetag-bits) ,widetag)
               ,result-tn 0 other-pointer-lowtag
               (not ,stack-allocate-p))
      ,@forms)))

;;;; error code
(defun emit-error-break (vop kind code values)
  (assemble ()
    #!-ud2-breakpoints
    (inst int #!+int4-breakpoints 4 #!-int4-breakpoints 3)
    ;; On Darwin, we need to use #x0b0f instead of int3 in order
    ;; to generate a SIGILL instead of a SIGTRAP as darwin/x86
    ;; doesn't seem to be reliably firing SIGTRAP
    ;; handlers. Hopefully this will be fixed by Apple at a
    ;; later date. Do the same on x86-64 as we do on x86 until this gets
    ;; sorted out.
    #!+ud2-breakpoints
    (inst word #x0b0f)
    ;; The return PC points here; note the location for the debugger.
    (when vop
      (note-this-location vop :internal-error))
    (inst byte kind)                       ; eg trap_Xyyy
    (case kind
      (#.invalid-arg-count-trap) ; there is no "payload" in this trap kind
      (t
       (inst byte code)
       (encode-internal-error-args values)))))

(defun generate-error-code (vop error-code &rest values)
  (apply #'generate-error-code+ nil vop error-code values))

(defun generate-error-code+ (preamble-emitter vop error-code &rest values)
  "Generate-Error-Code Error-code Value*
  Emit code for an error with the specified Error-Code and context Values."
  (assemble (*elsewhere*)
    (let ((start-lab (gen-label)))
      (emit-label start-lab)
      (when preamble-emitter
        (funcall preamble-emitter))
      (emit-error-break vop
                        (case error-code ; should be named ERROR-SYMBOL really
                          (invalid-arg-count-error invalid-arg-count-trap)
                          (t error-trap))
                        (error-number-or-lose error-code)
                        values)
      start-lab)))


;;;; PSEUDO-ATOMIC

;;; This is used to wrap operations which leave untagged memory lying
;;; around.  It's an operation which the AOP weenies would describe as
;;; having "cross-cutting concerns", meaning it appears all over the
;;; place and there's no logical single place to attach documentation.
;;; grep (mostly in src/runtime) is your friend

(defmacro maybe-pseudo-atomic (not-really-p &body body)
  `(if ,not-really-p
       (progn ,@body)
       (pseudo-atomic ,@body)))

;;; Unsafely clear pa flags so that the image can properly lose in a
;;; pa section.
#!+sb-thread
(defmacro %clear-pseudo-atomic ()
  '(inst mov (make-ea :qword :base thread-base-tn
              :disp (* n-word-bytes thread-pseudo-atomic-bits-slot))
    0))

#!+sb-safepoint
(defun emit-safepoint ()
  (inst test al-tn (make-ea :byte :disp gc-safepoint-page-addr)))

#!+sb-thread
(defmacro pseudo-atomic (&rest forms)
  #!+sb-safepoint-strictly
  `(progn ,@forms (emit-safepoint))
  #!-sb-safepoint-strictly
  (with-unique-names (label)
    `(let ((,label (gen-label)))
       (inst mov (make-ea :qword
                          :base thread-base-tn
                          :disp (* n-word-bytes thread-pseudo-atomic-bits-slot))
             rbp-tn)
       ,@forms
       (inst xor (make-ea :qword
                          :base thread-base-tn
                          :disp (* n-word-bytes thread-pseudo-atomic-bits-slot))
             rbp-tn)
       (inst jmp :z ,label)
       ;; if PAI was set, interrupts were disabled at the same time
       ;; using the process signal mask.
       (inst break pending-interrupt-trap)
       (emit-label ,label)
       #!+sb-safepoint
       ;; In this case, when allocation thinks a GC should be done, it
       ;; does not mark PA as interrupted, but schedules a safepoint
       ;; trap instead.  Let's take the opportunity to trigger that
       ;; safepoint right now.
       (emit-safepoint))))


#!-sb-thread
(defmacro pseudo-atomic (&rest forms)
  (with-unique-names (label)
    `(let ((,label (gen-label)))
       ;; FIXME: The MAKE-EA noise should become a MACROLET macro or
       ;; something. (perhaps SVLB, for static variable low byte)
       (inst mov (make-ea :qword :disp (+ nil-value
                                          (static-symbol-offset
                                           '*pseudo-atomic-bits*)
                                          (ash symbol-value-slot word-shift)
                                          (- other-pointer-lowtag)))
             rbp-tn)
       ,@forms
       (inst xor (make-ea :qword :disp (+ nil-value
                                          (static-symbol-offset
                                           '*pseudo-atomic-bits*)
                                          (ash symbol-value-slot word-shift)
                                          (- other-pointer-lowtag)))
             rbp-tn)
       (inst jmp :z ,label)
       ;; if PAI was set, interrupts were disabled at the same time
       ;; using the process signal mask.
       (inst break pending-interrupt-trap)
       (emit-label ,label))))

;;;; indexed references

(defmacro define-full-compare-and-swap
    (name type offset lowtag scs el-type &optional translate)
  `(progn
     (define-vop (,name)
         ,@(when translate `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg) :to :eval)
              (index :scs (,@(when (member translate '(%instance-cas %raw-instance-cas/word))
                               '(immediate))
                           any-reg) :to :eval)
              (old-value :scs ,scs :target rax)
              (new-value :scs ,scs))
       (:arg-types ,type tagged-num ,el-type ,el-type)
       (:temporary (:sc descriptor-reg :offset rax-offset
                        :from (:argument 2) :to :result :target value)  rax)
       (:results (value :scs ,scs))
       (:result-types ,el-type)
       (:generator 5
         (move rax old-value)
         (inst cmpxchg
               (make-ea :qword :base object
                        :index  (unless (sc-is index immediate) index)
                        :scale (ash 1 (- word-shift n-fixnum-tag-bits))
                        :disp (- (* (+ (if (sc-is index immediate) (tn-value index) 0)
                                       ,offset) n-word-bytes) ,lowtag))
               new-value :lock)
         (move value rax)))))

(defmacro define-full-reffer (name type offset lowtag scs el-type &optional translate)
  `(progn
     (define-vop (,name)
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg))
              (index :scs (any-reg)))
       (:arg-types ,type tagged-num)
       (:results (value :scs ,scs))
       (:result-types ,el-type)
       (:generator 3                    ; pw was 5
         (inst mov value (make-ea :qword :base object :index index
                                  :scale (ash 1 (- word-shift n-fixnum-tag-bits))
                                  :disp (- (* ,offset n-word-bytes)
                                           ,lowtag)))))
     (define-vop (,(symbolicate name "-C"))
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg)))
       (:info index)
       (:arg-types ,type
                   (:constant (load/store-index ,n-word-bytes ,(eval lowtag)
                                                ,(eval offset))))
       (:results (value :scs ,scs))
       (:result-types ,el-type)
       (:generator 2                    ; pw was 5
         (inst mov value (make-ea :qword :base object
                                  :disp (- (* (+ ,offset index) n-word-bytes)
                                           ,lowtag)))))))

(defmacro define-full-reffer+offset (name type offset lowtag scs el-type &optional translate)
  `(progn
     (define-vop (,name)
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg))
              (index :scs (any-reg)))
       (:info offset)
       (:arg-types ,type tagged-num
                   (:constant (constant-displacement other-pointer-lowtag
                                                     n-word-bytes vector-data-offset)))
       (:results (value :scs ,scs))
       (:result-types ,el-type)
       (:generator 3                    ; pw was 5
         (inst mov value (make-ea :qword :base object :index index
                                  :scale (ash 1 (- word-shift n-fixnum-tag-bits))
                                  :disp (- (* (+ ,offset offset) n-word-bytes)
                                           ,lowtag)))))
     (define-vop (,(symbolicate name "-C"))
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg)))
       (:info index offset)
       (:arg-types ,type
                   (:constant (load/store-index ,n-word-bytes ,(eval lowtag)
                                                ,(eval offset)))
                   (:constant (constant-displacement other-pointer-lowtag
                                                     n-word-bytes vector-data-offset)))
       (:results (value :scs ,scs))
       (:result-types ,el-type)
       (:generator 2                    ; pw was 5
         (inst mov value (make-ea :qword :base object
                                  :disp (- (* (+ ,offset index offset) n-word-bytes)
                                           ,lowtag)))))))

(defmacro define-full-setter (name type offset lowtag scs el-type &optional translate)
  `(progn
     (define-vop (,name)
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg))
              (index :scs (any-reg))
              (value :scs ,scs :target result))
       (:arg-types ,type tagged-num ,el-type)
       (:results (result :scs ,scs))
       (:result-types ,el-type)
       (:generator 4                    ; was 5
         (inst mov (make-ea :qword :base object :index index
                            :scale (ash 1 (- word-shift n-fixnum-tag-bits))
                            :disp (- (* ,offset n-word-bytes) ,lowtag))
               value)
         (move result value)))
     (define-vop (,(symbolicate name "-C"))
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg))
              (value :scs ,scs :target result))
       (:info index)
       (:arg-types ,type
                   (:constant (load/store-index ,n-word-bytes ,(eval lowtag)
                                                ,(eval offset)))
                   ,el-type)
       (:results (result :scs ,scs))
       (:result-types ,el-type)
       (:generator 3                    ; was 5
         (inst mov (make-ea :qword :base object
                            :disp (- (* (+ ,offset index) n-word-bytes)
                                     ,lowtag))
               value)
         (move result value)))))

(defmacro define-full-setter+offset (name type offset lowtag scs el-type &optional translate)
  `(progn
     (define-vop (,name)
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg))
              (index :scs (any-reg))
              (value :scs ,scs :target result))
       (:info offset)
       (:arg-types ,type tagged-num
                   (:constant (constant-displacement other-pointer-lowtag
                                                     n-word-bytes
                                                     vector-data-offset))
                   ,el-type)
       (:results (result :scs ,scs))
       (:result-types ,el-type)
       (:generator 4                    ; was 5
         (inst mov (make-ea :qword :base object :index index
                            :scale (ash 1 (- word-shift n-fixnum-tag-bits))
                            :disp (- (* (+ ,offset offset) n-word-bytes) ,lowtag))
               value)
         (move result value)))
     (define-vop (,(symbolicate name "-C"))
       ,@(when translate
           `((:translate ,translate)))
       (:policy :fast-safe)
       (:args (object :scs (descriptor-reg))
              (value :scs ,scs :target result))
       (:info index offset)
       (:arg-types ,type
                   (:constant (load/store-index ,n-word-bytes ,(eval lowtag)
                                                ,(eval offset)))
                   (:constant (constant-displacement other-pointer-lowtag
                                                     n-word-bytes
                                                     vector-data-offset))
                   ,el-type)
       (:results (result :scs ,scs))
       (:result-types ,el-type)
       (:generator 3                    ; was 5
         (inst mov (make-ea :qword :base object
                            :disp (- (* (+ ,offset index offset) n-word-bytes)
                                     ,lowtag))
               value)
         (move result value)))))

;;; Emit the most compact form of the test immediate instruction
;;; by using the smallest operand size that is the large enough to hold
;;; the immediate value Y. The operand size makes no difference since the result
;;; of the implied AND is not written back to a register. However, if the msb
;;; (the sign bit) of the immediate at a smaller size is 1 but at its true size
;;; (always a :QWORD) is 0, the S flag value could come out 1 instead of 0.
;;; SIGN-BIT-MATTERS specifies that a shorter operand size must not be selected
;;; if doing so could affect whether the sign flag comes out the same.
;;; e.g. if EDX is #xff, "TEST EDX, #x80" indicates a non-negative result
;;; whereas "TEST DL, #x80" indicates a negative result.
(defun emit-optimized-test-inst (x y sign-bit-matters)
  (let* ((size-override
          (cond ((or (typep y '(unsigned-byte 7))
                     (and (typep y '(unsigned-byte 8)) (not sign-bit-matters)))
                 :byte)
                ((or (typep y '(unsigned-byte 15))
                     (and (typep y '(unsigned-byte 16)) (not sign-bit-matters)))
                 :word)
                ((or (typep y '(unsigned-byte 31))
                     (and (typep y '(unsigned-byte 32)) (not sign-bit-matters)))
                 :dword)))
         (offset (tn-offset x))
         (modified-x
          (when size-override
            (cond ((sc-is x control-stack)
                   ;; TODO: a 7- or 8-bit pattern that does not span bytes
                   ;; should be testable as a :BYTE by suitably altering :DISP.
                   (make-ea size-override :base rbp-tn :disp (frame-byte-offset offset)))
                  ((sc-is x any-reg descriptor-reg signed-reg unsigned-reg)
                   (reg-in-size x size-override))))))
    (inst test (or modified-x x) y)))
