; Adapted from Wojciech Mula's toys/parse_rfc_date.
; License: Simplified BSD, matching the upstream repository.
; See README.md in this directory for the write-up and export table.
;
; MS x64 ABI: RCX, RDX are the input registers; RAX the return register.

; int32_t toys_rfc_date_parse(const char* in, uint32_t* fields)
; Parse RFC date strings like "Fri, 17 Apr 2015 16:14:11 GMT".
; fields[0..6] = wday, mday, mon, year_since_1900, hour, min, sec.
; Return 0 on success, or -1..-14 matching parse_rfc_date/scalar.cpp's
; UNITTESTS error numbering.
section '.text$toys_rfc_date_parse' code readable executable comdat align 16
public toys_rfc_date_parse
toys_rfc_date_parse:
        mov     r10, rcx                         ; input
        mov     r11, rdx                         ; fields

        mov     eax, [r10 + 0]
        cmp     eax, 02c6e7553h                  ; "Sun,"
        je      .wday_sun
        cmp     eax, 02c6e6f4dh                  ; "Mon,"
        je      .wday_mon
        cmp     eax, 02c657554h                  ; "Tue,"
        je      .wday_tue
        cmp     eax, 02c646557h                  ; "Wed,"
        je      .wday_wed
        cmp     eax, 02c756854h                  ; "Thu,"
        je      .wday_thu
        cmp     eax, 02c697246h                  ; "Fri,"
        je      .wday_fri
        cmp     eax, 02c746153h                  ; "Sat,"
        je      .wday_sat
        mov     eax, -1
        ret
.wday_sun:
        xor     eax, eax
        jmp     .store_wday
.wday_mon:
        mov     eax, 1
        jmp     .store_wday
.wday_tue:
        mov     eax, 2
        jmp     .store_wday
.wday_wed:
        mov     eax, 3
        jmp     .store_wday
.wday_thu:
        mov     eax, 4
        jmp     .store_wday
.wday_fri:
        mov     eax, 5
        jmp     .store_wday
.wday_sat:
        mov     eax, 6
.store_wday:
        mov     [r11 + 0], eax

        cmp     byte [r10 + 4], ' '
        jne     .err2

        lea     rcx, [r10 + 5]
        call    toys_rfc_date_parse_2digits
        cmp     eax, 1
        jl      .err3
        cmp     eax, 31
        jg      .err3
        mov     [r11 + 4], eax

        cmp     byte [r10 + 7], ' '
        jne     .err4

        mov     eax, [r10 + 8]
        cmp     eax, 0206e614ah                  ; "Jan "
        je      .mon_jan
        cmp     eax, 020626546h                  ; "Feb "
        je      .mon_feb
        cmp     eax, 02072614dh                  ; "Mar "
        je      .mon_mar
        cmp     eax, 020727041h                  ; "Apr "
        je      .mon_apr
        cmp     eax, 02079614dh                  ; "May "
        je      .mon_may
        cmp     eax, 0206e754ah                  ; "Jun "
        je      .mon_jun
        cmp     eax, 0206c754ah                  ; "Jul "
        je      .mon_jul
        cmp     eax, 020677541h                  ; "Aug "
        je      .mon_aug
        cmp     eax, 020706553h                  ; "Sep "
        je      .mon_sep
        cmp     eax, 02074634fh                  ; "Oct "
        je      .mon_oct
        cmp     eax, 020766f4eh                  ; "Nov "
        je      .mon_nov
        cmp     eax, 020636544h                  ; "Dec "
        je      .mon_dec
        mov     eax, -5
        ret
.mon_jan:
        xor     eax, eax
        jmp     .store_mon
.mon_feb:
        mov     eax, 1
        jmp     .store_mon
.mon_mar:
        mov     eax, 2
        jmp     .store_mon
.mon_apr:
        mov     eax, 3
        jmp     .store_mon
.mon_may:
        mov     eax, 4
        jmp     .store_mon
.mon_jun:
        mov     eax, 5
        jmp     .store_mon
.mon_jul:
        mov     eax, 6
        jmp     .store_mon
.mon_aug:
        mov     eax, 7
        jmp     .store_mon
.mon_sep:
        mov     eax, 8
        jmp     .store_mon
.mon_oct:
        mov     eax, 9
        jmp     .store_mon
.mon_nov:
        mov     eax, 10
        jmp     .store_mon
.mon_dec:
        mov     eax, 11
.store_mon:
        mov     [r11 + 8], eax

        lea     rcx, [r10 + 12]
        call    toys_rfc_date_parse_4digits
        sub     eax, 1900
        cmp     eax, 0
        jl      .err6
        cmp     eax, 1000
        jg      .err6
        mov     [r11 + 12], eax

        cmp     byte [r10 + 16], ' '
        jne     .err7

        lea     rcx, [r10 + 17]
        call    toys_rfc_date_parse_2digits
        cmp     eax, 1
        jl      .err8
        cmp     eax, 24
        jg      .err8
        mov     [r11 + 16], eax

        cmp     byte [r10 + 19], ':'
        jne     .err9

        lea     rcx, [r10 + 20]
        call    toys_rfc_date_parse_2digits
        cmp     eax, 0
        jl      .err10
        cmp     eax, 59
        jg      .err10
        mov     [r11 + 20], eax

        cmp     byte [r10 + 22], ':'
        jne     .err11

        lea     rcx, [r10 + 23]
        call    toys_rfc_date_parse_2digits
        cmp     eax, 0
        jl      .err12
        cmp     eax, 59
        jg      .err12
        mov     [r11 + 24], eax

        cmp     byte [r10 + 25], ' '
        jne     .err13
        cmp     byte [r10 + 26], 'G'
        jne     .err14
        cmp     byte [r10 + 27], 'M'
        jne     .err14
        cmp     byte [r10 + 28], 'T'
        jne     .err14

        xor     eax, eax
        ret

.err2:
        mov     eax, -2
        ret
.err3:
        mov     eax, -3
        ret
.err4:
        mov     eax, -4
        ret
.err6:
        mov     eax, -6
        ret
.err7:
        mov     eax, -7
        ret
.err8:
        mov     eax, -8
        ret
.err9:
        mov     eax, -9
        ret
.err10:
        mov     eax, -10
        ret
.err11:
        mov     eax, -11
        ret
.err12:
        mov     eax, -12
        ret
.err13:
        mov     eax, -13
        ret
.err14:
        mov     eax, -14
        ret

; int32_t toys_rfc_date_parse_ssse3(const char* in, uint32_t* fields)
; Full SIMD parser adapted from parse_rfc_date/sse.cpp. The input is the fixed
; 29-byte RFC date form "Fri, 17 Apr 2015 16:14:11 GMT".
; fields[0..6] = wday, mday, mon, year_since_1900, hour, min, sec.
; Return 0 on success, or -1 for a layout, weekday, month, digit, or range
; validation failure.
section '.text$toys_rfc_date_parse_ssse3' code readable executable comdat align 16
public toys_rfc_date_parse_ssse3
toys_rfc_date_parse_ssse3:
        mov     r10, rcx
        mov     r11, rdx

        movdqu  xmm0, dqword [r10]
        movdqu  xmm1, dqword [rfc_date_lo_valid]
        pcmpeqb xmm1, xmm0
        pmovmskb eax, xmm1
        cmp     eax, 0898h
        jne     .invalid

        movdqu  xmm2, dqword [r10 + 13]
        movdqu  xmm1, dqword [rfc_date_hi_valid]
        pcmpeqb xmm1, xmm2
        pmovmskb eax, xmm1
        cmp     eax, 0f248h
        jne     .invalid

        movdqu  xmm1, xmm0
        pshufb  xmm1, dqword [rfc_date_weekday_shuffle01]
        pcmpeqw xmm1, dqword [rfc_date_weekday_letters01]
        movdqu  xmm3, xmm0
        pshufb  xmm3, dqword [rfc_date_weekday_shuffle22]
        pcmpeqw xmm3, dqword [rfc_date_weekday_letters22]
        pand    xmm1, xmm3
        pmovmskb eax, xmm1
        test    eax, eax
        jz      .invalid
        bsf     eax, eax
        shr     eax, 1
        mov     [r11 + 0], eax

        movdqu  xmm1, xmm0
        pshufb  xmm1, dqword [rfc_date_month_shuffle]
        movdqu  xmm3, xmm1
        pcmpeqd xmm3, dqword [rfc_date_month0]
        movdqu  xmm4, xmm1
        pcmpeqd xmm4, dqword [rfc_date_month1]
        pcmpeqd xmm1, dqword [rfc_date_month2]
        packssdw xmm3, xmm4
        packssdw xmm1, xmm1
        packsswb xmm3, xmm1
        pmovmskb eax, xmm3
        test    eax, eax
        jz      .invalid
        bsf     eax, eax
        mov     [r11 + 8], eax

        movdqu  xmm1, xmm0
        pshufb  xmm1, dqword [rfc_date_lo_digits_shuffle]
        pshufb  xmm2, dqword [rfc_date_hi_digits_shuffle]
        por     xmm1, xmm2
        psubb   xmm1, dqword [rfc_date_ascii0]

        pxor    xmm2, xmm2
        movdqu  xmm3, xmm2
        pcmpgtb xmm3, xmm1
        movdqu  xmm4, xmm1
        pcmpgtb xmm4, dqword [rfc_date_digit9]
        por     xmm3, xmm4
        pmovmskb eax, xmm3
        test    eax, eax
        jnz     .invalid

        pmaddubsw xmm1, dqword [rfc_date_digit_multipliers]
        movdqu  xmm2, dqword [rfc_date_numbers_lo_bound]
        pcmpgtw xmm2, xmm1
        movdqu  xmm3, xmm1
        pcmpgtw xmm3, dqword [rfc_date_numbers_hi_bound]
        por     xmm2, xmm3
        pmovmskb eax, xmm2
        test    eax, eax
        jnz     .invalid

        pextrw  eax, xmm1, 4
        mov     [r11 + 4], eax
        pextrw  eax, xmm1, 5
        mov     [r11 + 16], eax
        pextrw  eax, xmm1, 6
        mov     [r11 + 20], eax
        pextrw  eax, xmm1, 7
        mov     [r11 + 24], eax

        pextrw  eax, xmm1, 2
        imul    eax, eax, 100
        pextrw  edx, xmm1, 3
        add     eax, edx
        sub     eax, 1900
        cmp     eax, 1000
        ja      .invalid
        mov     [r11 + 12], eax

        xor     eax, eax
        ret
.invalid:
        mov     eax, -1
        ret

; int32_t toys_rfc_date_parse_kendall_ssse3(const char* in, uint32_t* fields)
; Kendall Willets' parse_rfc_date/sse_kendall.cpp variant. It interleaves the
; month and weekday hashes into the same SSSE3 PMADDUBSW pass that converts the
; numeric fields. fields[0..6] = wday, mday, mon, year_since_1900, hour, min, sec.
; Return 0 on success, or -1 for a validation failure.
section '.text$toys_rfc_date_parse_kendall_ssse3' code readable executable comdat align 16
public toys_rfc_date_parse_kendall_ssse3
toys_rfc_date_parse_kendall_ssse3:
        mov     r10, rcx
        mov     r11, rdx

        movdqu  xmm0, dqword [r10]
        movdqu  xmm1, dqword [rfc_date_lo_valid]
        pcmpeqb xmm1, xmm0
        pmovmskb eax, xmm1
        cmp     eax, 0898h
        jne     .invalid

        movdqu  xmm1, dqword [r10 + 13]
        movdqu  xmm2, dqword [rfc_date_hi_valid]
        pcmpeqb xmm2, xmm1
        pmovmskb eax, xmm2
        cmp     eax, 0f248h
        jne     .invalid

        pshufb  xmm0, dqword [rfc_date_kendall_lo_shuffle]
        pshufb  xmm1, dqword [rfc_date_kendall_hi_shuffle]
        por     xmm0, xmm1
        psubb   xmm0, dqword [rfc_date_kendall_ascii0]

        pxor    xmm1, xmm1
        movdqa  xmm2, xmm1
        pcmpgtb xmm2, xmm0
        movdqa  xmm3, xmm0
        pcmpgtb xmm3, dqword [rfc_date_digit9]
        por     xmm2, xmm3
        pmovmskb eax, xmm2
        test    eax, 0fcfch
        jnz     .invalid

        pmaddubsw xmm0, dqword [rfc_date_kendall_converters]

        movdqu  xmm1, dqword [rfc_date_kendall_lo_bound]
        pcmpgtw xmm1, xmm0
        movdqa  xmm2, xmm0
        pcmpgtw xmm2, dqword [rfc_date_kendall_hi_bound]
        por     xmm1, xmm2
        pmovmskb eax, xmm1
        test    eax, 0fcfch
        jnz     .invalid

        pextrw  eax, xmm0, 7
        mov     [r11 + 4], eax
        pextrw  eax, xmm0, 5
        mov     [r11 + 16], eax
        pextrw  eax, xmm0, 3
        mov     [r11 + 20], eax
        pextrw  eax, xmm0, 1
        mov     [r11 + 24], eax

        ; indexed table accesses need a RIP-relative base; absolute
        ; [table + reg] would emit ADDR32 relocations that cannot reach the
        ; default x64 image base
        pextrw  eax, xmm0, 0
        shr     eax, 8
        and     eax, 00fh
        lea     r8, [rfc_date_kendall_months]
        mov     edx, [r10 + 8]
        cmp     [r8 + rax*4], edx
        jne     .invalid
        lea     r8, [rfc_date_kendall_months_inv]
        movzx   eax, byte [r8 + rax]
        mov     [r11 + 8], eax

        pextrw  eax, xmm0, 4
        shr     eax, 8
        and     eax, 007h
        lea     r8, [rfc_date_kendall_days]
        mov     edx, [r10]
        cmp     [r8 + rax*4], edx
        jne     .invalid
        lea     r8, [rfc_date_kendall_days_inv]
        movzx   eax, byte [r8 + rax]
        mov     [r11], eax

        pextrw  eax, xmm0, 2
        imul    eax, eax, 100
        pextrw  edx, xmm0, 6
        add     eax, edx
        sub     eax, 1900
        cmp     eax, 1000
        ja      .invalid
        mov     [r11 + 12], eax

        xor     eax, eax
        ret
.invalid:
        mov     eax, -1
        ret

; int32_t toys_rfc_date_parse_2digits(const char* in)
; Return 0..99, or -1 when either byte is not an ASCII digit.
section '.text$toys_rfc_date_parse_2digits' code readable executable comdat align 16
public toys_rfc_date_parse_2digits
toys_rfc_date_parse_2digits:
        movzx   eax, byte [rcx]
        sub     eax, '0'
        cmp     eax, 9
        ja      .invalid
        imul    eax, eax, 10

        movzx   edx, byte [rcx + 1]
        sub     edx, '0'
        cmp     edx, 9
        ja      .invalid
        add     eax, edx
        ret
.invalid:
        mov     eax, -1
        ret

; int32_t toys_rfc_date_parse_4digits(const char* in)
; Return 0..9999, or -1 when any byte is not an ASCII digit.
section '.text$toys_rfc_date_parse_4digits' code readable executable comdat align 16
public toys_rfc_date_parse_4digits
toys_rfc_date_parse_4digits:
        movzx   eax, byte [rcx]
        sub     eax, '0'
        cmp     eax, 9
        ja      .invalid
        imul    eax, eax, 1000

        movzx   edx, byte [rcx + 1]
        sub     edx, '0'
        cmp     edx, 9
        ja      .invalid
        imul    edx, edx, 100
        add     eax, edx

        movzx   edx, byte [rcx + 2]
        sub     edx, '0'
        cmp     edx, 9
        ja      .invalid
        imul    edx, edx, 10
        add     eax, edx

        movzx   edx, byte [rcx + 3]
        sub     edx, '0'
        cmp     edx, 9
        ja      .invalid
        add     eax, edx
        ret
.invalid:
        mov     eax, -1
        ret

; uint32_t toys_rfc_date_validate_layout_sse2(const char* in)
; Validate the fixed punctuation and "GMT" bytes from parse_rfc_date/sse.cpp's
; INPUT_VALIDATION stage. Returns 1 for matching layout, otherwise 0.
section '.text$toys_rfc_date_validate_layout_sse2' code readable executable comdat align 16
public toys_rfc_date_validate_layout_sse2
toys_rfc_date_validate_layout_sse2:
        movdqu  xmm0, dqword [rcx]
        movdqu  xmm1, dqword [rfc_date_lo_valid]
        pcmpeqb xmm0, xmm1
        pmovmskb eax, xmm0
        cmp     eax, 0898h
        jne     .false

        movdqu  xmm0, dqword [rcx + 13]
        movdqu  xmm1, dqword [rfc_date_hi_valid]
        pcmpeqb xmm0, xmm1
        pmovmskb eax, xmm0
        cmp     eax, 0f248h
        sete    al
        movzx   eax, al
        ret
.false:
        xor     eax, eax
        ret

; uint32_t toys_rfc_date_month_mask_ssse3(const char* in)
; Method-2 month matcher from parse_rfc_date/sse.cpp. The input points at the
; full RFC date string, and bytes 8..10 hold the month abbreviation. Matching
; months set their byte-position bit in the returned movemask; invalid month
; text returns zero. Requires SSSE3 for PSHUFB.
section '.text$toys_rfc_date_month_mask_ssse3' code readable executable comdat align 16
public toys_rfc_date_month_mask_ssse3
toys_rfc_date_month_mask_ssse3:
        movdqu  xmm0, dqword [rcx]
        pshufb  xmm0, dqword [rfc_date_month_shuffle]

        movdqu  xmm1, xmm0
        pcmpeqd xmm1, dqword [rfc_date_month0]
        movdqu  xmm2, xmm0
        pcmpeqd xmm2, dqword [rfc_date_month1]
        pcmpeqd xmm0, dqword [rfc_date_month2]

        packssdw xmm1, xmm2
        packssdw xmm0, xmm0
        packsswb xmm1, xmm0
        pmovmskb eax, xmm1
        ret

; Constants are grouped by the set of functions that use them, so unused
; groups are stripped along with their users.

; Fixed-layout masks and the shared digit limit: parse_ssse3, kendall_ssse3,
; and validate_layout_sse2.
section '.rdata$rfc_date_layout' data readable comdat align 16
public static rfc_date_lo_valid
rfc_date_lo_valid:
        db      0, 0, 0, ',', ' ', 0, 0, ' ', 0, 0, 0, ' ', 0, 0, 0, 0
rfc_date_hi_valid:
        db      0, 0, 0, ' ', 0, 0, ':', 0, 0, ':', 0, 0, ' ', 'G', 'M', 'T'
rfc_date_digit9:
        db      16 dup 9

; Weekday matcher tables: parse_ssse3 only.
section '.rdata$rfc_date_weekday' data readable comdat align 16
public static rfc_date_weekday_shuffle01
rfc_date_weekday_shuffle01:
        db      0, 1, 0, 1, 0, 1, 0, 1
        db      0, 1, 0, 1, 0, 1, 080h, 080h
rfc_date_weekday_shuffle22:
        db      2, 2, 2, 2, 2, 2, 2, 2
        db      2, 2, 2, 2, 2, 2, 080h, 080h
rfc_date_weekday_letters01:
        db      'S', 'u', 'M', 'o', 'T', 'u', 'W', 'e'
        db      'T', 'h', 'F', 'r', 'S', 'a', 0ffh, 0ffh
rfc_date_weekday_letters22:
        db      'n', 'n', 'n', 'n', 'e', 'e', 'd', 'd'
        db      'u', 'u', 'i', 'i', 't', 't', 0ffh, 0ffh

; Month matcher tables: parse_ssse3 and month_mask_ssse3.
section '.rdata$rfc_date_month' data readable comdat align 16
public static rfc_date_month_shuffle
rfc_date_month_shuffle:
        db      8, 9, 10, 080h, 8, 9, 10, 080h
        db      8, 9, 10, 080h, 8, 9, 10, 080h
rfc_date_month0:
        db      'J', 'a', 'n', 0, 'F', 'e', 'b', 0
        db      'M', 'a', 'r', 0, 'A', 'p', 'r', 0
rfc_date_month1:
        db      'M', 'a', 'y', 0, 'J', 'u', 'n', 0
        db      'J', 'u', 'l', 0, 'A', 'u', 'g', 0
rfc_date_month2:
        db      'S', 'e', 'p', 0, 'O', 'c', 't', 0
        db      'N', 'o', 'v', 0, 'D', 'e', 'c', 0

; Numeric-conversion tables: parse_ssse3 only.
section '.rdata$rfc_date_digits' data readable comdat align 16
public static rfc_date_lo_digits_shuffle
rfc_date_lo_digits_shuffle:
        db      12, 12, 12, 12, 12, 13, 14, 15
        db      5, 6, 080h, 080h, 080h, 080h, 080h, 080h
rfc_date_hi_digits_shuffle:
        db      080h, 080h, 080h, 080h, 080h, 080h, 080h, 080h
        db      080h, 080h, 4, 5, 7, 8, 10, 11
rfc_date_ascii0:
        db      16 dup '0'
rfc_date_digit_multipliers:
        db      0, 0, 0, 0, 10, 1, 10, 1
        db      10, 1, 10, 1, 10, 1, 10, 1
rfc_date_numbers_lo_bound:
        dw      0, 0, 19, 0, 1, 1, 0, 0
rfc_date_numbers_hi_bound:
        dw      0, 0, 29, 99, 31, 24, 59, 59

; Kendall hash tables: parse_kendall_ssse3 only.
section '.rdata$rfc_date_kendall' data readable comdat align 16
public static rfc_date_kendall_lo_shuffle
rfc_date_kendall_lo_shuffle:
        db      9, 10, 080h, 080h, 12, 13, 080h, 080h
        db      0, 1, 080h, 080h, 14, 15, 5, 6
rfc_date_kendall_hi_shuffle:
        db      080h, 080h, 10, 11, 080h, 080h, 7, 8
        db      080h, 080h, 4, 5, 080h, 080h, 080h, 080h
rfc_date_kendall_ascii0:
        db      0, 0, '0', '0', '0', '0', '0', '0'
        db      0, 0, '0', '0', '0', '0', '0', '0'
rfc_date_kendall_converters:
        db      0a2h, 059h, 10, 1, 10, 1, 10, 1
        db      0ach, 01dh, 10, 1, 10, 1, 10, 1
rfc_date_kendall_lo_bound:
        dw      0, 0, 19, 0, 0, 1, 0, 1
rfc_date_kendall_hi_bound:
        dw      0, 59, 29, 59, 0, 24, 99, 31
rfc_date_kendall_days:
        dd      02c756854h, 02c657554h, 02c6e7553h, 02c6e6f4dh
        dd      000000000h, 02c697246h, 02c646557h, 02c746153h
rfc_date_kendall_months:
        dd      020766f4eh, 020706553h, 0206e614ah, 02074634fh
        dd      02072614dh, 000000000h, 02079614dh, 000000000h
        dd      020677541h, 000000000h, 0206c754ah, 0206e754ah
        dd      020626546h, 020636544h, 020727041h, 000000000h
rfc_date_kendall_days_inv:
        db      4, 2, 0, 1, 0, 5, 3, 6
rfc_date_kendall_months_inv:
        db      10, 8, 0, 9, 2, 0, 4, 0, 7, 0, 6, 5, 1, 11, 3, 0
