ultoa_invert.S

/* Copyright (c) 2005,2007  Dmitry Xmelkov
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.
   * Neither the name of the copyright holders nor the names of
     contributors may be used to endorse or promote products derived
     from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. */

/* $Id$ */

#if !defined(__AVR_TINY__)

#ifndef __DOXYGEN__

#include "macros.inc"
#include "ntz.h"
#include "xtoa_fast.h"

/* --------------------------------------------------------------------
   char * __ultoa_invert (unsigned long val, char * str, int base)

   This function is intended for usage as internal printf's one.
   It differs from others of `xtoa_fast' family:
       * srt[] will NOT 0 terminated.
       * Sequence of digits is inverted.
       * It returns pointer to first byte after a string.
       * Only `XTOA_UPPER' flag is operated.
   Notes:
       * base: check only 8 and 16, all others are treated as 10.
       (internal printf's function).
*/

    /* Input    */
#define v_lo    r22
#define v_hi    r23
#define v_hlo   r24
#define v_hhi   r25
#define str_lo  r20
#define str_hi  r21
#define base    r18
#define flags   r19

    /* Used     */
#define v_fifth r26     /* val: bits 39..32                     */
#define t_lo    r18     /* temporary for shifted `val'          */
#define t_hi    r19
#define t_hlo   r20
#define t_hhi   r21
#define symb    r20     /* write to string                      */
#define cnt     r27     /* shift loop counter, local arg        */

    /* Fixed    */
#define rzero   r1

        ASSEMBLY_CLIB_SECTION
        .global __ultoa_invert
        .type   __ultoa_invert, "function"

__ultoa_invert:
        X_movw  ZL, str_lo
        clr     v_fifth                 ; needed for all (ultoa_lsr)
        cpi     base, 8
        breq    .L_oct
        cpi     base, 16
        breq    .L_hex

  ; decimal format
        clt                             ; flag of val == 0
.L_dec_loop:
        push    v_lo                    ; to calculate remander
  ; val &= ~1
        andi    v_lo, ~1
  ; val += 2
        subi    v_lo, lo8(-2)
        sbci    v_hi, hi8(-2)
        sbci    v_hlo, hlo8(-2)
        sbci    v_hhi, hhi8(-2)
        sbci    v_fifth, hhi8(-2)
  ; val += val/2
        ldi     cnt, 1
        rcall   .L_div_add
  ; val += val/16
        ldi     cnt, 4
        rcall   .L_div_add
  ; val += val/256
        add     v_lo, v_hi
        adc     v_hi, v_hlo
        adc     v_hlo, v_hhi
        adc     v_hhi, v_fifth
        adc     v_fifth, rzero
  ; val += val/65536
        add     v_lo, v_hlo
        adc     v_hi, v_hhi
        adc     v_hlo, v_fifth
        adc     v_hhi, rzero
        adc     v_fifth, rzero
  ; val += val >> 32
        add     v_lo, v_fifth
        adc     v_hi, rzero
        adc     v_hlo, rzero
        adc     v_hhi, rzero
        adc     v_fifth, rzero
  ; division result:  val /= 16
        rcall   .L_lsr_4                ; v_fitth := 0
        brne    1f
        set                             ; T := Z flag
1:
  ; rem:  val_original - 10*val
        pop     t_hi
#if  defined(__AVR_ENHANCED__) && __AVR_ENHANCED__
        ldi     t_lo, 10
        mul     t_lo, v_lo
        clr     r1
#else
        mov     r0, v_lo
        lsl     r0
        sub     t_hi, r0
        lsl     r0
        lsl     r0
#endif
        sub     t_hi, r0
  ; output digit
        subi    t_hi, lo8(-'0')
        st      Z+, t_hi
  ; quotient == 0 ?
        brtc    .L_dec_loop
  ; end of string
.L_eos:
        X_movw  r24, ZL
        ret

  ; octal format
.L_oct:
        mov     symb, v_lo
        andi    symb, 7
        subi    symb, lo8(-'0')
        st      Z+, symb
        ldi     cnt, 3
        rcall   .L_lsr
        brne    .L_oct
        rjmp    .L_eos

  ; hex format
.L_hex:
        mov     symb, v_lo
        andi    symb, 0x0f
        subi    symb, lo8(-'0')
        cpi     symb, '9' + 1
        brlo    3f
        subi    symb, lo8('9' + 1 - 'a')
        sbrc    flags, ntz(XTOA_UPPER) - 8
        subi    symb, lo8('a' - 'A')
3:      st      Z+, symb
        rcall   .L_lsr_4
        brne    .L_hex
        rjmp    .L_eos
        
.L_lsr_4:
        ldi     cnt, 4
.L_lsr:
        lsr     v_fifth
        ror     v_hhi
        ror     v_hlo
        ror     v_hi
        ror     v_lo
        dec     cnt
        brne    .L_lsr
  ; tst
        sbiw    v_hlo, 0                ; only Z flag is needed
        cpc     v_lo, rzero
        cpc     v_hi, rzero
        ret

.L_div_add:
  ; copy to temporary
        X_movw  t_lo, v_lo
        X_movw  t_hlo, v_hlo
        mov     r0, v_fifth
  ; lsr temporary
7:      lsr     r0
        ror     t_hhi
        ror     t_hlo
        ror     t_hi
        ror     t_lo
        dec     cnt
        brne    7b
  ; add
        add     v_lo, t_lo
        adc     v_hi, t_hi
        adc     v_hlo, t_hlo
        adc     v_hhi, t_hhi
        adc     v_fifth, r0             ; here r0 == 0
        ret

        .size   __ultoa_invert, . - __ultoa_invert
        .end

#endif  /* !__DOXYGEN__ */

#endif /* !defined(__AVR_TINY__) */