#lang racket/base
(require racket/unsafe/ops
         (for-syntax racket/base racket/fixnum))
(provide fxpopcount
         unsafe-fxpopcount*)
;; Count set bits for 30 bit number in 5 steps.
;; for 62 bit number in 6, for 8 bit numbers in 3

(define-for-syntax lut30
  #(#x2AAAAAAA
    #x0CCCCCCC
    #x30F0F0F0
    #x3F00FF00
    #x3FFF0000))

(define-for-syntax lut62
  #(#x2AAAAAAAAAAAAAAA
    #x0CCCCCCCCCCCCCCC
    #x30F0F0F0F0F0F0F0
    #x3F00FF00FF00FF00
    #x3FFF0000FFFF0000
    #x3FFFFFFF00000000))

(define-for-syntax lut8
  #(#xAA
    #xCC
    #xF0))

(define-syntax (define-fxpopcount stx)
  (syntax-case stx ()
    [(_ name bits)
     #'(define (name n)
         (unless (fixnum? n) (raise-argument-error 'name "fixnum?" 0 n))
         (unsafe-fxpopcount* n bits))]))

(define-syntax (unsafe-fxpopcount* stx)
  (syntax-case stx ()
    [(_ expr bits0)
     ;; Choose at compile time what word length is
     (let* ([bits (syntax-e #'bits0)]
            [bits (or bits (if (fixnum? (expt 2 61)) 62 30))]
            [lut
             (cond [(<= bits 8) lut8]
                   [(<= bits 30) lut30]
                   [(<= bits 62) lut62]
                   [else (raise-syntax-error "bit width too big" stx #'bits0)])])
       ;; Unroll the addition loop
       #`(let ([n expr])
           (let* #,(for/list ([m (in-vector lut)]
                              [b (in-naturals)])
                     (define f (bitwise-not m))
                     #`[n (unsafe-fx+ (unsafe-fxrshift (unsafe-fxand n #,m)
                                                       #,(fxlshift 1 b))
                                      (unsafe-fxand n #,f))])
             n)))]))

(define-fxpopcount fxpopcount #f)
