Arduino Inline Assembly string Library

This is a refactoring of the existing Arduino (avr) C string library. This was simply an academic exercise. This code should compile to the same size, since in most respects it is the same code. The majority of the comments were just copied from the original source code. Note, that the function names have an underscore appended to them. Beware, some of the functions have not been tested. Therefore, please check functionality before using any of this code.

string_.h

// string_ header
#ifndef _STRING_H
#define _STRING_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

void* memchr_(const void* s, int c, size_t n);
int memcmp_(const void* s1, const void* s2, size_t n);
void* memcpy_(void* s1, const void* s2, size_t n);
void* memmove_(void*, const void*, size_t) __attribute__ ((naked)); 
void* memset_(void* s, int c, size_t n);
void* memmem_(const void* s1, size_t len1, const void* s2, size_t len2);
char* strcat_(char* s1, const char* s2);
char* strchr_(const char* s, int c);
int strcmp_(const char* s1, const char* s2);
char* strcpy_(char* s1, const char* s2);
size_t strcspn_(const char* s1, const char* s2);
inline size_t strlen_(const char* s) __attribute__ ((naked)); 
char* strncat_(char* s1, const char* s2, size_t n);
int strncmp_(const char* s1, const char* s2, size_t n);
char* strncpy_(char* s1, const char* s2, size_t n);
char* strpbrk_(const char* s1, const char* s2);
char* strrchr_(const char* s, int c);
size_t strspn_(const char* s1, const char* s2);
char* strstr_(const char* s1, const char* s2);
char* strtok_r_(char* s, const char* delim, char** last);
char* strtok_(char* s1, const char* s2);
char* _strtok(char *s, const char *delim);
int strncasecmp_(const char* s1, const char* s2, size_t n);
int strcasecmp_(const char* s1, const char* s2);  
char* strchrnul_(const char* s, int c);
char* strlwr_(char* s);
char* strupr_(char* s);
void* strrev_(char *s);
size_t strnlen_(const char* s, size_t maxlen);
char* strsep_(register char **stringp, register const char *delim);
char* strdup_(const char *s1);
size_t strlcpy_(char *dst, const char *src, size_t siz);
size_t strlcat_(char *dst, const char *src, size_t siz);

#ifdef __cplusplus
}
#endif
#endif

string_.c

#include "string_.h"

#ifdef __cplusplus
extern "C" {
#endif

// Find first occurrence of c in s[n].
void* memchr_(const void* s, int c, size_t n)
{
  register char* ret asm("r24");

  __asm__ __volatile__ (
    "1:                  \n"
    "subi  %A1, 1        \n"
    "sbci  %B1, 0        \n"
    "brcs  2f            \n"
    "ld  __tmp_reg__, Z+ \n"
    "cp  __tmp_reg__, %3 \n"
    "brne 1b             \n"
    "sbiw  ZL, 1         \n"
    "movw  %0, ZL        \n"
    "ret                 \n"
    "2:                  \n"
    "clr %A0             \n"
    "clr %B0             \n"
    : "=r" (ret) : "r" (n), "z" (s), "r" (c) : "memory"
  );
  
  return ret;
}


// Compare unsigned char s1[n], s2[n].
int memcmp_(const void* s1, const void* s2, size_t n) 
{
  register int ret asm("r24");

  __asm__ __volatile__ (
    "rjmp 2f              \n"
    "1:                   \n"
    "ld   %0, X+          \n"
    "ld   __tmp_reg__, Z+ \n"
    "sub  %0, __tmp_reg__ \n"
    "brne 3f              \n"
    "2:                   \n"
    "subi %A1, 1          \n"
    "sbci %B1, 0          \n"
    "brcc 1b              \n"
    // strings are equal, clear both r24 and carry
    "sub  %A0, %A0        \n"
    "3:                   \n"
    "sbc   %B0, %B0       \n"
    : "=r" (ret) : "r" (n), "z" (s2), "x" (s1)
  );
  
  return ret;
}


// Copy char s2[n] to s1[n] in any order.
void* memcpy_(void* dst, const void* src, size_t n)
{
  __asm__ __volatile__ (
    // 11 words, (13 + len * 8) cycles
    "rjmp 2f              \n"
    "1:                   \n"
    "ld   __tmp_reg__, Z+ \n"
    "st   X+, __tmp_reg__ \n"
    "2:                   \n"
    "subi %A0, 1          \n"
    "sbci %B0, 0          \n"
    "brcc 1b              \n"
      : : "r" (n), "z" (src), "x" (dst) : "memory"
  );
/*  
  __asm__ __volatile__ (
    // if OPTIMIZE_SPEED
    //15 words, (14 + len * 6 - (len & 1)) cycles
    "sbrs %0, 0           \n"
    "rjmp 3f              \n"
    "rjmp 2f              \n"
    "1:                   \n"
    "ld   __tmp_reg__, Z+ \n"
    "st   X+, __tmp_reg__ \n"
    "2:                   \n"
    "ld   __tmp_reg__, Z+ \n"
    "st   X+, __tmp_reg__ \n"
    "3:                   \n"
    "subi %A0, 2          \n"
    "sbci %B0, 0          \n"
    "brcc 1b              \n"
    : : "r" (n), "z" (src), "x" (dst) : "memory"
  );
*/  
}


// Copy char src[n] to dst[n] safely.
void* memmove_(void* dst, const void* src, size_t n)
{
  // if src < dest, we have to copy in reverse order
  // otherwise memcpy will do the right thing
  __asm__ __volatile__ (
    "cp   r22, r24 \n" // src, dst
    "cpc  r23, r25 \n"
    "brcc 3f       \n"
    
    "movw ZL, r22  \n" // src
    "movw XL, r24  \n" // dst
    "add  ZL, r20  \n" // len
    "adc  ZH, r21  \n"
    "add  XL, r20  \n"
    "adc  XH, r21  \n"
    "rjmp 2f       \n"
    
    "1:                   \n"
    "ld   __tmp_reg__, -Z \n"
    "st   -X, __tmp_reg__ \n"
    
    "2:           \n"
    "subi r20, 1  \n" // len
    "sbci r21, 0  \n"
    "brcc 1b      \n"
    // return dest (unchanged)
    "ret          \n"
    
    "3:           \n"
    "rjmp memcpy_ \n"
  );
}


// Store c throughout unsigned char s[n].
void* memset_(void* s, int c, size_t n)
{
/*
  __asm__ __volatile__ (
    // if OPTIMIZE_SPEED
    // 11 words, (12 + len * 4 - (len & 1)) cycles
    "sbrs %0, 0  \n" // n
    "rjmp 3f     \n"
    "rjmp 2f     \n"
    "1:          \n"
    "st   X+, %1 \n" // c
    "2:          \n"
    "st   X+, %1 \n  // c
    "3:          \n"
    "subi %A0, 2 \n" // n
    "sbci %B0, 0 \n"
    "brcc 1b     \n"
    : : "r" (n), "r" (c), "x" (s)
  );
*/
  __asm__ __volatile__ (
    // 8 words, (11 + len * 6) cycles
    "rjmp 2f     \n"
    "1:          \n"
    "st   X+, %1 \n"
    "2:          \n"
    "subi %A0, 1 \n" 
    "sbci %B0, 0 \n"
    "brcc 1b     \n"
    : : "r" (n), "r" (c), "x" (s)
  );
}

// Find start of first occurrence of substring s2 of length len2 in memory area s1 of length len1.
void* memmem_(const void* s1, size_t len1, const void* s2, size_t len2)
{
  register void* _s1 asm("r24");
  register void* _s2 asm("r20");
  register void* ret asm("r24");
  register uint8_t beg2 asm("r17"); // begin of s2: s2[0]
  register uint8_t c1 asm("r16");   // char from s1[]
  //__tmp_reg__ char from s2[]: tuned for classic lpm instr.

  __asm__ __volatile__ (
    "cp   %A1, __zero_reg__ \n"
    "cpc  %B1, __zero_reg__ \n"
    "breq .L_ret \n"     // s2[] is empty
  
    "add  %A1, %A5 \n"   // len2 = &(s2[len2])
    "adc  %B1, %B5 \n"
    "add  %A0, %A4 \n"   // len1 = &(s1[len1])
    "adc  %B0, %B4 \n"

    "movw ZL, %5 \n"
    "ld   %2, Z+ \n"     // beg2 = s2[0]
    "movw %5, ZL \n"     // save: address of s2[1]
  
    "1: \n" 
    "movw XL, %A6 \n"    // goto to begin of s1[]

    "2: \n"
    "cp   XL, %A0 \n"    // find first char that is matched
    "cpc  XH, %B0 \n"
    "brsh .L_nomatch \n"
    "ld   %3, X+ \n"
    "cp   %3, %2 \n"
    "brne 2b \n"

    "movw %A6, XL \n"    // store address

    "movw ZL, %5 \n"
    "3: \n" 
    "cp   ZL, %A1 \n"    // compare strings
    "cpc  ZH, %B1 \n"
    "brsh .L_match \n"   // end of s2[] --> OK
    "cp   XL, %A0 \n"
    "cpc  XH, %B0 \n"
    "brsh .L_nomatch \n" // s1[] tail is too short
    "ld   %3, X+ \n"
    "ld   __tmp_reg__, Z+ \n"
    "cp   %3, __tmp_reg__ \n"
    "breq 3b \n"
    "rjmp 1b \n"         // no equal
  
    ".L_nomatch: \n"
    "ldi  %A6, 1 \n"
    "ldi  %B6, 0 \n"
    ".L_match: \n"
    "sbiw %A6, 1 \n"     // restore after post-increment
    ".L_ret: \n"
     : : "r" (len1), "r" (len2), "r" (beg2), "r" (c1), "r" (_s1), "r" (_s2), "r" (ret) 
  );
}


// copy char s2[] to end of s1[].
char* strcat_(char* s1, const char* s2)
{
  __asm__ __volatile__ (
    "1:                   \n"
    "ld   __tmp_reg__, X+ \n"
    "tst  __tmp_reg__     \n"
    "brne 1b              \n"
    "sbiw XL, 1           \n"
    "2:                   \n"
    "ld   __tmp_reg__, Z+ \n"
    "st   X+, __tmp_reg__ \n"
    "tst  __tmp_reg__     \n"
    "brne 2b              \n"
    : : "z" (s2), "x" (s1)
  );
}


// find first occurrence of c in char s[].
char* strchr_(const char* s, int c)
{
  register char* ret asm("r24");
  
  __asm__ __volatile__ (
    "1: \n"
    "ld   %0, Z+ \n"
    "cp   %0, %2 \n"
    "breq 2f \n"
    "tst  %A0 \n"   
    "brne 1b \n"
    // not found, return NULL pointer
    "clr  %B0 \n"   
    "ret \n"
    "2: \n"
    "sbiw ZL, 1 \n" 
    "movw %0, ZL \n"
      : "=r" (ret) : "z" (s), "r" (c)
  );
  
  return ret; 
}


// compare unsigned char s1[], s2[].
int strcmp_(const char* s1, const char* s2)
{
  register int ret asm("r24");
  
  __asm__ __volatile__ (
    //movw ZL, r22 //s2
    //movw XL, r24 //s1
    "1: \n"
    "ld   %0, X+ \n"
    "ld   __tmp_reg__, Z+ \n"
    "sub  %0, __tmp_reg__ \n"
    "cpse __tmp_reg__, __zero_reg__ \n"
    "breq 1b \n"
    // ret_hi = SREG.C ? 0xFF : 0
    "sbc  %B0, %B0 \n"
      : "=r" (ret) : "z" (s2), "x" (s1)
  );
    
  return ret;
}


// copy char s2[] to s1[].
char* strcpy_(char* s1, const char* s2)
{
  __asm__ __volatile__ (
    // 9 words, (14 + strlen(src) * 7) cycles
    "1:                   \n"
    "ld   __tmp_reg__, Z+ \n"
    "st   X+, __tmp_reg__ \n"
    "tst  __tmp_reg__     \n"
    "brne 1b              \n"
    : : "z" (s2), "x" (s1)
  );
}


// find index of first s1[i] that matches any s2[].
size_t strcspn_(const char* s1, const char* s2)
{
  register size_t n asm("r24");
  register uint8_t chs asm("r21");
  
  __asm__ __volatile__(
    // get next symbol from s1[]
    "1:                             \n"
    "ld   %1, X+                    \n"   // *s++
    "tst  %1                        \n"
    "breq 3f                        \n"
    // check an occurance
    "movw ZL, r22                   \n" 
    "2:                             \n"
    "ld   __tmp_reg__, Z+           \n"
    "cp   __tmp_reg__, %1           \n"
    "cpse __tmp_reg__, __zero_reg__ \n"
    "brne 2b                        \n"
      // branch if chs is't present in reject[]
    "brne  1b                       \n"
    // end of scanning: chs is found or end of s[] is reached
    // Return: X - 1 - str == X + ~str
    "3:                             \n"
    "com  %A0                       \n"
    "com  %B0                       \n"
    "add  %A0, XL                   \n"
    "adc  %B0, XH                   \n"
    : "=r" (n), "=r" (chs) : "x" (s1) //, "z" (s2)
  );
}


// find length of s[].
size_t strlen_(const char* s)
{
  register size_t len asm("r24");
  
  __asm__ __volatile__(
    // 10 words, (14 + strlen(src) * 5) cycles
    "1:                   \n"
    "ld   __tmp_reg__, Z+ \n"
    "tst  __tmp_reg__     \n"
    "brne 1b              \n"
    // Z points one character past the terminating NUL
    // return Z - 1 - src = (-1 - src) + Z = ~src + Z
    "com %A0              \n" 
    "com %B0              \n" 
    "add %A0, ZL          \n" 
    "adc %B0, ZH          \n" 
    : "=r" (len) : "z" (s)
  );
}


// copy char s2[max n] to end of s1[].
char* strncat_(char *dst, const char *src, size_t len)
{
  __asm__ __volatile__(
    "1:                   \n"
    "ld   __tmp_reg__, X+ \n"
    "tst  __tmp_reg__     \n"
    "brne 1b              \n"
    "sbiw XL, 1           \n"  // undo post-increment (point the the NUL)
    "2:                   \n"
    "subi %A0, 1          \n" // len
    "sbci %B0, 0          \n"
    "brcs 3f              \n"
    "ld   __tmp_reg__, Z+ \n"
    "tst  __tmp_reg__     \n"
    "st   X+, __tmp_reg__ \n"
    "brne 2b              \n"
    // return dst (unchanged)
    "ret                  \n"
    "3:                   \n"
    "st   X, __zero_reg__ \n"
    : "=r" (len) : "x" (dst), "z" (src)
  );
}


// compare unsigned char s1[max n], s2[max n].
int strncmp_(const char *s1, const char *s2, size_t len)
{
  register int ret asm("r24");

  __asm__ __volatile__(
    "1:                    \n"
    "subi %A1, 1           \n" // len
    "sbci %B1, 0           \n"
    "brcs 2f               \n"
    "ld   %0, X+           \n" 
    "ld   __tmp_reg__, Z+  \n"
    "sub  %A0, __tmp_reg__ \n" 
    "brne 3f               \n"
    "tst  __tmp_reg__      \n"
    "brne 1b               \n"
    "2:                    \n"
    "sub  %A0, %A0         \n" // clear ret and C flag
    "3:                    \n"
    "sbc  %B0, %B0         \n"
    : "=r" (ret) :  "r" (len), "x" (s1), "z" (s2)
  );
  
  return ret;
}

// copy char s2[max n] to s1[n].
char* strncpy_(char *dst, const char *src, size_t len)
{
  __asm__ __volatile__(
    "1:                    \n"
    "subi %A0, 1           \n"
    "sbci %B0, 0           \n"
    "brcs 4f               \n"
    "ld   __tmp_reg__, Z+  \n"
    "st   X+, __tmp_reg__  \n"
    "tst  __tmp_reg__      \n"
    "brne 1b               \n"
    // store null characters up to the end of dst
    // as the glibc manual says: This behavior is rarely useful, but specified by ISO C standard.
    "rjmp 3f               \n"
    "2:                    \n"
    "st   X+, __zero_reg__ \n"
    "3:                    \n"
    "subi %A0, 1           \n"
    "sbci %B0, 0           \n"
    "brcc 2b               \n"
    "4:                    \n"
    : :  "r" (len), "x" (dst), "z" (src)
  );
}


// find index of first s1[i] that matches any s2[].
char* strpbrk_(const char *s, const char *accept)
{
  register char* ret asm("r24");

  __asm__ __volatile__(
    "clr  %B0                        \n" // a trick to save 1 word
    // get next symbol from s[]
    "1:                              \n"
    "ld   %0, X+                     \n" // ret, *s++
    "tst  %A0                        \n"
    "breq 3f                         \n"
    // check an occurance
    "movw ZL, r22                    \n"
    "2:                              \n"
    "ld   __tmp_reg__, Z+            \n"
    "cp   r0, r24                    \n"
    "cpse  __tmp_reg__, __zero_reg__ \n"
    "brne 2b                         \n"
    "brne 1b                         \n" // branch if end of accept[] is reached
    // OK, is found
    "sbiw XL, 1                      \n"
    "movw %A0, XL                    \n"
    "3:                              \n"
    : "=r" (ret) : "x" (s)
  );

  return ret;
}


// find last occurrence of c in char s[].
char* strrchr_(const char *src, int val)
{
  register char* ret asm("r24");

  __asm__ __volatile__(
  "ldi  %A0, 1           \n" // ret, NULL + 1
  "ldi  %B0, 0           \n"
  "1:                    \n"
  "ld   __tmp_reg__, Z+  \n"
  "cp   __tmp_reg__, %A2 \n" //val_lo
  "brne 2f               \n"
  "movw %0, ZL           \n" // ret, remember this character was here
  "2:                    \n"
  "tst  __tmp_reg__      \n"
  "brne 1b               \n"
  "sbiw %0, 1            \n" // ret, undo post-increment
  : "=r" (ret) : "z" (src), "r" (val)
  );

  return ret;
}

// find index of first s1[i] that matches no s2[].
size_t strspn_(const char *s, const char *accept)
{
  register size_t ret asm("r24");
  register uint8_t chs asm("r21"); // char from s[]
  
  __asm__ __volatile__(
// This realization is compact, but is not very fast: an accept string is not cached.
    // get next symbol from s[]
    "1:                              \n"
    "ld   %2, X+                     \n" // *s++
    "tst  %2                         \n"
    "breq 3f                         \n"
    // check an occurance
    "movw ZL, r22                    \n"
    "2:                              \n"
    "ld   __tmp_reg__, Z+            \n"
    "cp   __tmp_reg__, %2            \n"
    "cpse  __tmp_reg__, __zero_reg__ \n"
    "brne 2b                         \n"
    "breq 1b                         \n" // branch if chs is present in accept[]
    // end of scanning: chs is not found or end of s[] is reached
    // Return: X - 1 - str == X + ~str
    "3:                              \n"
    "com  %A0                        \n"
    "com  %B0                        \n"
    "add  %A0, XL                    \n"
    "adc  %B0, XH                    \n"
    : "=r" (ret) : "x" (s), "r" (chs)
  );
  
  return ret;
}


// find first occurrence of s2[] in s1[].
char* strstr_(const char *s1, const char *s2)
{
  register char* ret asm("r24");
  register uint8_t chr1 asm("r20"); 
  register uint8_t beg2 asm("r21"); // begin of s2: s2[0]
  
  __asm__ __volatile__(
    "ld   %3, Z+           \n"
    "tst  %3               \n" // is str2 empty?
    "breq done             \n" // return original string (req'd by standard)
    "movw r22, ZL          \n" // save: address of second s2 byte
    "0:                    \n"
    "movw XL, r24          \n" // s1
    "1:                    \n"
    "ld   %2, X+           \n" // Find first char
    "cp   %2, %3           \n"
    "cpse %2, __zero_reg__ \n"
    "brne 1b               \n"
    "brne no_match         \n" // end of s1
    "movw %0, XL           \n" // store return value
    "2:                    \n"
    "ld   __tmp_reg__, Z+  \n" // compare strings
    "tst  __tmp_reg__      \n"
    "breq match            \n" // end of s2
    "ld   %2, X+           \n"
    "cp   %2, __tmp_reg__  \n"
    "cpse %2, __zero_reg__ \n" // break, if end of s1
    "breq 2b               \n"
    "movw ZL, r22          \n" // restore s2+1
    "cpse %2, __zero_reg__ \n"
    "rjmp 0b               \n"
    "no_match:             \n"
    "ldi  %A0, 1           \n" // s1
    "ldi  %B0, 0           \n"
    "match:                \n"
    "sbiw %0, 1            \n" // restore after post-increment
    "done:                 \n"
    : "=r" (ret) : "z" (s2), "r" (chr1), "r" (beg2)
  );
    
  return ret;
}


// find next token in s1[] delimited by s2[].
char* strtok_r_(char *str, const char *delim, char **last)
{
  register uint8_t dch asm("r18");
  register char* _dlm asm("r22");
  register char* _str asm("r24");

  __asm__ __volatile__(
    "ld   XL, Z+           \n" // X = *last
    "ld   XH, Z            \n"
    // check str
    "sbiw %3, 0            \n" // str
    "brne 1f               \n"
    "sbiw XL, 0            \n"
    "breq 9f               \n" // end of string
    "movw %3, XL           \n" // continue parsing
    // skip delimeters
    "1:                    \n"
    "movw XL, %3           \n" // p = str
    "2:                    \n"
    "movw %3, XL           \n"
    "ld   __tmp_reg__, X+  \n"
    "tst  __tmp_reg__      \n"
    "brne 3f               \n"
    "movw %3, __tmp_reg__  \n" // <r0, r1>
    "rjmp 8f               \n"
    "3:                    \n"
    "movw ZL, %2           \n" // delim
    "4:                    \n"
    "ld   %1, Z+           \n" // dch
    "tst  %1               \n" // dch
    "breq 5f               \n" // goto find
    "cp   %1, __tmp_reg__  \n" // dch
    "brne 4b               \n"
    "rjmp 2b               \n" // skip 1 byte
    // find new token end
    "5:                    \n" 
    "movw ZL, %2           \n" // delim
    "6:                    \n"
    "ld   %1, Z+           \n" // dch
    "cp   %1, __tmp_reg__  \n" // dch, __tmp_reg__ != 0
    "brne 7f               \n"
    "st   -X, __zero_reg__ \n"
    "adiw XL, 1            \n"
    "rjmp 9f               \n"
    "7:                    \n"
    "tst  %1               \n" // dch
    "brne 6b               \n"
    // next str byte
    "ld   __tmp_reg__, X+  \n"
    "tst  __tmp_reg__      \n"
    "brne 5b               \n"
    // stop parsing
    "8:                    \n"
    "movw XL, __tmp_reg__  \n" // <r0,r1>
    // save last pointer
    "9:                    \n"
    "movw ZL, r20          \n" // *last = X
    "st   Z+, XL           \n"
    "st   Z, XH            \n"
    : : "z" (last), "r" (dch), "r" (_dlm), "r" (_str)
  );
}

static char *p;

char* _strtok(char *s, const char *delim) { return strtok_r_(s, delim, &p); } 


// compare caseless unsigned char s1[max n], s2[max n].
int strncasecmp_(const char* s1, const char* s2, size_t len)
{
  register uint8_t ret asm("r24");
  register uint8_t tmp asm("r22");
  
  __asm__ __volatile__ (
    "1:                    \n"
    "subi %A2, 1           \n" // if (--len == -1) return 0
    "sbci %B2, 0           \n"
    "brlo 5f               \n"
    "ld   %A3, X+          \n" // *s1++
    "cpi  %A3, 'A'         \n" // if in [A-Z] then tolower()
    "brlt 2f               \n"
    "cpi  %A3, 'Z' + 1     \n"
    "brge 2f               \n"
    "subi %A3, 'A' - 'a'    \n"
    "2:                    \n"
    "ld   %4, Z+           \n" // *s2++
    "cpi  %4, 'A'          \n" // if in [A-Z] then tolower()
    "brlt 3f               \n"
    "cpi  %4, 'Z' + 1      \n"
    "brge 3f               \n"
    "subi %4, 'A' - 'a'    \n"
    "3:                    \n"
    "sub  %A3, %4          \n" // compare
    "cpse %4, __zero_reg__ \n" // break, if end of string
    "breq 1b               \n"
    "4:                    \n"
    "sbc  %B3, %B3         \n" // sign extension
    "ret                   \n"
    "5:                    \n"
    "sub  %A3, %A3         \n" // length limit, return 0
    "rjmp 4b               \n"
    : : "x" (s1), "z" (s2), "r" (len), "r" (ret), "r" (tmp)
  );
}


// Compare two strings ignoring case.
int strcasecmp_(const char *s1, const char *s2)
{
  register uint8_t ret asm("r24");
  register uint8_t tmp asm("r22");
  
  __asm__ __volatile__ (
    "1:                    \n"
    "ld   %A2, X+          \n" // *s1++
    "cpi  %A2, 'A'         \n" // if in [A-Z] then tolower()
    "brlt 2f               \n"
    "cpi  %A2, 'Z' + 1     \n"
    "brge 2f               \n"
    "subi %A2, 'A' - 'a'    \n"
    "2:                    \n"
    "ld   %3, Z+           \n" // *s2++
    "cpi  %3, 'A'          \n" // if in [A-Z] then tolower()
    "brlt 3f               \n"
    "cpi  %3, 'Z' + 1      \n"
    "brge 3f               \n"
    "subi %3, 'A' - 'a'    \n"
    "3:                    \n"
    "sub  %A2, %3          \n" // compare
    "cpse %3, __zero_reg__ \n" // break, if end of string
    "breq 1b               \n"
    "sbc  %B2, %B2         \n" // sign extension
    : : "x" (s1), "z" (s2), "r" (ret), "r" (tmp)
  );
}


// Like strchr() except that if c is not found in s, then it returns a pointer to the null byte at the end of s, rather than NULL.
char* strchrnul_(const char *s, int c)
{
  register char* ret asm("r24");

  __asm__ __volatile__ (
    "1:                             \n"  
    "ld   __tmp_reg__, Z+           \n"
    "cp   __tmp_reg__, %2           \n"
    "cpse __tmp_reg__, __zero_reg__ \n"
    "brne 1b                        \n"
    "sbiw ZL, 1                     \n"  // undo post-increment
    "movw %1, ZL                    \n"
    : : "z" (s), "r" (ret), "r" (c)
  );
}


// Convert a string to lower case.
char* strlwr_(char *s)
{
  register uint8_t temp asm("r22");

  __asm__ __volatile__ (
    "1:   \n"
    "ld   %1, X \n"
    "subi %1, 'A' \n"
    "cpi  %1, 'Z' - 'A' + 1 \n"
    "brlo 2f \n"               // if temp is A..Z, then temp += 'a'-'A'
    "subi %1, 'a' - 'A' \n"    // else restore temp
    "2:   \n"
    "subi %1, -'a' \n"
    "st   X+, %1 \n"
    "brne 1b \n"               // Z for temp
    : : "x" (s), "r" (temp)
  );
}


// Convert a string to upper case.
char *strupr_(char *s)
{
  register uint8_t temp asm("r22");

  __asm__ __volatile__ (
    "1: \n"
    "ld   %1, X \n"
    "subi %1, 'a' \n"
    "cpi  %1, 'z' - 'a' + 1 \n"
    "brlo 2f \n"             // if temp is a..z, then temp += 'A'-'a'
    "subi %1, 'A' - 'a' \n"  // else restore temp
    "2: \n"
    "subi %1, -'A' \n"
    "st   X+, %1 \n"
    "brne 1b \n"             // Z for temp
    : : "x" (s), "r" (temp)
  );
}


// Reverse the order of the string.
char* strrev_(char *s)
{
  register uint8_t rtmp asm("r22");
  register uint8_t ltmp asm("r23");
  
  __asm__ __volatile__ (
    // find end of string
    "1:           \n" 
    "mov  %1, %2  \n" // to obtain right nonzero character
    "ld   %2, Z+  \n"
    "tst  %2      \n"
    "brne 1b      \n"
    "sbiw ZL, 2   \n" // to last nonzero byte
    "rjmp 3f      \n"
    // swap bytes
    "2:           \n"
    "ld   %2, X   \n"
    "st   X+, %1  \n"
    "st   Z, %2   \n"
    "ld   %1, -Z  \n"
    "3:           \n"
    "cp   XL, ZL  \n"
    "cpc  XH, ZH  \n"
    "brlo 2b      \n"
    : : "z" (s), "r" (rtmp), "r" (ltmp)
  );
}


// Returns the number of chars in string pointed to by src, not including the terminating '\0' char, but at most len.
size_t strnlen_(const char* src, size_t len)
{
  register size_t ret asm("r24");

  __asm__ __volatile__ (
    "1: \n"
    "subi %A1, 1 \n"
    "sbci %B1, 0 \n"
    "ld  __tmp_reg__, Z+ \n"
    "cpse  __tmp_reg__, __zero_reg__ \n"
    "brcc 1b \n"
    // Z points one character past the terminating NUL
    // return Z - 1 - src = (-1 - src) + Z = ~src + Z
    "com  %A2 \n"
    "com  %B2 \n"
    "add  %A2, ZL \n"
    "adc  %B2, ZH \n"
    : : "z" (src), "r" (len), "r" (ret)
  );
}


// Parse a string into tokens.
char* strsep_(char **sp, const char *delim)
{
  register char* str asm("r20");
  register uint8_t chr asm("r19");
  
  __asm__ __volatile__ (
    // check a NULL pointer
    "ld   XL, Z            \n" // str address
    "ldd  XH, Z + 1        \n"
    "movw %1, XL           \n"// save for return
    "adiw XL, 0            \n"
    "breq 5f               \n" // return NULL
    // get a symbol from str
    "1:                    \n"
    "ld   %2, X+           \n"
    // scan delim[]
    "movw ZL, r22          \n" // delim
    "2:                    \n"
    "ld   __tmp_reg__, Z+  \n"
    "cp   __tmp_reg__, %2  \n"
    "cpse  __tmp_reg__, __zero_reg__ \n"
    "brne 2b \n" // if symbol is't match && no delim end
    "brne 1b \n" // if symbol is absent in delim[] && not a zero
    // chr is founded in delim[] (possible, it is a terminating zero of str)
    "tst  __tmp_reg__      \n" // the same, as chr
    "brne 3f               \n"
    "movw XL, __tmp_reg__  \n"
    "rjmp 4f               \n"
    // OK, delimeter symbol is founded
    "3:                    \n"
    "st   -X, __zero_reg__ \n" // replace by '\0'
    "adiw XL, 1            \n" // address of next token
    // save result to *sp and return
    "4:                    \n"
    "movw ZL, r24          \n"
    "st   Z, XL            \n"
    "std  Z+1, XH          \n"
    "5:                    \n"
    "movw r24, %1          \n" // return original address
    : : "z" (sp), "r" (str), "r" (chr)
  );
}


// Allocate memory and copy string into it, including the terminating null character.
char* strdup_(const char *s)
{
  __asm__ __volatile__ (
    "1: \n"
    "ld __tmp_reg__, Z+ \n"
    "tst __tmp_reg__ \n"
    "brne 1b \n"
    "com r24 \n"
    "com r25 \n"
    "add r24, r30 \n"
    "adc r25, r31 \n"
    "ldi r24, 1 \n"
    "ldi r25, 0 \n"
    "call malloc \n"
    "sbiw r24, 0 \n"
    "breq 3f \n"
    "movw %0, %1 \n"
    "movw r26, r24 \n"
    "2: \n"
    "ld __tmp_reg__, Z+ \n"
    "st X+, __tmp_reg__ \n"
    "tst __tmp_reg__ \n"
    "brne 2b \n"
    "3: \n"
    : : "z" (s), "y" (s)
  );
}


// Copy src to string dst of size siz.
size_t strlcpy_(char *dst, const char *src, size_t siz)
{
  register size_t ret asm("r24");

  __asm__ __volatile__ (
    // copy loop
    "1:   \n"
    "subi %A2, 1 \n"
    "sbci %B2, 0 \n"
    "brcs 4f \n"     // is possible with siz == 0
    "breq 3f \n"     // --> siz chars copied
    "ld  __tmp_reg__, Z+ \n"
    "st  X+, __tmp_reg__ \n"
    "tst __tmp_reg__ \n"
    "brne 1b \n"
    // calculate result (Z - 1 - src) and return
    "2: \n"
    "sub  ZL, %A1 \n"
    "sbc  ZH, %B1 \n"
    "sbiw ZL, 1 \n"
    "movw %A3, ZL \n"
    "ret \n"
    // terminate dst
    "3: \n"
    "st  X, __zero_reg__ \n"
    // find src end
    "4:   \n"
    "ld  __tmp_reg__, Z+ \n"
    "tst __tmp_reg__ \n"
    "brne 4b \n"
    "rjmp  2b \n"
    : : "x" (dst), "z" (src), "r" (siz), "r" ("ret")
  );
}



// Append src to string dst of size siz.
size_t strlcat_(char *dst, const char *src, size_t siz)
{
  register size_t ret asm("r24");

  __asm__ __volatile__ (
    // find end of dst: X := dst + strlen(dsr)
    "1:                   \n"
    "subi %A2, 1          \n"
    "sbci %B2, 0          \n"
    "brlo .Len            \n" // siz <= strlen(dst)
    "ld  __tmp_reg__, X+  \n"
    "tst __tmp_reg__      \n"
    "brne 1b              \n"
    "sbiw XL, 1           \n"
    "rjmp 3f              \n"
    // copy loop
    "2:                   \n"
    "ld  __tmp_reg__, Z+  \n"
    "st  X+, __tmp_reg__  \n"
    "tst __tmp_reg__      \n"
    "breq .Ldd            \n"
    "3:                   \n"
    "subi %A2, 1          \n"
    "sbci %B2, 2          \n"
    "brsh 2b              \n"
    "st   X, __zero_reg__ \n"
    // return (d - dst + strlen(s))
    "movw r22, ZL         \n" // update for strlen(s) calculation
    ".Len:                \n"
    "ld  __tmp_reg__, Z+  \n" // find end of src
    "tst __tmp_reg__      \n"
    "brne .Len            \n"
    "sub  ZL, %A2         \n" // Z := strlen(s) + 1
    "sbc  ZH, %B2         \n"
    "add  XL, ZL          \n" // d += strlen(s) + 1
    "adc  XH, ZH          \n"
    ".Ldd:                \n"
    "sec                  \n" // d -= dst + 1
    "sbc  XL, %A3         \n"
    "sbc  XH, %B3         \n"
    "movw %3, XL          \n" // return value
    : : "x" (dst), "z" (src), "r" (siz), "r" ("ret")
  );
}

#ifdef __cplusplus
}
#endif

About Jim Eli

µC experimenter
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment