2894 lines
76 KiB
C
2894 lines
76 KiB
C
/*-------------------------------------------------------------------------
|
|
SDCCpeeph.c - The peep hole optimizer: for interpreting the
|
|
peep hole rules
|
|
|
|
Copyright (C) 1999, Sandeep Dutta . sandeep.dutta@usa.net
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation; either version 2, or (at your option) any
|
|
later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
-------------------------------------------------------------------------*/
|
|
|
|
#include "common.h"
|
|
#include "dbuf_string.h"
|
|
|
|
#define ISCHARDIGIT(c) isdigit((unsigned char)c)
|
|
#define ISCHARSPACE(c) isspace((unsigned char)c)
|
|
#define ISCHARALNUM(c) isalnum((unsigned char)c)
|
|
|
|
static peepRule *rootRules = NULL;
|
|
static peepRule *currRule = NULL;
|
|
|
|
#define HTAB_SIZE 53
|
|
|
|
hTab *labelHash = NULL;
|
|
|
|
static struct
|
|
{
|
|
allocTrace values;
|
|
allocTrace labels;
|
|
} _G;
|
|
|
|
static int hashSymbolName (const char *name);
|
|
static void buildLabelRefCountHash (lineNode * head);
|
|
static void bindVar (int key, char **s, hTab ** vtab);
|
|
|
|
static bool matchLine (char *, const char *, hTab **);
|
|
|
|
#define FBYNAME(x) static int x (hTab *vars, lineNode *currPl, lineNode *endPl, \
|
|
lineNode *head, char *cmdLine)
|
|
|
|
#if !OPT_DISABLE_PIC14
|
|
void peepRules2pCode(peepRule *);
|
|
#endif
|
|
|
|
#if !OPT_DISABLE_PIC16
|
|
void pic16_peepRules2pCode(peepRule *);
|
|
#endif
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* getPatternVar - finds a pattern variable */
|
|
/*-----------------------------------------------------------------*/
|
|
|
|
static char*
|
|
getPatternVar (hTab *vars, char **cmdLine)
|
|
{
|
|
int varNumber;
|
|
char *digitend;
|
|
|
|
if (!cmdLine || !*cmdLine || !**cmdLine)
|
|
return NULL; /* no parameters given */
|
|
|
|
while (**cmdLine && ISCHARSPACE(**cmdLine))
|
|
(*cmdLine)++; /* skip whitespace */
|
|
|
|
if (**cmdLine != '%')
|
|
goto error;
|
|
(*cmdLine)++;
|
|
if (!ISCHARDIGIT (**cmdLine))
|
|
goto error;
|
|
varNumber = strtol (*cmdLine, &digitend, 10);
|
|
*cmdLine = digitend;
|
|
return hTabItemWithKey (vars, varNumber);
|
|
|
|
error:
|
|
fprintf (stderr,
|
|
"*** internal error: peephole restriction malformed: %s\n", *cmdLine);
|
|
return NULL;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* pcDistance - finds a label backward or forward */
|
|
/*-----------------------------------------------------------------*/
|
|
|
|
static int
|
|
pcDistance (lineNode *cpos, char *lbl, bool back)
|
|
{
|
|
lineNode *pl = cpos;
|
|
char buff[MAX_PATTERN_LEN];
|
|
int dist = 0;
|
|
|
|
SNPRINTF (buff, sizeof(buff) - 1, "%s:", lbl);
|
|
while (pl)
|
|
{
|
|
if (pl->line &&
|
|
!pl->isComment &&
|
|
!pl->isLabel &&
|
|
!pl->isDebug)
|
|
{
|
|
if (port->peep.getSize)
|
|
{
|
|
dist += port->peep.getSize(pl);
|
|
#if 0
|
|
fprintf(stderr, "Line: %s, dist: %i, total: %i\n", pl->line, port->peep.getSize(pl), dist);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
dist += 4; // maximum instruction size
|
|
}
|
|
}
|
|
|
|
if (strncmp (pl->line, buff, strlen (buff)) == 0)
|
|
return dist;
|
|
|
|
if (back)
|
|
pl = pl->prev;
|
|
else
|
|
pl = pl->next;
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* portIsDS390 - return true if port is DS390 */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (portIsDS390)
|
|
{
|
|
return ((strcmp(port->target,"ds390") == 0) ||
|
|
(strcmp(port->target,"ds400") == 0));
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* flat24bitMode - will check to see if we are in flat24 mode */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (flat24bitMode)
|
|
{
|
|
return (options.model == MODEL_FLAT24);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* xramMovcOption - check if using movc to read xram */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (xramMovcOption)
|
|
{
|
|
return (options.xram_movc && (strcmp(port->target,"mcs51") == 0));
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* useAcallAjmp - Enable replacement of lcall/ljmp with acall/ajmp */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (useAcallAjmp)
|
|
{
|
|
return (options.acall_ajmp && (strcmp(port->target,"mcs51") == 0));
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* labelInRange - will check to see if label is within range */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (labelInRange)
|
|
{
|
|
int dist = 0;
|
|
char *lbl = getPatternVar (vars, &cmdLine);
|
|
|
|
if (!lbl)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: labelInRange peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
|
|
/* If no parameters given, assume that %5 pattern variable
|
|
has the label name for backward compatibility */
|
|
lbl = hTabItemWithKey (vars, 5);
|
|
}
|
|
|
|
if (!lbl)
|
|
return FALSE;
|
|
|
|
do
|
|
{
|
|
/* Don't optimize jumps in a jump table; a more generic test */
|
|
if (currPl->ic && currPl->ic->op == JUMPTABLE)
|
|
return FALSE;
|
|
|
|
/* if the previous two instructions are "ljmp"s then don't
|
|
do it since it can be part of a jump table */
|
|
if (currPl->prev && currPl->prev->prev &&
|
|
strstr (currPl->prev->line, "ljmp") &&
|
|
strstr (currPl->prev->prev->line, "ljmp"))
|
|
return FALSE;
|
|
|
|
/* Calculate the label distance. For mcs51 the jump can be
|
|
-127 to + 127 bytes, for Z80 -126 to +129 bytes.*/
|
|
dist = (pcDistance (currPl, lbl, TRUE) +
|
|
pcDistance (currPl, lbl, FALSE));
|
|
|
|
/* Use 125 for now. Could be made more exact using port and
|
|
exact jump location instead of currPl. */
|
|
if (!dist || dist > 127)
|
|
return FALSE;
|
|
|
|
lbl = getPatternVar (vars, &cmdLine);
|
|
}
|
|
while (lbl);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* labelJTInRange - will check to see if label %5 and up are */
|
|
/* within range. */
|
|
/* Specifically meant to optimize long (3-byte) jumps to short */
|
|
/* (2-byte) jumps in jumptables */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (labelJTInRange)
|
|
{
|
|
char *lbl;
|
|
int dist, count, i;
|
|
|
|
/* Only optimize within a jump table */
|
|
if (currPl->ic && currPl->ic->op != JUMPTABLE)
|
|
return FALSE;
|
|
|
|
count = elementsInSet( IC_JTLABELS (currPl->ic) );
|
|
|
|
/* check all labels (this is needed if the case statements are unsorted) */
|
|
for (i=0; i<count; i++)
|
|
{
|
|
/* assumes that the %5 pattern variable has the first ljmp label */
|
|
lbl = hTabItemWithKey (vars, 5+i);
|
|
if (!lbl)
|
|
return FALSE;
|
|
|
|
dist = (pcDistance (currPl, lbl, TRUE) +
|
|
pcDistance (currPl, lbl, FALSE));
|
|
|
|
/* three terms used to calculate allowable distance */
|
|
/* Could be made more exact and port-specific. */
|
|
if (!dist ||
|
|
dist > 127+ /* range of sjmp */
|
|
(3+3*i)+ /* offset between this jump and currPl,
|
|
should use pcDistance instead? */
|
|
(count-i-1) /* if peephole applies distance is shortened */
|
|
)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* optimizeReturn - is it allowed to optimize RET instructions */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (optimizeReturn)
|
|
{
|
|
return (options.peepReturn >= 0);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* labelIsReturnOnly - Check if label is followed by ret */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (labelIsReturnOnly)
|
|
{
|
|
/* assumes that %5 pattern variable has the label name */
|
|
const char *label, *p;
|
|
const lineNode *pl;
|
|
int len;
|
|
char * retInst;
|
|
|
|
/* Don't optimize jumps in a jump table; a more generic test */
|
|
if (currPl->ic && currPl->ic->op == JUMPTABLE)
|
|
return FALSE;
|
|
|
|
if (!(label = getPatternVar (vars, &cmdLine)))
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: labelIsReturnOnly peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
|
|
/* If no parameters given, assume that %5 pattern variable
|
|
has the label name for backward compatibility */
|
|
label = hTabItemWithKey (vars, 5);
|
|
}
|
|
|
|
if (!label)
|
|
return FALSE;
|
|
len = strlen(label);
|
|
|
|
for(pl = currPl; pl; pl = pl->next)
|
|
{
|
|
if (pl->line && !pl->isDebug && !pl->isComment && pl->isLabel)
|
|
{
|
|
if (strncmp(pl->line, label, len) == 0)
|
|
break; /* Found Label */
|
|
if (strlen(pl->line) != 7 || !ISCHARDIGIT(*(pl->line)) ||
|
|
!ISCHARDIGIT(*(pl->line+1)) || !ISCHARDIGIT(*(pl->line+2)) ||
|
|
!ISCHARDIGIT(*(pl->line+3)) || !ISCHARDIGIT(*(pl->line+4)) ||
|
|
*(pl->line+5) != '$')
|
|
{
|
|
return FALSE; /* non-local label encountered */
|
|
}
|
|
}
|
|
}
|
|
if (!pl)
|
|
return FALSE; /* did not find the label */
|
|
pl = pl->next;
|
|
while (pl && (pl->isDebug || pl->isComment || pl->isLabel))
|
|
pl = pl->next;
|
|
if (!pl || !pl->line || pl->isDebug)
|
|
return FALSE; /* next line not valid */
|
|
p = pl->line;
|
|
for (p = pl->line; *p && ISCHARSPACE(*p); p++)
|
|
;
|
|
|
|
retInst = "ret";
|
|
if (TARGET_HC08_LIKE)
|
|
retInst = "rts";
|
|
if (strcmp(p, retInst) == 0)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* labelIsUncondJump - Check if label %5 is followed by an */
|
|
/* unconditional jump and put the destination of that jump in %6 */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (labelIsUncondJump)
|
|
{
|
|
/* assumes that %5 pattern variable has the label name */
|
|
const char *label;
|
|
char *p, *q;
|
|
const lineNode *pl;
|
|
bool found = FALSE;
|
|
int len;
|
|
char * jpInst = NULL;
|
|
char * jpInst2 = NULL;
|
|
|
|
label = hTabItemWithKey (vars, 5);
|
|
if (!label)
|
|
return FALSE;
|
|
len = strlen(label);
|
|
|
|
for (pl = currPl; pl; pl = pl->prev)
|
|
{
|
|
if (pl->line && !pl->isDebug && !pl->isComment && pl->isLabel)
|
|
{
|
|
if (strncmp(pl->line, label, len) == 0)
|
|
{
|
|
found = TRUE;
|
|
break; /* Found Label */
|
|
}
|
|
if (strlen(pl->line) != 7 || !ISCHARDIGIT(*(pl->line)) ||
|
|
!ISCHARDIGIT(*(pl->line+1)) || !ISCHARDIGIT(*(pl->line+2)) ||
|
|
!ISCHARDIGIT(*(pl->line+3)) || !ISCHARDIGIT(*(pl->line+4)) ||
|
|
*(pl->line+5) != '$')
|
|
{
|
|
break; /* non-local label encountered */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
for (pl = currPl; pl; pl = pl->next)
|
|
{
|
|
if (pl->line && !pl->isDebug && !pl->isComment && pl->isLabel)
|
|
{
|
|
if (strncmp(pl->line, label, len) == 0)
|
|
{
|
|
found = TRUE;
|
|
break; /* Found Label */
|
|
}
|
|
if (strlen(pl->line) != 7 || !ISCHARDIGIT(*(pl->line)) ||
|
|
!ISCHARDIGIT(*(pl->line+1)) || !ISCHARDIGIT(*(pl->line+2)) ||
|
|
!ISCHARDIGIT(*(pl->line+3)) || !ISCHARDIGIT(*(pl->line+4)) ||
|
|
*(pl->line+5) != '$')
|
|
{
|
|
return FALSE; /* non-local label encountered */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pl || !found)
|
|
return FALSE; /* did not find the label */
|
|
pl = pl->next;
|
|
while (pl && (pl->isDebug || pl->isComment))
|
|
pl = pl->next;
|
|
if (!pl || !pl->line)
|
|
return FALSE; /* next line not valid */
|
|
p = pl->line;
|
|
while (*p && ISCHARSPACE(*p))
|
|
p++;
|
|
|
|
if (TARGET_MCS51_LIKE)
|
|
{
|
|
jpInst = "ljmp";
|
|
jpInst2 = "sjmp";
|
|
}
|
|
else if (TARGET_HC08_LIKE)
|
|
{
|
|
jpInst = "jmp";
|
|
jpInst2 = "bra";
|
|
}
|
|
else if (TARGET_Z80_LIKE)
|
|
{
|
|
jpInst = "jp";
|
|
jpInst2 = "jr";
|
|
}
|
|
else if (TARGET_IS_STM8)
|
|
{
|
|
jpInst = (options.model == MODEL_LARGE ? "jpf" : "jp");
|
|
jpInst2 = "jra";
|
|
}
|
|
else if (TARGET_PDK_LIKE)
|
|
{
|
|
jpInst = "goto";
|
|
}
|
|
len = strlen(jpInst);
|
|
if (strncmp(p, jpInst, len))
|
|
{
|
|
len = jpInst2 ? strlen(jpInst2) : 0;
|
|
if(!jpInst2 || strncmp(p, jpInst2, len))
|
|
return FALSE; /* next line is no jump */
|
|
}
|
|
|
|
p += len;
|
|
while (*p && ISCHARSPACE(*p))
|
|
p++;
|
|
|
|
q = p;
|
|
while (*q && *q!=';')
|
|
q++;
|
|
while (q>p && ISCHARSPACE(*q))
|
|
q--;
|
|
len = q-p;
|
|
if (len == 0)
|
|
return FALSE; /* no destination? */
|
|
|
|
if (TARGET_Z80_LIKE)
|
|
{
|
|
while (q>p && *q!=',')
|
|
q--;
|
|
if (*q==',')
|
|
return FALSE; /* conditional jump */
|
|
}
|
|
|
|
/* now put the destination in %6 */
|
|
bindVar (6, &p, &vars);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* okToRemoveSLOC - Check if label %1 is a SLOC and not other */
|
|
/* usage of it in the code depends on a value from this section */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (okToRemoveSLOC)
|
|
{
|
|
const lineNode *pl;
|
|
const char *sloc, *p;
|
|
int dummy1, dummy2, dummy3;
|
|
|
|
/* assumes that %1 as the SLOC name */
|
|
sloc = hTabItemWithKey (vars, 1);
|
|
if (sloc == NULL) return FALSE;
|
|
p = strstr(sloc, "sloc");
|
|
if (p == NULL) return FALSE;
|
|
p += 4;
|
|
if (sscanf(p, "%d_%d_%d", &dummy1, &dummy2, &dummy3) != 3) return FALSE;
|
|
/*TODO: ultra-paranoid: get funtion name from "head" and check that */
|
|
/* the sloc name begins with that. Probably not really necessary */
|
|
|
|
/* Look for any occurance of this SLOC before the peephole match */
|
|
for (pl = currPl->prev; pl; pl = pl->prev) {
|
|
if (pl->line && !pl->isDebug && !pl->isComment
|
|
&& *pl->line != ';' && strstr(pl->line, sloc))
|
|
return FALSE;
|
|
}
|
|
/* Look for any occurance of this SLOC after the peephole match */
|
|
for (pl = endPl->next; pl; pl = pl->next) {
|
|
if (pl->line && !pl->isDebug && !pl->isComment
|
|
&& *pl->line != ';' && strstr(pl->line, sloc))
|
|
return FALSE;
|
|
}
|
|
return TRUE; /* safe for a peephole to remove it :) */
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* deadMove - Check, if a pop/push pair can be removed */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (deadMove)
|
|
{
|
|
const char *reg = hTabItemWithKey (vars, 1);
|
|
|
|
if (port->peep.deadMove)
|
|
return port->peep.deadMove (reg, currPl, head);
|
|
|
|
fprintf (stderr, "Function deadMove not initialized in port structure\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* labelHashEntry- searches for a label in the list labelHash */
|
|
/* Builds labelHash, if it does not yet exist. */
|
|
/* Returns the labelHashEntry or NULL */
|
|
/*-----------------------------------------------------------------*/
|
|
labelHashEntry *
|
|
getLabelRef (const char *label, lineNode *head)
|
|
{
|
|
labelHashEntry *entry;
|
|
|
|
/* If we don't have the label hash table yet, build it. */
|
|
if (!labelHash)
|
|
{
|
|
buildLabelRefCountHash (head);
|
|
}
|
|
|
|
entry = hTabFirstItemWK (labelHash, hashSymbolName (label));
|
|
|
|
while (entry)
|
|
{
|
|
if (!strcmp (label, entry->name))
|
|
{
|
|
break;
|
|
}
|
|
entry = hTabNextItemWK (labelHash);
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
/* labelRefCount:
|
|
|
|
* takes two parameters: a variable (bound to a label name)
|
|
* and an expected reference count.
|
|
*
|
|
* Returns TRUE if that label is defined and referenced exactly
|
|
* the given number of times.
|
|
*/
|
|
FBYNAME (labelRefCount)
|
|
{
|
|
int varNumber, expectedRefCount;
|
|
bool rc = FALSE;
|
|
|
|
if (sscanf (cmdLine, "%*[ \t%]%d %d", &varNumber, &expectedRefCount) == 2)
|
|
{
|
|
char *label = hTabItemWithKey (vars, varNumber);
|
|
|
|
if (label)
|
|
{
|
|
labelHashEntry *entry = getLabelRef (label, head);
|
|
|
|
if (entry)
|
|
{
|
|
#if 0
|
|
/* debug spew. */
|
|
fprintf (stderr, "labelRefCount: %s has refCount %d, want %d\n",
|
|
label, entry->refCount, expectedRefCount);
|
|
#endif
|
|
|
|
rc = (expectedRefCount == entry->refCount);
|
|
}
|
|
else
|
|
{
|
|
// Not a local label. We do not know how often it might be referenced.
|
|
rc = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "*** internal error: var %d not bound"
|
|
" in peephole labelRefCount rule.\n",
|
|
varNumber);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: labelRefCount peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* labelRefCountChange:
|
|
* takes two parameters: a variable (bound to a label name)
|
|
* and a signed int for changing the reference count.
|
|
*
|
|
* Please note, this function is not a conditional. It unconditionally
|
|
* changes the label. It should be passed as the 'last' function
|
|
* so it only is applied if all other conditions have been met.
|
|
*
|
|
* should always return TRUE
|
|
*/
|
|
FBYNAME (labelRefCountChange)
|
|
{
|
|
int varNumber, RefCountDelta;
|
|
bool rc = FALSE;
|
|
|
|
/* If we don't have the label hash table yet, build it. */
|
|
if (!labelHash)
|
|
{
|
|
buildLabelRefCountHash (head);
|
|
}
|
|
|
|
if (sscanf (cmdLine, "%*[ \t%]%d %i", &varNumber, &RefCountDelta) == 2)
|
|
{
|
|
char *label = hTabItemWithKey (vars, varNumber);
|
|
|
|
if (label)
|
|
{
|
|
labelHashEntry *entry;
|
|
|
|
entry = hTabFirstItemWK (labelHash, hashSymbolName (label));
|
|
|
|
while (entry)
|
|
{
|
|
if (!strcmp (label, entry->name))
|
|
{
|
|
break;
|
|
}
|
|
entry = hTabNextItemWK (labelHash);
|
|
}
|
|
if (entry)
|
|
{
|
|
if (0 <= entry->refCount + RefCountDelta)
|
|
{
|
|
entry->refCount += RefCountDelta;
|
|
rc = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "*** internal error: label %s may not get"
|
|
" negative refCount in %s peephole.\n",
|
|
label, __func__);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not a local label. We do not know how often it might be referenced.
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "*** internal error: var %d not bound"
|
|
" in peephole %s rule.\n",
|
|
varNumber, __func__);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: labelRefCountChange peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Within the context of the lines currPl through endPl, determine
|
|
** if the variable var contains a symbol that is volatile. Returns
|
|
** TRUE only if it is certain that this was not volatile (the symbol
|
|
** was found and not volatile, or var was a constant or CPU register).
|
|
** Returns FALSE if the symbol was found and volatile, the symbol was
|
|
** not found, or var was a indirect/pointer addressing mode.
|
|
*/
|
|
static bool
|
|
notVolatileVariable(const char *var, lineNode *currPl, lineNode *endPl)
|
|
{
|
|
char symname[SDCC_NAME_MAX + 1];
|
|
char *p = symname;
|
|
const char *vp = var;
|
|
lineNode *cl;
|
|
operand *op;
|
|
iCode *last_ic;
|
|
|
|
const bool global_not_volatile = currFunc ? !currFunc->funcUsesVolatile : false;
|
|
|
|
/* Can't tell if indirect accesses are volatile or not, so
|
|
** assume they are (if there is a volatile access in the function at all), just to be safe.
|
|
*/
|
|
if (TARGET_IS_MCS51 || TARGET_IS_DS390 || TARGET_IS_DS400)
|
|
{
|
|
if (*var=='@')
|
|
return global_not_volatile;
|
|
}
|
|
if (TARGET_Z80_LIKE)
|
|
{
|
|
if (var[0] == '#')
|
|
return true;
|
|
if (var[0] == '(')
|
|
return global_not_volatile;
|
|
if (strstr (var, "(bc)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, "(de)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, "(hl)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, "(ix"))
|
|
return global_not_volatile;
|
|
if (strstr (var, "(iy"))
|
|
return global_not_volatile;
|
|
}
|
|
|
|
if (TARGET_IS_STM8)
|
|
{
|
|
if (var[0] == '#')
|
|
return true;
|
|
if (var[0] == '(')
|
|
return global_not_volatile;
|
|
if (strstr (var, "(x)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, "(y)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, ", x)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, ", y)"))
|
|
return global_not_volatile;
|
|
if (strstr (var, ", sp)"))
|
|
return global_not_volatile;
|
|
if (strchr (var, '[') && strchr (var, ']'))
|
|
return global_not_volatile;
|
|
if (strstr(var, "0x") || strstr(var, "0X") || isdigit(var[0]))
|
|
return global_not_volatile;
|
|
}
|
|
|
|
if (TARGET_PDK_LIKE)
|
|
{
|
|
if (var[0] == '#')
|
|
return true;
|
|
if (!strcmp (var, "p"))
|
|
return true;
|
|
}
|
|
|
|
/* Extract a symbol name from the variable */
|
|
while (*vp && (*vp!='_'))
|
|
vp++;
|
|
while (*vp && (ISCHARALNUM(*vp) || *vp=='_'))
|
|
*p++ = *vp++;
|
|
*p='\0';
|
|
|
|
if (!symname[0])
|
|
{
|
|
/* Nothing resembling a symbol name was found, so it can't
|
|
be volatile
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
last_ic = NULL;
|
|
for (cl = currPl; cl!=endPl->next; cl = cl->next)
|
|
{
|
|
if (cl->ic && (cl->ic!=last_ic))
|
|
{
|
|
last_ic = cl->ic;
|
|
switch (cl->ic->op)
|
|
{
|
|
case IFX:
|
|
op = IC_COND (cl->ic);
|
|
if (IS_SYMOP (op) &&
|
|
( !strcmp(OP_SYMBOL (op)->rname, symname) ||
|
|
(OP_SYMBOL (op)->isspilt &&
|
|
SPIL_LOC (op) &&
|
|
!strcmp(SPIL_LOC (op)->rname, symname)) ))
|
|
{
|
|
return !op->isvolatile;
|
|
}
|
|
case JUMPTABLE:
|
|
op = IC_JTCOND (cl->ic);
|
|
if (IS_SYMOP (op) &&
|
|
( !strcmp(OP_SYMBOL (op)->rname, symname) ||
|
|
(OP_SYMBOL (op)->isspilt &&
|
|
SPIL_LOC (op) &&
|
|
!strcmp(SPIL_LOC (op)->rname, symname)) ))
|
|
{
|
|
return !op->isvolatile;
|
|
}
|
|
default:
|
|
op = IC_LEFT (cl->ic);
|
|
if (IS_SYMOP (op) &&
|
|
( !strcmp(OP_SYMBOL (op)->rname, symname) ||
|
|
(OP_SYMBOL (op)->isspilt &&
|
|
SPIL_LOC (op) &&
|
|
!strcmp(SPIL_LOC (op)->rname, symname)) ))
|
|
{
|
|
return !op->isvolatile;
|
|
}
|
|
op = IC_RIGHT (cl->ic);
|
|
if (IS_SYMOP (op) &&
|
|
( !strcmp(OP_SYMBOL (op)->rname, symname) ||
|
|
(OP_SYMBOL (op)->isspilt &&
|
|
SPIL_LOC (op) &&
|
|
!strcmp(SPIL_LOC (op)->rname, symname)) ))
|
|
{
|
|
return !op->isvolatile;
|
|
}
|
|
op = IC_RESULT (cl->ic);
|
|
if (IS_SYMOP (op) &&
|
|
( !strcmp(OP_SYMBOL (op)->rname, symname) ||
|
|
(OP_SYMBOL (op)->isspilt &&
|
|
SPIL_LOC (op) &&
|
|
!strcmp(SPIL_LOC (op)->rname, symname)) ))
|
|
{
|
|
return !op->isvolatile;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Couldn't find the symbol for some reason. Assume volatile if the current function touches anything volatile. */
|
|
return global_not_volatile;
|
|
}
|
|
|
|
/* notVolatile:
|
|
*
|
|
* This rule restriction has two different behaviours depending on
|
|
* the number of parameters given.
|
|
*
|
|
* if notVolatile (no parameters given)
|
|
* The rule is applied only if none of the iCodes originating
|
|
* the matched pattern reference a volatile operand.
|
|
*
|
|
* if notVolatile %1 ... (one or more parameters given)
|
|
* The rule is applied if the parameters are not expressions
|
|
* containing volatile symbols and are not pointer accesses.
|
|
*
|
|
*/
|
|
FBYNAME (notVolatile)
|
|
{
|
|
int varNumber;
|
|
char *var;
|
|
char *digitend;
|
|
lineNode *cl;
|
|
operand *op;
|
|
|
|
if (!cmdLine)
|
|
{
|
|
/* If no parameters given, just scan the iCodes for volatile operands */
|
|
for (cl = currPl; cl!=endPl->next; cl = cl->next)
|
|
{
|
|
if (cl->ic)
|
|
{
|
|
switch (cl->ic->op)
|
|
{
|
|
case IFX:
|
|
op = IC_COND (cl->ic);
|
|
if (IS_SYMOP (op) && op->isvolatile)
|
|
return FALSE;
|
|
case JUMPTABLE:
|
|
op = IC_JTCOND (cl->ic);
|
|
if (IS_SYMOP (op) && op->isvolatile)
|
|
return FALSE;
|
|
default:
|
|
op = IC_LEFT (cl->ic);
|
|
if (IS_SYMOP (op) && op->isvolatile)
|
|
return FALSE;
|
|
op = IC_RIGHT (cl->ic);
|
|
if (IS_SYMOP (op) && op->isvolatile)
|
|
return FALSE;
|
|
op = IC_RESULT (cl->ic);
|
|
if (IS_SYMOP (op) && op->isvolatile)
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* There were parameters; check the volatility of each */
|
|
while (*cmdLine && ISCHARSPACE(*cmdLine))
|
|
cmdLine++;
|
|
while (*cmdLine)
|
|
{
|
|
if (*cmdLine!='%')
|
|
goto error;
|
|
cmdLine++;
|
|
if (!ISCHARDIGIT(*cmdLine))
|
|
goto error;
|
|
varNumber = strtol(cmdLine, &digitend, 10);
|
|
cmdLine = digitend;
|
|
while (*cmdLine && ISCHARSPACE(*cmdLine))
|
|
cmdLine++;
|
|
|
|
var = hTabItemWithKey (vars, varNumber);
|
|
|
|
if (var)
|
|
{
|
|
if (!notVolatileVariable (var, currPl, endPl))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "*** internal error: var %d not bound"
|
|
" in peephole notVolatile rule.\n",
|
|
varNumber);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
fprintf (stderr,
|
|
"*** internal error: notVolatile peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
/*------------------------------------------------------------------*/
|
|
/* setFromConditionArgs - parse a peephole condition's arguments */
|
|
/* to produce a set of strings, one per argument. Variables %x will */
|
|
/* be replaced with their values. String literals (in single quotes)*/
|
|
/* are accepted and return in unquoted form. */
|
|
/*------------------------------------------------------------------*/
|
|
static set *
|
|
setFromConditionArgs (char *cmdLine, hTab * vars)
|
|
{
|
|
int varNumber;
|
|
char *var;
|
|
char *digitend;
|
|
set *operands = NULL;
|
|
|
|
if (!cmdLine)
|
|
return NULL;
|
|
|
|
while (*cmdLine && ISCHARSPACE(*cmdLine))
|
|
cmdLine++;
|
|
|
|
while (*cmdLine)
|
|
{
|
|
if (*cmdLine == '%')
|
|
{
|
|
cmdLine++;
|
|
if (!ISCHARDIGIT(*cmdLine))
|
|
goto error;
|
|
varNumber = strtol(cmdLine, &digitend, 10);
|
|
cmdLine = digitend;
|
|
|
|
var = hTabItemWithKey (vars, varNumber);
|
|
|
|
if (var)
|
|
{
|
|
addSetHead (&operands, var);
|
|
}
|
|
else
|
|
goto error;
|
|
}
|
|
else if (*cmdLine == '\'' )
|
|
{
|
|
char quote = *cmdLine;
|
|
|
|
var = ++cmdLine;
|
|
while (*cmdLine && *cmdLine != quote)
|
|
cmdLine++;
|
|
if (*cmdLine == quote)
|
|
*cmdLine++ = '\0';
|
|
else
|
|
goto error;
|
|
addSetHead (&operands, var);
|
|
}
|
|
else
|
|
goto error;
|
|
|
|
while (*cmdLine && ISCHARSPACE(*cmdLine))
|
|
cmdLine++;
|
|
}
|
|
|
|
return operands;
|
|
|
|
error:
|
|
deleteSet (&operands);
|
|
return NULL;
|
|
}
|
|
|
|
static const char *
|
|
operandBaseName (const char *op)
|
|
{
|
|
if (TARGET_IS_MCS51 || TARGET_IS_DS390 || TARGET_IS_DS400)
|
|
{
|
|
if (!strcmp (op, "acc") || !strncmp (op, "acc.", 4))
|
|
return "a";
|
|
if (!strncmp (op, "ar", 2) && ISCHARDIGIT(*(op+2)) && !*(op+3))
|
|
return op+1;
|
|
// bug 1739475, temp fix
|
|
if (op[0] == '@')
|
|
return operandBaseName(op+1);
|
|
}
|
|
if (TARGET_Z80_LIKE)
|
|
{
|
|
if (!strcmp (op, "d") || !strcmp (op, "e") || !strcmp (op, "(de)"))
|
|
return "de";
|
|
if (!strcmp (op, "b") || !strcmp (op, "c") || !strcmp (op, "(bc)"))
|
|
return "bc";
|
|
if (!strcmp (op, "h") || !strcmp (op, "l") || !strcmp (op, "(hl)") || !strcmp (op, "(hl+)") || !strcmp (op, "(hl-)"))
|
|
return "hl";
|
|
if (!strcmp (op, "iyh") || !strcmp (op, "iyl") || strstr (op, "iy"))
|
|
return "iy";
|
|
if (!strcmp (op, "ixh") || !strcmp (op, "ixl") || strstr (op, "ix"))
|
|
return "ix";
|
|
if (!strcmp (op, "a"))
|
|
return "af";
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* notUsed - Check, if value in register is not read again */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (notUsed)
|
|
{
|
|
const char *what;
|
|
bool ret;
|
|
|
|
set *operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands || elementsInSet(operands) != 1)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: notUsed peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
what = setFirstItem (operands);
|
|
|
|
if (!port->peep.notUsed)
|
|
{
|
|
fprintf (stderr, "Function notUsed not initialized in port structure\n");
|
|
return FALSE;
|
|
}
|
|
|
|
ret = port->peep.notUsed (what, endPl, head);
|
|
|
|
deleteSet(&operands);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* notUsed - Check, if value in register is not read again */
|
|
/* starting from label */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (notUsedFrom)
|
|
{
|
|
const char *what, *label;
|
|
set *operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands || elementsInSet(operands) != 2)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: notUsedFrom peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
what = setFirstItem (operands);
|
|
label = setNextItem (operands);
|
|
|
|
if (port->peep.notUsedFrom)
|
|
return port->peep.notUsedFrom (what, label, head);
|
|
|
|
fprintf (stderr, "Function notUsed not initialized in port structure\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* canAssign - Check, if we can do ld dst, src. */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (canAssign)
|
|
{
|
|
set *operands;
|
|
const char *dst, *src, *exotic;
|
|
|
|
operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands || elementsInSet(operands) < 2 || elementsInSet(operands) > 3)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: canAssign peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
if(elementsInSet(operands) == 3)
|
|
{
|
|
exotic = setFirstItem (operands);
|
|
src = setNextItem (operands);
|
|
dst = setNextItem (operands);
|
|
}
|
|
else
|
|
{
|
|
exotic = 0;
|
|
src = setFirstItem (operands);
|
|
dst = setNextItem (operands);
|
|
}
|
|
|
|
if (port->peep.canAssign)
|
|
{
|
|
bool ret = port->peep.canAssign (dst, src, exotic);
|
|
deleteSet (&operands);
|
|
return (ret);
|
|
}
|
|
|
|
deleteSet (&operands);
|
|
|
|
fprintf (stderr, "Function canAssign not initialized in port structure\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* operandsNotRelated - returns true if the condition's operands */
|
|
/* are not related (taking into account register name aliases). */
|
|
/* N-way comparison performed between all operands. */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (operandsNotRelated)
|
|
{
|
|
set *operands;
|
|
const char *op1, *op2;
|
|
|
|
operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: operandsNotRelated peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
while ((op1 = setFirstItem (operands)))
|
|
{
|
|
deleteSetItem (&operands, (void*)op1);
|
|
op1 = operandBaseName (op1);
|
|
|
|
for (op2 = setFirstItem (operands); op2; op2 = setNextItem (operands))
|
|
{
|
|
op2 = operandBaseName (op2);
|
|
if (strcmp (op1, op2) == 0)
|
|
{
|
|
deleteSet (&operands);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteSet (&operands);
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* notSimilar - Check, if one is another's substring */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (notSimilar)
|
|
{
|
|
set *operands;
|
|
const char *op1, *op2;
|
|
|
|
operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: notSimilar peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
while ((op1 = setFirstItem (operands)))
|
|
{
|
|
deleteSetItem (&operands, (void*)op1);
|
|
|
|
for (op2 = setFirstItem (operands); op2; op2 = setNextItem (operands))
|
|
{
|
|
if ((strstr (op1, op2) || strstr (op2, op1)) && strcmp (op1, op2) == 0)
|
|
{
|
|
deleteSet (&operands);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteSet (&operands);
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* symmParmStack - Caller readjusts stack by the number of bytes
|
|
that were pushed in all calls to this function */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (symmParmStack)
|
|
{
|
|
if (port->peep.symmParmStack)
|
|
return port->peep.symmParmStack();
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* notSame - Check, that arguments are pairwise not the same */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (notSame)
|
|
{
|
|
set *operands;
|
|
const char *op1, *op2;
|
|
|
|
operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: notSame peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
while ((op1 = setFirstItem (operands)))
|
|
{
|
|
deleteSetItem (&operands, (void*)op1);
|
|
|
|
for (op2 = setFirstItem (operands); op2; op2 = setNextItem (operands))
|
|
{
|
|
if (strcmp (op1, op2) == 0)
|
|
{
|
|
deleteSet (&operands);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteSet (&operands);
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* same - Check if first operand matches any of the remaining */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (same)
|
|
{
|
|
set *operands;
|
|
const char *match, *op;
|
|
|
|
operands = setFromConditionArgs(cmdLine, vars);
|
|
|
|
if (!operands)
|
|
{
|
|
fprintf(stderr,
|
|
"*** internal error: same peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
operands = reverseSet(operands);
|
|
|
|
match = setFirstItem(operands);
|
|
for (op = setNextItem(operands); op; op = setNextItem(operands))
|
|
{
|
|
if (strcmp(match, op) == 0)
|
|
{
|
|
deleteSet(&operands);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
deleteSet(&operands);
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* operandsLiteral - returns true if the condition's operands are */
|
|
/* literals. */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (operandsLiteral)
|
|
{
|
|
set *operands;
|
|
const char *op;
|
|
|
|
operands = setFromConditionArgs (cmdLine, vars);
|
|
|
|
if (!operands)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: operandsLiteral peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
for (op = setFirstItem (operands); op; op = setNextItem (operands))
|
|
{
|
|
if (!isdigit( (unsigned char)(*op) ))
|
|
{
|
|
deleteSet (&operands);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
deleteSet (&operands);
|
|
return TRUE;
|
|
}
|
|
|
|
static long *
|
|
immdGet (const char *pc, long *pl)
|
|
{
|
|
long s = 1;
|
|
|
|
if (!pc || !pl)
|
|
return NULL;
|
|
|
|
// omit space
|
|
for (; ISCHARSPACE (*pc); pc++);
|
|
// parse sign
|
|
for (; !ISCHARDIGIT (*pc); pc++)
|
|
if (*pc == '-')
|
|
s *= -1;
|
|
else if (*pc == '+')
|
|
s *= +1;
|
|
else
|
|
return NULL;
|
|
|
|
if (pc[0] == '0' && (pc[1] == 'x' || pc[1] == 'X'))
|
|
{
|
|
if (sscanf (pc + 2, "%lx", pl) != 1)
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
if (sscanf (pc, "%ld", pl) != 1)
|
|
return NULL;
|
|
}
|
|
|
|
*pl *= s;
|
|
return pl;
|
|
}
|
|
|
|
static bool
|
|
immdError (const char *info, const char *param, const char *cmd)
|
|
{
|
|
fprintf (stderr, "*** internal error: immdInRange gets "
|
|
"%s: \"%s\" in \"%s\"\n", info, param, cmd);
|
|
return FALSE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* immdInRange - returns true if the sum or difference of two */
|
|
/* immediates is in a give range. */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (immdInRange)
|
|
{
|
|
char r[64], operator[8];
|
|
const char *op;
|
|
long i, j, k, h, low, high, left_l, right_l, order;
|
|
const char *padd[] = {"+", "'+'", "\"+\"", "add", "'add'", "\"add\""};
|
|
const char *psub[] = {"-", "'-'", "\"-\"", "sub", "'sub'", "\"sub\""};
|
|
|
|
for (i = order = 0; order < 6;)
|
|
{
|
|
// pick up one parameter in the temp buffer r[64]
|
|
for (; ISCHARSPACE (cmdLine[i]) && cmdLine[i]; i++);
|
|
for (j = i; !ISCHARSPACE (cmdLine[j]) && cmdLine[j]; j++);
|
|
if (!cmdLine[i]) // unexpected end
|
|
return immdError ("no enough input", "", cmdLine);
|
|
else
|
|
{
|
|
for (k = i; k < j; k++)
|
|
r[k - i] = cmdLine[k];
|
|
r[j - i] = 0;
|
|
}
|
|
// parse the string by order
|
|
switch (order)
|
|
{
|
|
case 0: // lower bound
|
|
if (!immdGet (r, &low))
|
|
return immdError ("bad lower bound", r, cmdLine);
|
|
break;
|
|
case 1: // upper bound
|
|
if (!immdGet (r, &high))
|
|
return immdError ("bad upper bound", r, cmdLine);
|
|
break;
|
|
case 2: // operator
|
|
if (sscanf (r, "%s", operator) != 1)
|
|
return immdError ("bad operator", r, cmdLine);
|
|
break;
|
|
case 3: // left operand
|
|
if (immdGet (r, &left_l)) // the left operand is given directly
|
|
{
|
|
}
|
|
else if (r[0] == '%') // the left operand is passed via pattern match
|
|
{
|
|
if (!immdGet (r + 1, &k) || !(op = hTabItemWithKey (vars, (int) k)))
|
|
return immdError ("bad left operand", r, cmdLine);
|
|
else if (!immdGet (op, &left_l))
|
|
return immdError ("bad left operand", op, r);
|
|
}
|
|
else
|
|
return immdError ("bad left operand", r, cmdLine);
|
|
break;
|
|
case 4: // right operand
|
|
if (immdGet (r, &right_l)) // the right operand is given directly
|
|
{
|
|
}
|
|
else if (r[0] == '%') // the right operand is passed via pattern match
|
|
{
|
|
if (!immdGet (r + 1, &k) || !(op = hTabItemWithKey (vars, (int) k)))
|
|
return immdError ("bad right operand", r, cmdLine);
|
|
else if (!immdGet (op, &right_l))
|
|
return immdError ("bad right operand", op, r);
|
|
}
|
|
else
|
|
return immdError ("bad right operand", r, cmdLine);
|
|
break;
|
|
case 5: // result
|
|
if (r[0] != '%' || !immdGet (r + 1, &h))
|
|
return immdError ("bad result container", r, cmdLine);
|
|
break;
|
|
default: // should not reach
|
|
return immdError ("unexpected input", "", cmdLine);
|
|
break;
|
|
}
|
|
order++;
|
|
i = j;
|
|
}
|
|
|
|
// calculate
|
|
for (j = k = 0; k < sizeof (padd) / sizeof (padd[0]); k++) // add
|
|
if (strcmp (operator, padd[k]) == 0)
|
|
{
|
|
i = left_l + right_l;
|
|
j = 1;
|
|
break;
|
|
}
|
|
if (!j)
|
|
for (k = 0; k < sizeof (psub) / sizeof (psub[0]); k++) // sub
|
|
if (strcmp (operator, psub[k]) == 0)
|
|
{
|
|
i = left_l - right_l;
|
|
j = 1;
|
|
break;
|
|
}
|
|
if (!j)
|
|
return immdError ("bad operator", operator, cmdLine);
|
|
|
|
// bind the result
|
|
if ((low <= i && i <= high) || (high <= i && i <= low))
|
|
{
|
|
char *p[] = {r, NULL};
|
|
sprintf (r, "%ld", i);
|
|
bindVar ((int) h, p, &vars);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* inSequence - Check that numerical constants are in sequence */
|
|
/*-----------------------------------------------------------------*/
|
|
FBYNAME (inSequence)
|
|
{
|
|
set *operands;
|
|
const char *op;
|
|
long seq, val, stride;
|
|
|
|
if ((operands = setFromConditionArgs(cmdLine, vars)) == NULL)
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: inSequence peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
operands = reverseSet(operands);
|
|
|
|
op = setFirstItem(operands);
|
|
if ((immdGet(op, &stride) == NULL) || ((op = setNextItem(operands)) == NULL))
|
|
{
|
|
fprintf (stderr,
|
|
"*** internal error: inSequence peephole restriction"
|
|
" malformed: %s\n", cmdLine);
|
|
return FALSE;
|
|
}
|
|
|
|
for (seq = LONG_MIN; op; op = setNextItem(operands))
|
|
{
|
|
if ((immdGet(op, &val) == NULL) || ((seq != LONG_MIN) && (val != seq+stride)))
|
|
{
|
|
deleteSet(&operands);
|
|
return FALSE;
|
|
}
|
|
seq = val;
|
|
}
|
|
|
|
deleteSet(&operands);
|
|
return TRUE;
|
|
}
|
|
|
|
static const struct ftab
|
|
{
|
|
char *fname;
|
|
int (*func) (hTab *, lineNode *, lineNode *, lineNode *, char *);
|
|
}
|
|
ftab[] = // sorted on the number of times used
|
|
{ // in the peephole rules on 2010-06-12
|
|
{
|
|
"labelRefCount", labelRefCount // 161
|
|
},
|
|
{
|
|
"notVolatile", notVolatile // 118
|
|
},
|
|
{
|
|
"notUsed", notUsed // 96
|
|
},
|
|
{
|
|
"labelRefCountChange", labelRefCountChange // 86
|
|
},
|
|
{
|
|
"labelInRange", labelInRange // 51
|
|
},
|
|
{
|
|
"notSame", notSame // 28
|
|
},
|
|
{
|
|
"operandsNotRelated", operandsNotRelated // 28
|
|
},
|
|
{
|
|
"same", same // z88dk z80
|
|
},
|
|
{
|
|
"labelJTInRange", labelJTInRange // 13
|
|
},
|
|
{
|
|
"24bitMode", flat24bitMode // 9
|
|
},
|
|
{
|
|
"canAssign", canAssign // 8
|
|
},
|
|
{
|
|
"inSequence", inSequence // z88dk z80
|
|
},
|
|
{
|
|
"optimizeReturn", optimizeReturn // ? just a guess
|
|
},
|
|
{
|
|
"notUsedFrom", notUsedFrom // ? just a guess
|
|
},
|
|
{
|
|
"labelIsReturnOnly", labelIsReturnOnly // 6
|
|
},
|
|
{
|
|
"operandsLiteral", operandsLiteral // 6
|
|
},
|
|
{
|
|
"portIsDS390", portIsDS390 // 5
|
|
},
|
|
{
|
|
"labelIsUncondJump", labelIsUncondJump // 4
|
|
},
|
|
{
|
|
"deadMove", deadMove // 2
|
|
},
|
|
{
|
|
"useAcallAjmp", useAcallAjmp // 2
|
|
},
|
|
{
|
|
"xramMovcOption", xramMovcOption // 2
|
|
},
|
|
{
|
|
"okToRemoveSLOC", okToRemoveSLOC // 0
|
|
},
|
|
{
|
|
"immdInRange", immdInRange
|
|
},
|
|
{
|
|
"notSimilar", notSimilar
|
|
},
|
|
{
|
|
"symmParmStack", symmParmStack
|
|
},
|
|
};
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* callFuncByName - calls a function as defined in the table */
|
|
/*-----------------------------------------------------------------*/
|
|
static int
|
|
callFuncByName (char *fname,
|
|
hTab *vars,
|
|
lineNode *currPl, /* first source line matched */
|
|
lineNode *endPl, /* last source line matched */
|
|
lineNode *head)
|
|
{
|
|
int i;
|
|
char *cmdCopy, *funcName, *funcArgs, *cmdTerm;
|
|
char c;
|
|
int rc;
|
|
|
|
/* Isolate the function name part (we are passed the full condition
|
|
* string including arguments)
|
|
*/
|
|
cmdTerm = cmdCopy = Safe_strdup(fname);
|
|
|
|
do
|
|
{
|
|
funcArgs = funcName = cmdTerm;
|
|
while ((c = *funcArgs) && c != ' ' && c != '\t' && c != '(')
|
|
funcArgs++;
|
|
*funcArgs = '\0'; /* terminate the function name */
|
|
if (c)
|
|
funcArgs++;
|
|
|
|
/* Find the start of the arguments */
|
|
if (c == ' ' || c == '\t')
|
|
while ((c = *funcArgs) && (c == ' ' || c == '\t'))
|
|
funcArgs++;
|
|
|
|
/* If the arguments started with an opening parenthesis, */
|
|
/* use the closing parenthesis for the end of the */
|
|
/* arguments and look for the start of another condition */
|
|
/* that can optionally follow. If there was no opening */
|
|
/* parethesis, then everything that follows are arguments */
|
|
/* and there can be no additional conditions. */
|
|
if (c == '(')
|
|
{
|
|
|
|
int num_parenthesis = 0;
|
|
cmdTerm = funcArgs;
|
|
|
|
while ((c = *cmdTerm) && (c != ')' || num_parenthesis))
|
|
{
|
|
if (c == '(')
|
|
num_parenthesis++;
|
|
else if (c == ')')
|
|
num_parenthesis--;
|
|
cmdTerm++;
|
|
}
|
|
*cmdTerm = '\0'; /* terminate the arguments */
|
|
if (c == ')')
|
|
{
|
|
cmdTerm++;
|
|
while ((c = *cmdTerm) && (c == ' ' || c == '\t' || c == ','))
|
|
cmdTerm++;
|
|
if (!*cmdTerm)
|
|
cmdTerm = NULL;
|
|
}
|
|
else
|
|
cmdTerm = NULL; /* closing parenthesis missing */
|
|
}
|
|
else
|
|
cmdTerm = NULL;
|
|
|
|
if (!*funcArgs)
|
|
funcArgs = NULL;
|
|
|
|
rc = -1;
|
|
for (i = 0; i < ((sizeof (ftab)) / (sizeof (struct ftab))); i++)
|
|
{
|
|
if (strcmp (ftab[i].fname, funcName) == 0)
|
|
{
|
|
rc = (*ftab[i].func) (vars, currPl, endPl, head, funcArgs);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc == -1)
|
|
{
|
|
fprintf (stderr,
|
|
"could not find named function \"%s\" in "
|
|
"peephole function table\n",
|
|
funcName);
|
|
// If the function couldn't be found, let's assume it's
|
|
// a bad rule and refuse it.
|
|
rc = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
while (rc && cmdTerm);
|
|
|
|
Safe_free(cmdCopy);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* newPeepRule - creates a new peeprule and attach it to the root */
|
|
/*-----------------------------------------------------------------*/
|
|
static peepRule *
|
|
newPeepRule (lineNode * match,
|
|
lineNode * replace,
|
|
char *cond,
|
|
int restart,
|
|
int barrier)
|
|
{
|
|
peepRule *pr;
|
|
|
|
pr = Safe_alloc ( sizeof (peepRule));
|
|
pr->match = match;
|
|
pr->replace = replace;
|
|
pr->restart = restart;
|
|
pr->barrier = barrier;
|
|
|
|
if (cond && *cond)
|
|
{
|
|
pr->cond = Safe_strdup (cond);
|
|
}
|
|
else
|
|
pr->cond = NULL;
|
|
|
|
pr->vars = newHashTable (16);
|
|
|
|
/* if root is empty */
|
|
if (!rootRules)
|
|
rootRules = currRule = pr;
|
|
else
|
|
currRule = currRule->next = pr;
|
|
|
|
return pr;
|
|
}
|
|
|
|
#define SKIP_SPACE(x,y) { while (*x && (ISCHARSPACE(*x) || *x == '\n')) x++; \
|
|
if (!*x) { fprintf(stderr,y); return ; } }
|
|
|
|
#define EXPECT_STR(x,y,z) { while (*x && strncmp(x,y,strlen(y))) x++ ; \
|
|
if (!*x) { fprintf(stderr,z); return ; } }
|
|
#define EXPECT_CHR(x,y,z) { while (*x && *x != y) x++ ; \
|
|
if (!*x) { fprintf(stderr,z); return ; } }
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* getPeepLine - parses the peep lines */
|
|
/*-----------------------------------------------------------------*/
|
|
static void
|
|
getPeepLine (lineNode ** head, const char **bpp)
|
|
{
|
|
char lines[MAX_PATTERN_LEN];
|
|
char *lp;
|
|
int isComment;
|
|
|
|
lineNode *currL = NULL;
|
|
const char *bp = *bpp;
|
|
while (1)
|
|
{
|
|
|
|
if (!*bp)
|
|
{
|
|
fprintf (stderr, "unexpected end of match pattern\n");
|
|
return;
|
|
}
|
|
|
|
if (*bp == '\n')
|
|
{
|
|
bp++;
|
|
while (ISCHARSPACE (*bp) ||
|
|
*bp == '\n')
|
|
bp++;
|
|
}
|
|
|
|
if (*bp == '}')
|
|
{
|
|
bp++;
|
|
break;
|
|
}
|
|
|
|
/* read till end of line */
|
|
lp = lines;
|
|
while ((*bp != '\n' && *bp != '}') && *bp)
|
|
*lp++ = *bp++;
|
|
*lp = '\0';
|
|
|
|
lp = lines;
|
|
while (*lp && ISCHARSPACE(*lp))
|
|
lp++;
|
|
isComment = (*lp == ';');
|
|
|
|
if (!isComment || (isComment && !options.noPeepComments))
|
|
{
|
|
const char *dummy1;
|
|
int dummy2;
|
|
|
|
if (!currL)
|
|
*head = currL = newLineNode (lines);
|
|
else
|
|
currL = connectLine (currL, newLineNode (lines));
|
|
currL->isComment = isComment;
|
|
currL->isLabel = isLabelDefinition (currL->line, &dummy1, &dummy2,
|
|
TRUE);
|
|
}
|
|
|
|
}
|
|
|
|
*bpp = bp;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* readRules - reads the rules from a string buffer */
|
|
/*-----------------------------------------------------------------*/
|
|
static void
|
|
readRules (const char *bp)
|
|
{
|
|
char restart = 0, barrier = 0;
|
|
char lines[MAX_PATTERN_LEN];
|
|
size_t safetycounter;
|
|
char *lp;
|
|
const char *rp;
|
|
lineNode *match;
|
|
lineNode *replace;
|
|
lineNode *currL = NULL;
|
|
|
|
if (!bp)
|
|
return;
|
|
top:
|
|
restart = 0;
|
|
barrier = 0;
|
|
|
|
/* look for the token "replace" that is the
|
|
start of a rule */
|
|
while (*bp && strncmp (bp, "replace", 7))
|
|
{
|
|
if (!strncmp (bp, "barrier", 7))
|
|
barrier = 1;
|
|
bp++;
|
|
}
|
|
|
|
/* if not found */
|
|
if (!*bp)
|
|
return;
|
|
|
|
/* then look for either "restart" or '{' */
|
|
while (strncmp (bp, "restart", 7) &&
|
|
*bp != '{' && bp)
|
|
bp++;
|
|
|
|
/* not found */
|
|
if (!*bp)
|
|
{
|
|
fprintf (stderr, "expected 'restart' or '{'\n");
|
|
return;
|
|
}
|
|
|
|
/* if brace */
|
|
if (*bp == '{')
|
|
bp++;
|
|
else
|
|
{ /* must be restart */
|
|
restart++;
|
|
bp += strlen ("restart");
|
|
/* look for '{' */
|
|
EXPECT_CHR (bp, '{', "expected '{'\n");
|
|
bp++;
|
|
}
|
|
|
|
/* skip thru all the blank space */
|
|
SKIP_SPACE (bp, "unexpected end of rule\n");
|
|
|
|
match = replace = currL = NULL;
|
|
/* we are the start of a rule */
|
|
getPeepLine (&match, &bp);
|
|
|
|
/* now look for by */
|
|
EXPECT_STR (bp, "by", "expected 'by'\n");
|
|
|
|
/* then look for a '{' */
|
|
EXPECT_CHR (bp, '{', "expected '{'\n");
|
|
bp++;
|
|
|
|
/* save char position (needed for generating error msg) */
|
|
rp = bp;
|
|
|
|
SKIP_SPACE (bp, "unexpected end of rule\n");
|
|
getPeepLine (&replace, &bp);
|
|
|
|
/* look for a 'if' */
|
|
while ((ISCHARSPACE (*bp) || *bp == '\n' || (*bp == '/' && *(bp+1) == '/')) && *bp)
|
|
{
|
|
++bp;
|
|
if (*bp == '/') while (*bp && *bp != '\n') ++bp;
|
|
}
|
|
|
|
if (strncmp (bp, "if", 2) == 0)
|
|
{
|
|
bp += 2;
|
|
while ((ISCHARSPACE (*bp) || *bp == '\n' || (*bp == '/' && *(bp+1) == '/')) && *bp)
|
|
{
|
|
bp++;
|
|
if (*bp == '/')
|
|
while (*bp && *bp != '\n')
|
|
bp++;
|
|
}
|
|
if (!*bp)
|
|
{
|
|
fprintf (stderr, "expected condition name\n");
|
|
return;
|
|
}
|
|
|
|
/* look for the condition */
|
|
lp = lines;
|
|
for (safetycounter = 0; *bp && (*bp != '\n'); safetycounter++)
|
|
{
|
|
wassertl(safetycounter < MAX_PATTERN_LEN, "Peephole line too long.\n");
|
|
*lp++ = *bp++;
|
|
}
|
|
*lp = '\0';
|
|
|
|
newPeepRule (match, replace, lines, restart, barrier);
|
|
}
|
|
else
|
|
{
|
|
if (*bp && strncmp (bp, "replace", 7) && strncmp (bp, "barrier", 7))
|
|
{
|
|
/* not the start of a new peeprule, so "if" should be here */
|
|
|
|
char strbuff[1000];
|
|
char *cp;
|
|
|
|
/* go to the start of the line following "{" of the "by" token */
|
|
while (*rp && (*rp == '\n'))
|
|
rp++;
|
|
|
|
/* copy text of rule starting with line after "by {" */
|
|
cp = strbuff;
|
|
while (*rp && (rp < bp) && ((cp - strbuff) < sizeof(strbuff)))
|
|
*cp++ = *rp++;
|
|
|
|
/* and now the rest of the line */
|
|
while (*rp && (*rp != '\n') && ((cp - strbuff) < sizeof(strbuff)))
|
|
*cp++ = *rp++;
|
|
|
|
*cp = '\0';
|
|
fprintf (stderr, "%s\nexpected '} if ...'\n", strbuff);
|
|
return;
|
|
}
|
|
newPeepRule (match, replace, NULL, restart, barrier);
|
|
}
|
|
goto top;
|
|
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* keyForVar - returns the numeric key for a var */
|
|
/*-----------------------------------------------------------------*/
|
|
static int
|
|
keyForVar (const char *d)
|
|
{
|
|
int i = 0;
|
|
|
|
while (ISCHARDIGIT (*d))
|
|
{
|
|
i *= 10;
|
|
i += (*d++ - '0');
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* bindVar - binds a value to a variable in the given hashtable */
|
|
/*-----------------------------------------------------------------*/
|
|
static void
|
|
bindVar (int key, char **s, hTab ** vtab)
|
|
{
|
|
char vval[MAX_PATTERN_LEN];
|
|
char *vvx;
|
|
char *vv = vval;
|
|
|
|
/* first get the value of the variable */
|
|
vvx = *s;
|
|
/* the value is ended by a ',' or space or newline or null or ) */
|
|
while (*vvx &&
|
|
*vvx != ',' &&
|
|
!ISCHARSPACE (*vvx) &&
|
|
*vvx != '\n' &&
|
|
*vvx != ':' &&
|
|
*vvx != ')')
|
|
{
|
|
char ubb = 0;
|
|
/* if we find a '(' then we need to balance it */
|
|
if (*vvx == '(')
|
|
{
|
|
ubb++;
|
|
while (ubb)
|
|
{
|
|
*vv++ = *vvx++;
|
|
if (*vvx == '(')
|
|
ubb++;
|
|
if (*vvx == ')')
|
|
ubb--;
|
|
}
|
|
// include the trailing ')'
|
|
*vv++ = *vvx++;
|
|
}
|
|
else
|
|
*vv++ = *vvx++;
|
|
}
|
|
*s = vvx;
|
|
*vv = '\0';
|
|
/* got value */
|
|
vvx = traceAlloc (&_G.values, Safe_strdup(vval));
|
|
|
|
hTabAddItem (vtab, key, vvx);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* matchLine - matches one line */
|
|
/*-----------------------------------------------------------------*/
|
|
static bool
|
|
matchLine (char *s, const char *d, hTab ** vars)
|
|
{
|
|
if (!s || !(*s))
|
|
return FALSE;
|
|
|
|
/* skip leading white spaces */
|
|
while (ISCHARSPACE (*s))
|
|
s++;
|
|
while (ISCHARSPACE (*d))
|
|
d++;
|
|
|
|
while (*s && *d)
|
|
{
|
|
/* skip white space in both */
|
|
while (ISCHARSPACE(*s))
|
|
s++;
|
|
while (ISCHARSPACE(*d))
|
|
d++;
|
|
|
|
/* if the destination is a var */
|
|
if (*d == '%' && ISCHARDIGIT (*(d + 1)) && vars)
|
|
{
|
|
const char *v = hTabItemWithKey (*vars, keyForVar (d + 1));
|
|
/* if the variable is already bound
|
|
then it MUST match with dest */
|
|
if (v)
|
|
{
|
|
while (*v)
|
|
if (*v++ != *s++)
|
|
return FALSE;
|
|
}
|
|
else
|
|
/* variable not bound we need to bind it */
|
|
bindVar (keyForVar (d + 1), &s, vars);
|
|
|
|
/* in either case go past the variable */
|
|
d++;
|
|
while (ISCHARDIGIT (*d))
|
|
d++;
|
|
}
|
|
else if (ISCHARSPACE (*s) && ISCHARSPACE (*d)) /* whitespace sequences match any whitespace sequences */
|
|
{
|
|
while (ISCHARSPACE (*s))
|
|
s++;
|
|
while (ISCHARSPACE (*d))
|
|
d++;
|
|
}
|
|
else if (*s == ',' && *d == ',') /* Allow comman to match comma followed by whitespace */
|
|
{
|
|
s++, d++;
|
|
while (ISCHARSPACE (*s))
|
|
s++;
|
|
while (ISCHARSPACE (*d))
|
|
d++;
|
|
}
|
|
else if (*s && *d) /* they should be an exact match otherwise */
|
|
{
|
|
if (*s++ != *d++)
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* skip trailing whitespaces */
|
|
if (*s)
|
|
while (ISCHARSPACE (*s))
|
|
s++;
|
|
|
|
if (*d)
|
|
while (ISCHARSPACE (*d))
|
|
d++;
|
|
|
|
/* after all this if only one of them
|
|
has something left over then no match */
|
|
if (*s || *d)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* matchRule - matches a all the rule lines */
|
|
/*-----------------------------------------------------------------*/
|
|
static bool
|
|
matchRule (lineNode * pl,
|
|
lineNode ** mtail,
|
|
peepRule * pr,
|
|
lineNode * head)
|
|
{
|
|
lineNode *spl; /* source pl */
|
|
lineNode *rpl; /* rule peep line */
|
|
|
|
/* setToNull((void *) &pr->vars); */
|
|
/* pr->vars = newHashTable(100); */
|
|
|
|
/* for all the lines defined in the rule */
|
|
rpl = pr->match;
|
|
spl = pl;
|
|
while (spl && rpl)
|
|
{
|
|
|
|
/* if the source line starts with a ';' then
|
|
comment line don't process or the source line
|
|
contains == . debugger information skip it */
|
|
if (spl->line &&
|
|
(*spl->line == ';' || spl->isDebug))
|
|
{
|
|
spl = spl->next;
|
|
continue;
|
|
}
|
|
|
|
if (!matchLine (spl->line, rpl->line, &pr->vars))
|
|
return FALSE;
|
|
|
|
rpl = rpl->next;
|
|
if (rpl)
|
|
spl = spl->next;
|
|
}
|
|
|
|
/* if rules ended */
|
|
if (!rpl)
|
|
{
|
|
/* if this rule has additional conditions */
|
|
if (pr->cond)
|
|
{
|
|
if (callFuncByName (pr->cond, pr->vars, pl, spl, head))
|
|
{
|
|
*mtail = spl;
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
*mtail = spl;
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
reassociate_ic_down (lineNode *shead, lineNode *stail,
|
|
lineNode *rhead, lineNode *rtail)
|
|
{
|
|
lineNode *csl; /* current source line */
|
|
lineNode *crl; /* current replacement line */
|
|
|
|
csl = shead;
|
|
crl = rhead;
|
|
while (1)
|
|
{
|
|
/* skip over any comments */
|
|
while (csl!=stail->next && csl->isComment)
|
|
csl = csl->next;
|
|
while (crl!=rtail->next && crl->isComment)
|
|
crl = crl->next;
|
|
|
|
/* quit if we reach the end */
|
|
if ((csl==stail->next) || (crl==rtail->next) || crl->ic)
|
|
break;
|
|
|
|
if (matchLine(csl->line,crl->line,NULL))
|
|
{
|
|
crl->ic = csl->ic;
|
|
csl = csl->next;
|
|
crl = crl->next;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
reassociate_ic_up (lineNode *shead, lineNode *stail,
|
|
lineNode *rhead, lineNode *rtail)
|
|
{
|
|
lineNode *csl; /* current source line */
|
|
lineNode *crl; /* current replacement line */
|
|
|
|
csl = stail;
|
|
crl = rtail;
|
|
while (1)
|
|
{
|
|
/* skip over any comments */
|
|
while (csl!=shead->prev && csl->isComment)
|
|
csl = csl->prev;
|
|
while (crl!=rhead->prev && crl->isComment)
|
|
crl = crl->prev;
|
|
|
|
/* quit if we reach the end */
|
|
if ((csl==shead->prev) || (crl==rhead->prev) || crl->ic)
|
|
break;
|
|
|
|
if (matchLine(csl->line,crl->line,NULL))
|
|
{
|
|
crl->ic = csl->ic;
|
|
csl = csl->prev;
|
|
crl = crl->prev;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------*/
|
|
/* reassociate_ic - reassociate replacement lines with origin iCode */
|
|
/*------------------------------------------------------------------*/
|
|
static void
|
|
reassociate_ic (lineNode *shead, lineNode *stail,
|
|
lineNode *rhead, lineNode *rtail)
|
|
{
|
|
lineNode *csl; /* current source line */
|
|
lineNode *crl; /* current replacement line */
|
|
bool single_iCode;
|
|
iCode *ic;
|
|
|
|
/* Check to see if all the source lines (excluding comments) came
|
|
** for the same iCode
|
|
*/
|
|
ic = NULL;
|
|
for (csl=shead;csl!=stail->next;csl=csl->next)
|
|
if (csl->ic && !csl->isComment)
|
|
{
|
|
ic = csl->ic;
|
|
break;
|
|
}
|
|
single_iCode = (ic!=NULL);
|
|
for (csl=shead;csl!=stail->next;csl=csl->next)
|
|
if ((csl->ic != ic) && !csl->isComment)
|
|
{
|
|
/* More than one iCode was found. However, if it's just the
|
|
** last line with the different iCode and it was not changed
|
|
** in the replacement, everything else must be the first iCode.
|
|
*/
|
|
if ((csl==stail) && matchLine (stail->line, rtail->line, NULL))
|
|
{
|
|
rtail->ic = stail->ic;
|
|
for (crl=rhead;crl!=rtail;crl=crl->next)
|
|
crl->ic = ic;
|
|
return;
|
|
}
|
|
|
|
single_iCode = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* If all of the source lines came from the same iCode, then so have
|
|
** all of the replacement lines too.
|
|
*/
|
|
if (single_iCode)
|
|
{
|
|
for (crl=rhead;crl!=rtail->next;crl=crl->next)
|
|
crl->ic = ic;
|
|
return;
|
|
}
|
|
|
|
/* The source lines span iCodes, so we may end up with replacement
|
|
** lines that we don't know which iCode(s) to associate with. Do the
|
|
** best we can by using the following strategies:
|
|
** 1) Start at the top and scan down. As long as the source line
|
|
** matches the replacement line, they have the same iCode.
|
|
** 2) Start at the bottom and scan up. As long as the source line
|
|
** matches the replacement line, they have the same iCode.
|
|
** 3) For any label in the source, look for a matching label in
|
|
** the replacment. If found, they have the same iCode. From
|
|
** these matching labels, scan down for additional matching
|
|
** lines; if found, they also have the same iCode.
|
|
*/
|
|
|
|
/* Strategy #1: Start at the top and scan down for matches
|
|
*/
|
|
reassociate_ic_down(shead,stail,rhead,rtail);
|
|
|
|
/* Strategy #2: Start at the bottom and scan up for matches
|
|
*/
|
|
reassociate_ic_up(shead,stail,rhead,rtail);
|
|
|
|
/* Strategy #3: Try to match labels
|
|
*/
|
|
csl = shead;
|
|
while (1)
|
|
{
|
|
/* skip over any comments */
|
|
while (csl!=stail->next && csl->isComment)
|
|
csl = csl->next;
|
|
if (csl==stail->next)
|
|
break;
|
|
|
|
if (csl->isLabel)
|
|
{
|
|
/* found a source line label; look for it in the replacment lines */
|
|
crl = rhead;
|
|
while (1)
|
|
{
|
|
while (crl!=rtail->next && crl->isComment)
|
|
crl = crl->next;
|
|
if (crl==rtail->next)
|
|
break;
|
|
if (matchLine(csl->line, crl->line, NULL))
|
|
{
|
|
reassociate_ic_down(csl,stail,crl,rtail);
|
|
break;
|
|
}
|
|
else
|
|
crl = crl->next;
|
|
}
|
|
}
|
|
csl = csl->next;
|
|
}
|
|
|
|
/* Try to assign a meaningful iCode to any comment that is missing
|
|
one. Since they are comments, it's ok to make mistakes; we are just
|
|
trying to improve continuity to simplify other tests.
|
|
*/
|
|
ic = NULL;
|
|
for (crl=rtail;crl!=rhead->prev;crl=crl->prev)
|
|
{
|
|
if (!crl->ic && ic && crl->isComment)
|
|
crl->ic = ic;
|
|
ic = crl->ic;
|
|
}
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* replaceRule - does replacement of a matching pattern */
|
|
/*-----------------------------------------------------------------*/
|
|
static void
|
|
replaceRule (lineNode ** shead, lineNode * stail, peepRule * pr)
|
|
{
|
|
lineNode *cl = NULL;
|
|
lineNode *pl = NULL, *lhead = NULL;
|
|
/* a long function name and long variable name can evaluate to
|
|
4x max pattern length e.g. "mov dptr,((fie_var>>8)<<8)+fie_var" */
|
|
char lb[MAX_PATTERN_LEN*4];
|
|
char *lbp;
|
|
lineNode *comment = NULL;
|
|
|
|
/* collect all the comment lines in the source */
|
|
for (cl = *shead; cl != stail; cl = cl->next)
|
|
{
|
|
if (cl->line && (*cl->line == ';' || cl->isDebug))
|
|
{
|
|
pl = (pl ? connectLine (pl, newLineNode (cl->line)) :
|
|
(comment = newLineNode (cl->line)));
|
|
pl->isDebug = cl->isDebug;
|
|
pl->isComment = cl->isComment || (*cl->line == ';');
|
|
}
|
|
}
|
|
cl = NULL;
|
|
|
|
/* for all the lines in the replacement pattern do */
|
|
for (pl = pr->replace; pl; pl = pl->next)
|
|
{
|
|
char *v;
|
|
char *l;
|
|
lbp = lb;
|
|
|
|
l = pl->line;
|
|
|
|
while (*l)
|
|
{
|
|
/* if the line contains a variable */
|
|
if (*l == '%' && ISCHARDIGIT (*(l + 1)))
|
|
{
|
|
v = hTabItemWithKey (pr->vars, keyForVar (l + 1));
|
|
if (!v)
|
|
{
|
|
fprintf (stderr, "used unbound variable in replacement\n");
|
|
l++;
|
|
continue;
|
|
}
|
|
while (*v) {
|
|
*lbp++ = *v++;
|
|
}
|
|
l++;
|
|
while (ISCHARDIGIT (*l)) {
|
|
l++;
|
|
}
|
|
continue;
|
|
}
|
|
*lbp++ = *l++;
|
|
}
|
|
|
|
*lbp = '\0';
|
|
if (cl)
|
|
cl = connectLine (cl, newLineNode (lb));
|
|
else
|
|
lhead = cl = newLineNode (lb);
|
|
cl->isComment = pl->isComment;
|
|
cl->isLabel = pl->isLabel;
|
|
}
|
|
|
|
/* add the comments if any to the head of list */
|
|
if (comment)
|
|
{
|
|
lineNode *lc = comment;
|
|
while (lc->next)
|
|
lc = lc->next;
|
|
lc->next = lhead;
|
|
if (lhead)
|
|
lhead->prev = lc;
|
|
lhead = comment;
|
|
}
|
|
|
|
if (lhead && cl)
|
|
{
|
|
/* determine which iCodes the replacment lines relate to */
|
|
reassociate_ic(*shead,stail,lhead,cl);
|
|
|
|
/* now we need to connect / replace the original chain */
|
|
/* if there is a prev then change it */
|
|
if ((*shead)->prev)
|
|
{
|
|
(*shead)->prev->next = lhead;
|
|
lhead->prev = (*shead)->prev;
|
|
}
|
|
*shead = lhead;
|
|
/* now for the tail */
|
|
if (stail && stail->next)
|
|
{
|
|
stail->next->prev = cl;
|
|
if (cl)
|
|
cl->next = stail->next;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* the replacement is empty - delete the source lines */
|
|
if ((*shead)->prev)
|
|
(*shead)->prev->next = stail->next;
|
|
if (stail->next)
|
|
stail->next->prev = (*shead)->prev;
|
|
*shead = stail->next;
|
|
}
|
|
}
|
|
|
|
/* Returns TRUE if this line is a label definition.
|
|
|
|
* If so, start will point to the start of the label name,
|
|
* and len will be it's length.
|
|
*/
|
|
bool
|
|
isLabelDefinition (const char *line, const char **start, int *len, bool isPeepRule)
|
|
{
|
|
const char *cp = line;
|
|
|
|
/* This line is a label if it consists of:
|
|
* [optional whitespace] followed by identifier chars
|
|
* (alnum | $ | _ ) followed by a colon.
|
|
*/
|
|
|
|
while (*cp && ISCHARSPACE (*cp))
|
|
{
|
|
cp++;
|
|
}
|
|
|
|
if (!*cp)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*start = cp;
|
|
|
|
while (ISCHARALNUM (*cp) || (*cp == '$') || (*cp == '_') ||
|
|
(isPeepRule && (*cp == '%')))
|
|
{
|
|
cp++;
|
|
}
|
|
|
|
if ((cp == *start) || (*cp != ':'))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*len = (cp - (*start));
|
|
return TRUE;
|
|
}
|
|
|
|
/* Not perfect, will not find all references yet.
|
|
Will however find references in call on Z80, which is sufficient to fix #2970351. */
|
|
bool
|
|
isLabelReference (const char *line, const char **start, int *len)
|
|
{
|
|
const char *s, *e;
|
|
if (!TARGET_Z80_LIKE && !TARGET_IS_STM8 && !TARGET_PDK_LIKE)
|
|
return FALSE;
|
|
|
|
s = line;
|
|
while (ISCHARSPACE (*s))
|
|
++s;
|
|
|
|
if(strncmp(s, "call", 4))
|
|
return FALSE;
|
|
s += 4;
|
|
|
|
while (ISCHARSPACE (*s))
|
|
++s;
|
|
|
|
/* Skip condition in conditional call */
|
|
if (strchr(s, ','))
|
|
s = strchr(s, ',') + 1;
|
|
|
|
e = s, *len = 0;
|
|
while(*e && !ISCHARSPACE (*e) && *e != ';')
|
|
++e, ++(*len);
|
|
|
|
*start = s;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Quick & dirty string hash function. */
|
|
static int
|
|
hashSymbolName (const char *name)
|
|
{
|
|
int hash = 0;
|
|
|
|
while (*name)
|
|
{
|
|
hash = (hash << 6) ^ *name;
|
|
name++;
|
|
}
|
|
|
|
if (hash < 0)
|
|
{
|
|
hash = -hash;
|
|
}
|
|
|
|
return hash % HTAB_SIZE;
|
|
}
|
|
|
|
/* Build a hash of all labels in the passed set of lines
|
|
* and how many times they are referenced.
|
|
*/
|
|
static void
|
|
buildLabelRefCountHash (lineNode *head)
|
|
{
|
|
lineNode *line;
|
|
const char *label;
|
|
int labelLen;
|
|
int i;
|
|
|
|
assert (labelHash == NULL);
|
|
labelHash = newHashTable (HTAB_SIZE);
|
|
|
|
/* First pass: locate all the labels. */
|
|
for (line = head; line; line = line->next)
|
|
{
|
|
bool ref = FALSE;
|
|
/* run isLabelDefinition to:
|
|
- look for labels in inline assembler
|
|
- calculate labelLen
|
|
*/
|
|
if ((line->isLabel || line->isInline) && isLabelDefinition (line->line, &label, &labelLen, FALSE) ||
|
|
(ref = TRUE) && isLabelReference (line->line, &label, &labelLen))
|
|
{
|
|
labelHashEntry *entry, *e;
|
|
|
|
assert (labelLen <= SDCC_NAME_MAX);
|
|
|
|
entry = traceAlloc (&_G.labels, Safe_alloc(sizeof (labelHashEntry)));
|
|
|
|
memcpy (entry->name, label, labelLen);
|
|
entry->name[labelLen] = 0;
|
|
entry->refCount = -1;
|
|
|
|
for (e = hTabFirstItemWK (labelHash, hashSymbolName (entry->name)); e; e = hTabNextItemWK (labelHash))
|
|
if (!strcmp (entry->name, e->name))
|
|
goto c;
|
|
|
|
/* Assume function entry points are referenced somewhere, */
|
|
/* even if we can't find a reference (might be from outside */
|
|
/* the function) */
|
|
if (line->ic && (line->ic->op == FUNCTION) || ref)
|
|
entry->refCount++;
|
|
|
|
hTabAddItem (&labelHash, hashSymbolName (entry->name), entry);
|
|
}
|
|
c:;
|
|
}
|
|
|
|
|
|
/* Second pass: for each line, note all the referenced labels. */
|
|
/* This is ugly, O(N^2) stuff. Optimizations welcome... */
|
|
for (line = head; line; line = line->next)
|
|
{
|
|
if (line->isComment)
|
|
continue;
|
|
|
|
/* Padauk skip instructions */
|
|
if (TARGET_PDK_LIKE &&
|
|
(!strncmp(line->line, "ceqsn", 5) || !strncmp(line->line, "cneqsn", 6) ||
|
|
!strncmp(line->line, "t0sn", 4) || !strncmp(line->line, "t1sn", 4) ||
|
|
!strncmp(line->line, "izsn", 4) || !strncmp(line->line, "dzsn", 4)))
|
|
{
|
|
const lineNode *const l = line->next->next;
|
|
wassert (l);
|
|
if (l->isLabel && isLabelDefinition (l->line, &label, &labelLen, false))
|
|
{
|
|
char name[SDCC_NAME_MAX];
|
|
strcpy(name, label);
|
|
name[labelLen] = 0;
|
|
|
|
labelHashEntry *e = hTabFirstItemWK (labelHash, hashSymbolName (name));
|
|
if (e)
|
|
e->refCount++;
|
|
}
|
|
}
|
|
|
|
|
|
for (i = 0; i < HTAB_SIZE; i++)
|
|
{
|
|
labelHashEntry *thisEntry;
|
|
|
|
thisEntry = hTabFirstItemWK (labelHash, i);
|
|
|
|
while (thisEntry)
|
|
{
|
|
const char *s;
|
|
if ((s = strstr (line->line, thisEntry->name)) && !ISCHARALNUM (*(s + strlen (thisEntry->name))) && (s == line->line || !ISCHARALNUM (*(s - 1))))
|
|
thisEntry->refCount++;
|
|
|
|
thisEntry = hTabNextItemWK (labelHash);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* Spew the contents of the table. Debugging fun only. */
|
|
for (i = 0; i < HTAB_SIZE; i++)
|
|
{
|
|
labelHashEntry *thisEntry;
|
|
|
|
thisEntry = hTabFirstItemWK (labelHash, i);
|
|
|
|
while (thisEntry)
|
|
{
|
|
fprintf (stderr, "label: %s ref %d\n",
|
|
thisEntry->name, thisEntry->refCount);
|
|
thisEntry = hTabNextItemWK (labelHash);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* How does this work?
|
|
peepHole
|
|
For each rule,
|
|
For each line,
|
|
Try to match
|
|
If it matches,
|
|
replace and restart.
|
|
|
|
matchRule
|
|
matchLine
|
|
|
|
Where is stuff allocated?
|
|
|
|
*/
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* peepHole - matches & substitutes rules */
|
|
/*-----------------------------------------------------------------*/
|
|
void
|
|
peepHole (lineNode ** pls)
|
|
{
|
|
lineNode *spl;
|
|
peepRule *pr;
|
|
lineNode *mtail = NULL;
|
|
bool restart, replaced;
|
|
|
|
#if !OPT_DISABLE_PIC14 || !OPT_DISABLE_PIC16
|
|
/* The PIC port uses a different peep hole optimizer based on "pCode" */
|
|
if (TARGET_PIC_LIKE)
|
|
return;
|
|
#endif
|
|
|
|
assert(labelHash == NULL);
|
|
|
|
do
|
|
{
|
|
restart = FALSE;
|
|
|
|
/* for all rules */
|
|
for (pr = rootRules; pr; pr = pr->next)
|
|
{
|
|
if (restart && pr->barrier)
|
|
break;
|
|
|
|
for (spl = *pls; spl; spl = replaced ? spl : spl->next)
|
|
{
|
|
replaced = FALSE;
|
|
|
|
/* if inline assembler then no peep hole */
|
|
if (spl->isInline)
|
|
continue;
|
|
|
|
/* don't waste time starting a match on debug symbol
|
|
** or comment */
|
|
if (spl->isDebug || spl->isComment || *(spl->line)==';')
|
|
continue;
|
|
|
|
mtail = NULL;
|
|
|
|
/* Tidy up any data stored in the hTab */
|
|
|
|
/* if it matches */
|
|
if (matchRule (spl, &mtail, pr, *pls))
|
|
{
|
|
/* restart at the replaced line */
|
|
replaced = TRUE;
|
|
|
|
/* then replace */
|
|
if (spl == *pls)
|
|
{
|
|
replaceRule (pls, mtail, pr);
|
|
spl = *pls;
|
|
}
|
|
else
|
|
replaceRule (&spl, mtail, pr);
|
|
|
|
/* if restart rule type then
|
|
start at the top again */
|
|
if (pr->restart)
|
|
{
|
|
restart = TRUE;
|
|
}
|
|
}
|
|
|
|
if (pr->vars)
|
|
{
|
|
hTabDeleteAll (pr->vars);
|
|
Safe_free (pr->vars);
|
|
pr->vars = NULL;
|
|
}
|
|
|
|
freeTrace (&_G.values);
|
|
}
|
|
}
|
|
} while (restart == TRUE);
|
|
|
|
if (labelHash)
|
|
{
|
|
hTabDeleteAll (labelHash);
|
|
freeTrace (&_G.labels);
|
|
}
|
|
labelHash = NULL;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* readFileIntoBuffer - reads a file into a string buffer */
|
|
/*-----------------------------------------------------------------*/
|
|
static char *
|
|
readFileIntoBuffer (char *fname)
|
|
{
|
|
FILE *f;
|
|
char *rs = NULL;
|
|
int nch = 0;
|
|
int ch;
|
|
char lb[MAX_PATTERN_LEN];
|
|
|
|
if (!(f = fopen (fname, "r")))
|
|
{
|
|
fprintf (stderr, "cannot open peep rule file\n");
|
|
return NULL;
|
|
}
|
|
|
|
while ((ch = fgetc (f)) != EOF)
|
|
{
|
|
lb[nch++] = ch;
|
|
|
|
/* if we maxed out our local buffer */
|
|
if (nch >= (MAX_PATTERN_LEN - 2))
|
|
{
|
|
lb[nch] = '\0';
|
|
/* copy it into allocated buffer */
|
|
if (rs)
|
|
{
|
|
rs = Safe_realloc (rs, strlen (rs) + strlen (lb) + 1);
|
|
strncatz (rs, lb, strlen (rs) + strlen (lb) + 1);
|
|
}
|
|
else
|
|
{
|
|
rs = Safe_strdup (lb);
|
|
}
|
|
nch = 0;
|
|
}
|
|
}
|
|
fclose (f);
|
|
|
|
/* if some characters left over */
|
|
if (nch)
|
|
{
|
|
lb[nch] = '\0';
|
|
/* copy it into allocated buffer */
|
|
if (rs)
|
|
{
|
|
rs = Safe_realloc (rs, strlen (rs) + strlen (lb) + 1);
|
|
strncatz (rs, lb, strlen (rs) + strlen (lb) + 1);
|
|
}
|
|
else
|
|
{
|
|
rs = Safe_strdup (lb);
|
|
}
|
|
}
|
|
return rs;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* initPeepHole - initialises the peep hole optimizer stuff */
|
|
/*-----------------------------------------------------------------*/
|
|
void
|
|
initPeepHole (void)
|
|
{
|
|
char *s;
|
|
|
|
/* read in the default rules */
|
|
if (!options.nopeep)
|
|
{
|
|
readRules (port->peep.default_rules);
|
|
}
|
|
|
|
/* if we have any additional file read it too */
|
|
if (options.peep_file)
|
|
{
|
|
readRules (s = readFileIntoBuffer (options.peep_file));
|
|
setToNull ((void *) &s);
|
|
/* override nopeep setting, default rules have not been read */
|
|
options.nopeep = 0;
|
|
}
|
|
|
|
#if !OPT_DISABLE_PIC14
|
|
/* Convert the peep rules into pcode.
|
|
NOTE: this is only support in the PIC port (at the moment)
|
|
*/
|
|
if (TARGET_IS_PIC14)
|
|
peepRules2pCode (rootRules);
|
|
#endif
|
|
|
|
#if !OPT_DISABLE_PIC16
|
|
/* Convert the peep rules into pcode.
|
|
NOTE: this is only support in the PIC port (at the moment)
|
|
and the PIC16 port (VR 030601)
|
|
*/
|
|
if (TARGET_IS_PIC16)
|
|
pic16_peepRules2pCode (rootRules);
|
|
|
|
#endif
|
|
}
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
/* StrStr - case-insensitive strstr implementation */
|
|
/*-----------------------------------------------------------------*/
|
|
const char * StrStr (const char * str1, const char * str2)
|
|
{
|
|
const char * cp = str1;
|
|
const char * s1;
|
|
const char * s2;
|
|
|
|
if ( !*str2 )
|
|
return str1;
|
|
|
|
while (*cp)
|
|
{
|
|
s1 = cp;
|
|
s2 = str2;
|
|
|
|
while ( *s1 && *s2 && !(tolower(*s1)-tolower(*s2)) )
|
|
s1++, s2++;
|
|
|
|
if (!*s2)
|
|
return( cp );
|
|
|
|
cp++;
|
|
}
|
|
|
|
return (NULL) ;
|
|
}
|