Generate integer encoders and decoders with thought towards the size of a FIXNUM
authorAlejandro R Sedeño <asedeno@google.com>
Mon, 17 Dec 2012 18:47:55 +0000 (13:47 -0500)
committerAlejandro R Sedeño <asedeno@google.com>
Thu, 3 Jan 2013 17:32:25 +0000 (12:32 -0500)
Rather than hard-code assumptions about the size of a FIXNUM, check
the size of FIXNUM at compile time and generate code appropriate for
the number of bits we're working with, with FIXNUM optimizations
in place if appropriate.

Includes some changes from swmckay@gmail.com.

wire-format.lisp

index 8ce1b90..fffa360 100644 (file)
             (t (return x))))))
 
 
-(defun zig-zag-encode32 (val)
-  (declare #.$optimize-serialization)
-  (declare (type (signed-byte 32) val))
-  (logxor (ash val 1) (ash val -31)))
-
-(defun zig-zag-encode64 (val)
-  (declare #.$optimize-serialization)
-  (declare (type (signed-byte 64) val))
-  (logxor (ash val 1) (ash val -63)))
-
-(define-compiler-macro zig-zag-encode32 (&whole form val)
-  (if (atom val)
-    `(locally (declare #.$optimize-serialization
-                       (type (signed-byte 32) ,val))
-       (logxor (ash ,val 1) (ash ,val -31)))
-    form))
-
-(define-compiler-macro zig-zag-encode64 (&whole form val)
-  (if (atom val)
-    `(locally (declare #.$optimize-serialization
-                       (type (signed-byte 64) ,val))
-       (logxor (ash ,val 1) (ash ,val -63)))
-    form))
-
-(defun zig-zag-decode32 (val)
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 32) val))
-  (logxor (ash val -1) (- (logand val 1))))
-
-(defun zig-zag-decode64 (val)
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 64) val))
-  (logxor (ash val -1) (- (logand val 1))))
-
-(define-compiler-macro zig-zag-decode32 (&whole form val)
-  (if (atom val)
-    `(locally (declare #.$optimize-serialization
-                       (type (unsigned-byte 32) ,val))
-       (logxor (ash ,val -1) (- (logand ,val 1))))
-    form))
-
-(define-compiler-macro zig-zag-decode64 (&whole form val)
-  (if (atom val)
-    `(locally (declare #.$optimize-serialization
-                       (type (unsigned-byte 64) ,val))
-       (logxor (ash ,val -1) (- (logand ,val 1))))
-    form))
+(defmacro gen-zig-zag (bits)
+  "Generate 32- or 64-bit versions of zig-zag encoder/decoder."
+  (assert (and (plusp bits) (zerop (mod bits 8))))
+  (let* ((zig-zag-encode (fintern "~A~A" 'zig-zag-encode bits))
+         (zig-zag-decode (fintern "~A~A" 'zig-zag-decode bits))
+         (zig-zag-shift (1+ (- bits))))
+    `(progn
+       (defun ,zig-zag-encode (val)
+         (declare #.$optimize-serialization)
+         (declare (type (signed-byte ,bits) val))
+         (logxor (ash val 1) (ash val ,zig-zag-shift)))
+       (define-compiler-macro ,zig-zag-encode (&whole form val)
+         (if (atom val)
+           `(locally (declare #.$optimize-serialization
+                              (type (signed-byte ,',bits) ,val))
+              (logxor (ash ,val 1) (ash ,val ,',zig-zag-shift)))
+           form))
+       (defun ,zig-zag-decode (val)
+         (declare #.$optimize-serialization)
+         (declare (type (unsigned-byte ,bits) val))
+         (logxor (ash val -1) (- (logand val 1))))
+       (define-compiler-macro ,zig-zag-decode (&whole form val)
+         (if (atom val)
+           `(locally (declare #.$optimize-serialization
+                              (type (unsigned-byte ,',bits) ,val))
+              (logxor (ash ,val -1) (- (logand ,val 1))))
+           form)))))
+
+(gen-zig-zag 32)
+(gen-zig-zag 64)
 
 
 ;;; Serializers
 ;;; Wire-level encoders
 ;;; These are called at the lowest level, so arg types are assumed to be correct
 
-(defun encode-uint32 (val buffer index)
-  "Encodes the unsigned 32-bit integer 'val' as a varint into the buffer
-   at the given index.
-   Modifies the buffer, and returns the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 32) val)
-           (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Seven bits at a time, least significant bits first
-  (loop do (let ((bits (ildb (byte 7 0) val)))
-             (declare (type (unsigned-byte 8) bits))
-             (setq val (iash val -7))
-             (setf (aref buffer index) (ilogior bits (if (i= val 0) 0 128)))
-             (iincf index))
-        until (i= val 0))
-  (values index buffer))                        ;return the buffer to improve 'trace'
-
-(defun encode-uint64 (val buffer index)
-  "Encodes the unsigned 64-bit integer 'val' as a varint into the buffer
-   at the given index.
-   Modifies the buffer, and returns the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 64) val)
-           (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (loop do (let ((bits (ldb (byte 7 0) val)))
-             (declare (type (unsigned-byte 8) bits))
-             (setq val (ash val -7))
-             (setf (aref buffer index) (ilogior bits (if (zerop val) 0 128)))
-             (iincf index))
-        until (zerop val))
-  (values index buffer))
-
-(defun encode-fixed32 (val buffer index)
-  "Encodes the unsigned 32-bit integer 'val' as a fixed int into the buffer
-   at the given index.
-   Modifies the buffer, and returns the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 32) val)
-           (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (loop repeat 4 doing
-    (let ((byte (ildb (byte 8 0) val)))
-      (declare (type (unsigned-byte 8) byte))
-      (setq val (iash val -8))
-      (setf (aref buffer index) byte)
-      (iincf index)))
-  (values index buffer))
-
-(defun encode-fixed64 (val buffer index)
-  "Encodes the unsigned 64-bit integer 'val' as a fixed int into the buffer
-   at the given index.
-   Modifies the buffer, and returns the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 64) val)
-           (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (loop repeat 8 doing
-    (let ((byte (ldb (byte 8 0) val)))
-      (declare (type (unsigned-byte 8) byte))
-      (setq val (ash val -8))
-      (setf (aref buffer index) byte)
-      (iincf index)))
-  (values index buffer))
-
-(defun encode-sfixed32 (val buffer index)
-  "Encodes the signed 32-bit integer 'val' as a fixed int into the buffer
-   at the given index.
-   Modifies the buffer, and returns the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (signed-byte 32) val)
-           (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (loop repeat 4 doing
-    (let ((byte (ildb (byte 8 0) val)))
-      (declare (type (unsigned-byte 8) byte))
-      (setq val (iash val -8))
-      (setf (aref buffer index) byte)
-      (iincf index)))
-  (values index buffer))
-
-(defun encode-sfixed64 (val buffer index)
-  "Encodes the signed 64-bit integer 'val' as a fixed int into the buffer
-   at the given index.
-   Modifies the buffer, and returns the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (signed-byte 64) val)
-           (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (loop repeat 8 doing
-    (let ((byte (ldb (byte 8 0) val)))
-      (declare (type (unsigned-byte 8) byte))
-      (setq val (ash val -8))
-      (setf (aref buffer index) byte)
-      (iincf index)))
-  (values index buffer))
+(defmacro generate-integer-encoders (bits)
+  "Generate 32- or 64-bit versions of integer encoders."
+  (assert (and (plusp bits) (zerop (mod bits 8))))
+  (let* ((encode-uint   (fintern "~A~A" 'encode-uint bits))
+         (encode-fixed  (fintern "~A~A" 'encode-fixed bits))
+         (encode-sfixed (fintern "~A~A" 'encode-sfixed bits))
+         (bytes (/ bits 8))
+         ;; Given bits, can we use fixnums safely?
+         (fixnump (<= bits (integer-length most-negative-fixnum)))
+         (ldb (if fixnump 'ildb 'ldb))
+         (ash (if fixnump 'iash 'ash))
+         (zerop-val (if fixnump '(i= val 0) '(zerop val))))
+    `(progn
+       (defun ,encode-uint (val buffer index)
+         ,(format nil
+                  "Encodes the unsigned ~A-bit integer 'val' as a varint into the buffer at the given index.~
+                   ~&    Modifies the buffer, and returns the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (unsigned-byte ,bits) val)
+                  (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         ;; Seven bits at a time, least significant bits first
+         (loop do (let ((bits (,ldb (byte 7 0) val)))
+                    (declare (type (unsigned-byte 8) bits))
+                    (setq val (,ash val -7))
+                    (setf (aref buffer index)
+                          (ilogior bits (if ,zerop-val 0 128)))
+                    (iincf index))
+               until ,zerop-val)
+         (values index buffer))                 ;return the buffer to improve 'trace'
+       (defun ,encode-fixed (val buffer index)
+         ,(format nil
+                  "Encodes the unsigned ~A-bit integer 'val' as a fixed int into the buffer at the given index.~
+                   ~&    Modifies the buffer, and returns the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (unsigned-byte ,bits) val)
+                  (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         (loop repeat ,bytes doing
+           (let ((byte (,ldb (byte 8 0) val)))
+             (declare (type (unsigned-byte 8) byte))
+             (setq val (,ash val -8))
+             (setf (aref buffer index) byte)
+             (iincf index)))
+         (values index buffer))
+       (defun ,encode-sfixed (val buffer index)
+         ,(format nil
+                  "Encodes the signed ~A-bit integer 'val' as a fixed int into the buffer at the given index.~
+                   ~&    Modifies the buffer, and returns the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (signed-byte ,bits) val)
+                  (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         (loop repeat ,bytes doing
+           (let ((byte (,ldb (byte 8 0) val)))
+             (declare (type (unsigned-byte 8) byte))
+             (setq val (,ash val -8))
+             (setf (aref buffer index) byte)
+             (iincf index)))
+         (values index buffer)))))
+
+(generate-integer-encoders 32)
+(generate-integer-encoders 64)
 
 (defun encode-single (val buffer index)
   "Encodes the single float 'val' into the buffer at the given index.
 
 ;; Decode the value from the buffer at the given index,
 ;; then return the value and new index into the buffer
-(defun decode-uint32 (buffer index)
-  "Decodes the next 32-bit varint integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Seven bits at a time, least significant bits first
-  (let ((val 0))
-    (declare (type (unsigned-byte 32) val))
-    (loop for places fixnum upfrom 0 by 7
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (let ((bits (ildb (byte 7 0) byte)))
-               (declare (type (unsigned-byte 8) bits))
-               (setq val (ilogior val (iash bits places))))
-          until (i< byte 128)
-          finally (progn
-                    (assert (< val #.(ash 1 32)) ()
-                            "The value ~D is longer than 32 bits" val)
-                    (return (values val index))))))
-
-(defun decode-uint64 (buffer index)
-  "Decodes the next 64-bit varint integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Seven bits at a time, least significant bits first
-  (let ((val 0))
-    (declare (type (unsigned-byte 64) val))
-    (loop for places fixnum upfrom 0 by 7
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (let ((bits (ildb (byte 7 0) byte)))
-               (declare (type (unsigned-byte 8) bits))
-               (setq val (logior val (ash bits places))))
-          until (i< byte 128)
-          finally (return (values val index)))))
-
-(defun decode-int32 (buffer index)
-  "Decodes the next 32-bit varint integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (multiple-value-bind (val index)
-      (decode-uint32 buffer index)
-    (declare (type fixnum val))
-    (when (i= (ildb (byte 1 31) val) 1)
-      (idecf val #.(ash 1 32)))
-    (values val index)))
-
-(defun decode-int64 (buffer index)
-  "Decodes the next 64-bit varint integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  (multiple-value-bind (val index)
-      (decode-uint64 buffer index)
-    (when (i= (ldb (byte 1 63 ) val) 1)
-      (decf val #.(ash 1 64)))
-    (values val index)))
-
-(defun decode-fixed32 (buffer index)
-  "Decodes the next 32-bit unsigned fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Eight bits at a time, least significant bits first
-  (let ((val 0))
-    (declare (type fixnum val))
-    (loop repeat 4
-          for places fixnum upfrom 0 by 8
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (setq val (ilogior val (iash byte places))))
-    (values val index)))
-
-(defun decode-fixed64 (buffer index)
-  "Decodes the next unsigned 64-bit fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Eight bits at a time, least significant bits first
-  (let ((val 0))
-    (loop repeat 8
-          for places fixnum upfrom 0 by 8
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (setq val (logior val (ash byte places))))
-    (values val index)))
-
-(defun decode-sfixed32 (buffer index)
-  "Decodes the next 32-bit signed fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Eight bits at a time, least significant bits first
-  (let ((val 0))
-    (declare (type fixnum val))
-    (loop repeat 4
-          for places fixnum upfrom 0 by 8
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (setq val (ilogior val (iash byte places))))
-    (when (i= (ldb (byte 1 31) val) 1)              ;sign bit set, so negative value
-      (decf val #.(ash 1 32)))
-    (values val index)))
-
-(defun decode-sfixed64 (buffer index)
-  "Decodes the next signed 64-bit fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer.
-   Watch out, this function turns off all type checking and array bounds checking."
-  (declare #.$optimize-serialization)
-  (declare (type (simple-array (unsigned-byte 8)) buffer)
-           (type fixnum index))
-  ;; Eight bits at a time, least significant bits first
-  (let ((val 0))
-    (loop repeat 8
-          for places fixnum upfrom 0 by 8
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (setq val (logior val (ash byte places))))
-    (when (i= (ldb (byte 1 63) val) 1)             ;sign bit set, so negative value
-      (decf val #.(ash 1 64)))
-    (values val index)))
+(defmacro generate-integer-decoders (bits)
+  "Generate 32- or 64-bit versions of integer decoders."
+  (assert (and (plusp bits) (zerop (mod bits 8))))
+  (let* ((decode-uint (fintern "~A~A" 'decode-uint bits))
+         (decode-int  (fintern "~A~A" 'decode-int bits))
+         (decode-fixed  (fintern "~A~A" 'decode-fixed bits))
+         (decode-sfixed (fintern "~A~A" 'decode-sfixed bits))
+         (bytes (/ bits 8))
+         ;; Given bits, can we use fixnums safely?
+         (fixnump (<= bits (integer-length most-negative-fixnum)))
+         (ldb (if fixnump 'ildb 'ldb))
+         (ash (if fixnump 'iash 'ash))
+         (decf (if fixnump 'idecf 'decf))
+         (logior (if fixnump 'ilogior 'logior)))
+    `(progn
+       (defun ,decode-uint (buffer index)
+         ,(format nil
+                  "Decodes the next ~A-bit varint integer in the buffer at the given index.~
+                   ~&    Returns both the decoded value and the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         ;; Seven bits at a time, least significant bits first
+         (let ((val 0))
+           (declare (type (unsigned-byte ,bits) val))
+           (loop for places fixnum upfrom 0 by 7
+                 for byte fixnum = (prog1 (aref buffer index) (iincf index))
+                 do (let ((bits (ildb (byte 7 0) byte)))
+                      (declare (type (unsigned-byte 8) bits))
+                      (setq val (,logior val (,ash bits places))))
+                 until (i< byte 128)
+                 finally (progn
+                           (assert (< val ,(ash 1 bits)) ()
+                                   "The value ~D is longer than ~A bits" val ,bits)
+                           (return (values val index))))))
+       (defun ,decode-int (buffer index)
+         ,(format nil
+                  "Decodes the next ~A-bit varint integer in the buffer at the given index.~
+                   ~&    Returns both the decoded value and the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         (multiple-value-bind (val index)
+             (,decode-uint buffer index)
+           ,@(when fixnump `((declare (type fixnum val))))
+           (when (i= (,ldb (byte 1 ,(1- bits)) val) 1)
+             (,decf val ,(ash 1 bits)))
+           (values val index)))
+       (defun ,decode-fixed (buffer index)
+         ,(format nil
+                  "Decodes the next ~A-bit unsigned fixed integer in the buffer at the given index.~
+                   ~&    Returns both the decoded value and the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         ;; Eight bits at a time, least significant bits first
+         (let ((val 0))
+           ,@(when fixnump `((declare (type fixnum val))))
+           (loop repeat ,bytes
+                 for places fixnum upfrom 0 by 8
+                 for byte fixnum = (prog1 (aref buffer index) (iincf index))
+                 do (setq val (,logior val (,ash byte places))))
+           (values val index)))
+       (defun ,decode-sfixed (buffer index)
+         ,(format nil
+                  "Decodes the next ~A-bit signed fixed integer in the buffer at the given index.~
+                   ~&    Returns both the decoded value and the new index into the buffer.~
+                   ~&    Watch out, this function turns off all type checking and array bounds checking." bits)
+         (declare #.$optimize-serialization)
+         (declare (type (simple-array (unsigned-byte 8)) buffer)
+                  (type fixnum index))
+         ;; Eight bits at a time, least significant bits first
+         (let ((val 0))
+           ,@(when fixnump `((declare (type fixnum val))))
+           (loop repeat ,bytes
+                 for places fixnum upfrom 0 by 8
+                 for byte fixnum = (prog1 (aref buffer index) (iincf index))
+                 do (setq val (,logior val (,ash byte places))))
+           (when (i= (,ldb (byte 1 ,(1- bits)) val) 1)  ;sign bit set, so negative value
+             (,decf val ,(ash 1 bits)))
+           (values val index))))))
+
+(generate-integer-decoders 32)
+(generate-integer-decoders 64)
 
 (defun decode-single (buffer index)
   "Decodes the next single float in the buffer at the given index.
 ;;; Wire-level lengths
 ;;; These are called at the lowest level, so arg types are assumed to be correct
 
-(defun length32 (val)
-  "Returns the length that 'val' will take when encoded as a 32-bit integer."
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 32) val))
-  (let ((size 0))
-    (declare (type fixnum size))
-    (loop do (progn
-               (setq val (iash val -7))
-               (iincf size))
-          until (i= val 0))
-    size))
-
-(defun length64 (val)
-  "Returns the length that 'val' will take when encoded as a 64-bit integer."
-  (declare #.$optimize-serialization)
-  (declare (type (unsigned-byte 64) val))
-  (let ((size 0))
-    (declare (type fixnum size))
-    (loop do (progn
-               (setq val (ash val -7))
-               (iincf size))
-          until (zerop val))
-    size))
+(defmacro gen-length (bits)
+  "Generate 32- or 64-bit versions of integer length functions."
+  (assert (and (plusp bits) (zerop (mod bits 8))))
+  (let* (;; Given bits, can we use fixnums safely?
+         (fixnump (<= bits (integer-length most-negative-fixnum)))
+         (ash (if fixnump 'iash 'ash))
+         (zerop-val (if fixnump '(i= val 0) '(zerop val))))
+    `(defun ,(fintern "~A~A" 'length bits) (val)
+       ,(format nil "Returns the length that 'val' will take when encoded as a ~A-bit integer." bits)
+       (declare #.$optimize-serialization)
+       (declare (type (unsigned-byte ,bits) val))
+       (let ((size 0))
+         (declare (type fixnum size))
+         (loop do (progn
+                    (setq val (,ash val -7))
+                    (iincf size))
+               until ,zerop-val)
+         size))))
+
+(gen-length 32)
+(gen-length 64)
 
 
 ;;; Skipping elements