Updated cstrike gamedata.
1 /* Pawn compiler - Staging buffer and optimizer
5 * The staging buffer allows buffered output of generated code, deletion
6 * of redundant code, optimization by a tinkering process and reversing
7 * the ouput of evaluated expressions (which is used for the reversed
8 * evaluation of arguments in functions).
9 * Initially, stgwrite() writes to the file directly, but after a call to
10 * stgset(TRUE), output is redirected to the buffer. After a call to
11 * stgset(FALSE), stgwrite()'s output is directed to the file again. Thus
12 * only one routine is used for writing to the output, which can be
13 * buffered output or direct output.
15 * staging buffer variables: stgbuf - the buffer
16 * stgidx - current index in the staging buffer
17 * staging - if true, write to the staging buffer;
18 * if false, write to file directly.
20 * The peephole optimizer uses a dual "pipeline". The staging buffer (described
21 * above) gets optimized for each expression or sub-expression in a function
22 * call. The peephole optimizer is recursive, but it does not span multiple
23 * sub-expressions. However, the data gets written to a second buffer that
24 * behaves much like the staging buffer. This second buffer gathers all
25 * optimized strings from the staging buffer for a complete expression. The
26 * peephole optmizer then runs over this second buffer to find optimzations
27 * across function parameter boundaries.
30 * Copyright (c) ITB CompuPhase, 1997-2006
32 * This software is provided "as-is", without any express or implied warranty.
33 * In no event will the authors be held liable for any damages arising from
34 * the use of this software.
36 * Permission is granted to anyone to use this software for any purpose,
37 * including commercial applications, and to alter it and redistribute it
38 * freely, subject to the following restrictions:
40 * 1. The origin of this software must not be misrepresented; you must not
41 * claim that you wrote the original software. If you use this software in
42 * a product, an acknowledgment in the product documentation would be
43 * appreciated but is not required.
44 * 2. Altered source versions must be plainly marked as such, and must not be
45 * misrepresented as being the original software.
46 * 3. This notice may not be removed or altered from any source distribution.
52 #include <stdlib.h> /* for atoi() */
56 #include <alloc/fortify.h>
62 #pragma warning(disable:4125) /* decimal digit terminates octal escape sequence */
71 static int stgstring(char *start,char *end);
72 static void stgopt(char *start,char *end,int (*outputfunc)(char *str));
76 #define sSTG_MAX 20480
78 static char *stgbuf=NULL;
79 static int stgmax=0; /* current size of the staging buffer */
81 static char *stgpipe=NULL;
82 static int pipemax=0; /* current size of the stage pipe, a second staging buffer */
85 #define CHECK_STGBUFFER(index) if ((int)(index)>=stgmax) grow_stgbuffer(&stgbuf, &stgmax, (index)+1)
86 #define CHECK_STGPIPE(index) if ((int)(index)>=pipemax) grow_stgbuffer(&stgpipe, &pipemax, (index)+1)
88 static void grow_stgbuffer(char **buffer, int *curmax, int requiredsize)
91 int clear= (*buffer==NULL); /* if previously none, empty buffer explicitly */
93 assert(*curmax<requiredsize);
94 /* if the staging buffer (holding intermediate code for one line) grows
95 * over a few kBytes, there is probably a run-away expression
97 if (requiredsize>sSTG_MAX)
98 error(102,"staging buffer"); /* staging buffer overflow (fatal error) */
99 *curmax=requiredsize+sSTG_GROW;
101 p=(char *)realloc(*buffer,*curmax*sizeof(char));
103 p=(char *)malloc(*curmax*sizeof(char));
105 error(102,"staging buffer"); /* staging buffer overflow (fatal error) */
111 SC_FUNC void stgbuffer_cleanup(void)
126 /* the variables "stgidx" and "staging" are declared in "scvars.c" */
130 * Copies a mark into the staging buffer. At this moment there are three
132 * sSTARTREORDER identifies the beginning of a series of expression
133 * strings that must be written to the output file in
135 * sENDREORDER identifies the end of 'reverse evaluation'
136 * sEXPRSTART + idx only valid within a block that is evaluated in
137 * reordered order, it identifies the start of an
138 * expression; the "idx" value is the argument position
140 * Global references: stgidx (altered)
142 * staging (referred to only)
144 SC_FUNC void stgmark(char mark)
147 CHECK_STGBUFFER(stgidx);
148 stgbuf[stgidx++]=mark;
152 static int rebuffer(char *str)
154 if (sc_status==statWRITE) {
155 if (pipeidx>=2 && stgpipe[pipeidx-1]=='\0' && stgpipe[pipeidx-2]!='\n')
156 pipeidx-=1; /* overwrite last '\0' */
157 while (*str!='\0') { /* copy to staging buffer */
158 CHECK_STGPIPE(pipeidx);
159 stgpipe[pipeidx++]=*str++;
161 CHECK_STGPIPE(pipeidx);
162 stgpipe[pipeidx++]='\0';
167 static int filewrite(char *str)
169 if (sc_status==statWRITE)
170 return pc_writeasm(outf,str);
176 * Writes the string "st" to the staging buffer or to the output file. In the
177 * case of writing to the staging buffer, the terminating byte of zero is
178 * copied too, but... the optimizer can only work on complete lines (not on
179 * fractions of it. Therefore if the string is staged, if the last character
180 * written to the buffer is a '\0' and the previous-to-last is not a '\n',
181 * the string is concatenated to the last string in the buffer (the '\0' is
182 * overwritten). This also means an '\n' used in the middle of a string isn't
183 * recognized and could give wrong results with the optimizer.
184 * Even when writing to the output file directly, all strings are buffered
185 * until a whole line is complete.
187 * Global references: stgidx (altered)
189 * staging (referred to only)
191 SC_FUNC void stgwrite(const char *st)
196 assert(stgidx==0 || stgbuf!=NULL); /* staging buffer must be valid if there is (apparently) something in it */
197 if (stgidx>=2 && stgbuf[stgidx-1]=='\0' && stgbuf[stgidx-2]!='\n')
198 stgidx-=1; /* overwrite last '\0' */
199 while (*st!='\0') { /* copy to staging buffer */
200 CHECK_STGBUFFER(stgidx);
201 stgbuf[stgidx++]=*st++;
203 CHECK_STGBUFFER(stgidx);
204 stgbuf[stgidx++]='\0';
206 len=(stgbuf!=NULL) ? strlen(stgbuf) : 0;
207 CHECK_STGBUFFER(len+strlen(st)+1);
210 if (len>0 && stgbuf[len-1]=='\n') {
219 * Writes the staging buffer to the output file via stgstring() (for
220 * reversing expressions in the buffer) and stgopt() (for optimizing). It
223 * Global references: stgidx (altered)
224 * stgbuf (referred to only)
225 * staging (referred to only)
227 SC_FUNC void stgout(int index)
236 /* first pass: sub-expressions */
237 if (sc_status==statWRITE)
238 reordered=stgstring(&stgbuf[index],&stgbuf[stgidx]);
241 /* second pass: optimize the buffer created in the first pass */
242 if (sc_status==statWRITE) {
244 stgopt(stgpipe,stgpipe+pipeidx,filewrite);
246 /* there is no sense in re-optimizing if the order of the sub-expressions
247 * did not change; so output directly
249 for (idx=0; idx<pipeidx; idx+=strlen(stgpipe+idx)+1)
250 filewrite(stgpipe+idx);
253 pipeidx=0; /* reset second pipe */
262 * Analyses whether code strings should be output to the file as they appear
263 * in the staging buffer or whether portions of it should be re-ordered.
264 * Re-ordering takes place in function argument lists; Pawn passes arguments
265 * to functions from right to left. When arguments are "named" rather than
266 * positional, the order in the source stream is indeterminate.
267 * This function calls itself recursively in case it needs to re-order code
268 * strings, and it uses a private stack (or list) to mark the start and the
269 * end of expressions in their correct (reversed) order.
270 * In any case, stgstring() sends a block as large as possible to the
271 * optimizer stgopt().
273 * In "reorder" mode, each set of code strings must start with the token
274 * sEXPRSTART, even the first. If the token sSTARTREORDER is represented
275 * by '[', sENDREORDER by ']' and sEXPRSTART by '|' the following applies:
276 * '[]...' valid, but useless; no output
277 * '[|...] valid, but useless; only one string
278 * '[|...|...] valid and usefull
279 * '[...|...] invalid, first string doesn't start with '|'
282 static int stgstring(char *start,char *end)
290 if (*start==sSTARTREORDER) {
291 start+=1; /* skip token */
292 /* allocate a argstack with sMAXARGS items */
293 stack=(argstack *)malloc(sMAXARGS*sizeof(argstack));
295 error(103); /* insufficient memory */
296 reordered=1; /* mark that the expression is reordered */
297 nest=1; /* nesting counter */
298 argc=0; /* argument counter */
299 arg=-1; /* argument index; no valid argument yet */
311 if ((*start & sEXPRSTART)==sEXPRSTART) {
314 stack[arg].end=start-1; /* finish previous argument */
315 arg=(unsigned char)*start - sEXPRSTART;
316 stack[arg].start=start+1;
322 start+=strlen(start)+1;
325 } while (nest); /* enddo */
327 stack[arg].end=start-1; /* finish previous argument */
330 stgstring(stack[argc].start,stack[argc].end);
335 while (ptr<end && *ptr!=sSTARTREORDER)
337 stgopt(start,ptr,rebuffer);
346 * Scraps code from the staging buffer by resetting "stgidx" to "index".
348 * Global references: stgidx (altered)
349 * staging (reffered to only)
351 SC_FUNC void stgdel(int index,cell code_index)
359 SC_FUNC int stgget(int *index,cell *code_index)
363 *code_index=code_idx;
370 * Sets staging on or off. If it's turned off, the staging buffer must be
371 * initialized to an empty string. If it's turned on, the routine makes sure
372 * the index ("stgidx") is set to 0 (it should already be 0).
374 * Global references: staging (altered)
376 * stgbuf (contents altered)
378 SC_FUNC void stgset(int onoff)
384 CHECK_STGBUFFER(stgidx);
385 /* write any contents that may be put in the buffer by stgwrite()
386 * when "staging" was 0
388 if (strlen(stgbuf)>0)
395 * Initialize all sequence strings of the peehole optimizer. The strings
396 * are embedded in the .EXE file in compressed format, here we expand
397 * them (and allocate memory for the sequences).
399 static SEQUENCE *sequences;
401 SC_FUNC int phopt_init(void)
406 /* count number of sequences */
407 for (number=0; sequences_cmp[number].find!=NULL; number++)
409 number++; /* include an item for the NULL terminator */
411 if ((sequences=(SEQUENCE*)malloc(number * sizeof(SEQUENCE)))==NULL)
414 /* pre-initialize all to NULL (in case of failure) */
415 for (i=0; i<number; i++) {
416 sequences[i].find=NULL;
417 sequences[i].replace=NULL;
418 sequences[i].savesize=0;
421 /* expand all strings */
422 for (i=0; i<number-1; i++) {
423 len = strexpand(str,(unsigned char*)sequences_cmp[i].find,sizeof str,SCPACK_TABLE);
424 assert(len<=sizeof str);
425 assert(len==(int)strlen(str)+1);
426 sequences[i].find=(char*)malloc(len);
427 if (sequences[i].find!=NULL)
428 strcpy(sequences[i].find,str);
429 len = strexpand(str,(unsigned char*)sequences_cmp[i].replace,sizeof str,SCPACK_TABLE);
430 assert(len<=sizeof str);
431 assert(len==(int)strlen(str)+1);
432 sequences[i].replace=(char*)malloc(len);
433 if (sequences[i].replace!=NULL)
434 strcpy(sequences[i].replace,str);
435 sequences[i].savesize=sequences_cmp[i].savesize;
436 if (sequences[i].find==NULL || sequences[i].replace==NULL)
437 return phopt_cleanup();
443 SC_FUNC int phopt_cleanup(void)
446 if (sequences!=NULL) {
448 while (sequences[i].find!=NULL || sequences[i].replace!=NULL) {
449 if (sequences[i].find!=NULL)
450 free(sequences[i].find);
451 if (sequences[i].replace!=NULL)
452 free(sequences[i].replace);
461 #define MAX_OPT_VARS 5
462 #define MAX_OPT_CAT 5 /* max. values that are concatenated */
463 #if sNAMEMAX > (PAWN_CELL_SIZE/4) * MAX_OPT_CAT
464 #define MAX_ALIAS sNAMEMAX
466 #define MAX_ALIAS (PAWN_CELL_SIZE/4) * MAX_OPT_CAT
469 static int matchsequence(char *start,char *end,char *pattern,
470 char symbols[MAX_OPT_VARS][MAX_ALIAS+1],
474 char str[MAX_ALIAS+1];
475 char *start_org=start;
480 for (var=0; var<MAX_OPT_VARS; var++)
481 symbols[var][0]='\0';
483 while (*start=='\t' || *start==' ')
489 case '%': /* new "symbol" */
491 assert(isdigit(*pattern));
492 var=atoi(pattern) - 1;
493 assert(var>=0 && var<MAX_OPT_VARS);
494 assert(*start=='-' || alphanum(*start));
495 for (i=0; start<end && (*start=='-' || *start=='+' || alphanum(*start)); i++,start++) {
496 assert(i<=MAX_ALIAS);
500 if (symbols[var][0]!='\0') {
501 if (strcmp(symbols[var],str)!=0)
502 return FALSE; /* symbols should be identical */
504 strcpy(symbols[var],str);
508 value=-strtol(pattern+1,&pattern,16);
509 ptr=itoh((ucell)value);
511 if (tolower(*start) != tolower(*ptr))
516 pattern--; /* there is an increment following at the end of the loop */
519 if (*start!='\t' && *start!=' ')
521 while (start<end && (*start=='\t' || *start==' '))
525 while (start<end && (*start=='\t' || *start==' '))
526 start++; /* skip trailing white space */
528 while (start<end && *start!='\n')
529 start++; /* skip trailing comment */
532 assert(*(start+1)=='\0');
533 start+=2; /* skip '\n' and '\0' */
534 if (*(pattern+1)!='\0')
535 while (start<end && *start=='\t' || *start==' ')
536 start++; /* skip leading white space of next instruction */
539 if (tolower(*start) != tolower(*pattern))
546 *match_length=(int)(start-start_org);
550 static char *replacesequence(char *pattern,char symbols[MAX_OPT_VARS][MAX_ALIAS+1],int *repl_length)
556 /* calculate the length of the new buffer
557 * this is the length of the pattern plus the length of all symbols (note
558 * that the same symbol may occur multiple times in the pattern) plus
559 * line endings and startings ('\t' to start a line and '\n\0' to end one)
561 assert(repl_length!=NULL);
567 lptr++; /* skip '%' */
568 assert(isdigit(*lptr));
570 assert(var>=0 && var<MAX_OPT_VARS);
571 assert(symbols[var][0]!='\0'); /* variable should be defined */
572 *repl_length+=strlen(symbols[var]);
575 *repl_length+=3; /* '\t', '\n' & '\0' */
583 /* allocate a buffer to replace the sequence in */
584 if ((buffer=(char*)malloc(*repl_length))==NULL)
585 return (char*)error(103);
587 /* replace the pattern into this temporary buffer */
589 *lptr++='\t'; /* the "replace" patterns do not have tabs */
591 assert((int)(lptr-buffer)<*repl_length);
594 /* write out the symbol */
596 assert(isdigit(*pattern));
597 var=atoi(pattern) - 1;
598 assert(var>=0 && var<MAX_OPT_VARS);
599 assert(symbols[var][0]!='\0'); /* variable should be defined */
600 strcpy(lptr,symbols[var]);
601 lptr+=strlen(symbols[var]);
604 /* finish the line, optionally start the next line with an indent */
607 if (*(pattern+1)!='\0')
616 assert((int)(lptr-buffer)==*repl_length);
620 static void strreplace(char *dest,char *replace,int sub_length,int repl_length,int dest_length)
622 int offset=sub_length-repl_length;
623 if (offset>0) { /* delete a section */
624 memmove(dest,dest+offset,dest_length-offset);
625 memset(dest+dest_length-offset,0xcc,offset); /* not needed, but for cleanlyness */
626 } else if (offset<0) { /* insert a section */
627 memmove(dest-offset, dest, dest_length);
629 memcpy(dest, replace, repl_length);
634 * Optimizes the staging buffer by checking for series of instructions that
635 * can be coded more compact. The routine expects the lines in the staging
636 * buffer to be separated with '\n' and '\0' characters.
638 * The longest sequences should probably be checked first.
641 static void stgopt(char *start,char *end,int (*outputfunc)(char *str))
643 char symbols[MAX_OPT_VARS][MAX_ALIAS+1];
644 int seq,match_length,repl_length;
646 char *debut=start; /* save original start of the buffer */
648 assert(sequences!=NULL);
649 /* do not match anything if debug-level is maximum */
650 if (pc_optimize>sOPTIMIZE_NONE && sc_status==statWRITE) {
656 while (sequences[seq].find!=NULL) {
658 if (*sequences[seq].find=='\0') {
659 if (pc_optimize==sOPTIMIZE_NOMACRO) {
660 break; /* don't look further */
662 seq++; /* continue with next string */
666 if (matchsequence(start,end,sequences[seq].find,symbols,&match_length)) {
667 char *replace=replacesequence(sequences[seq].replace,symbols,&repl_length);
668 /* If the replacement is bigger than the original section, we may need
669 * to "grow" the staging buffer. This is quite complex, due to the
670 * re-ordering of expressions that can also happen in the staging
671 * buffer. In addition, it should not happen: the peephole optimizer
672 * must replace sequences with *shorter* sequences, not longer ones.
673 * So, I simply forbid sequences that are longer than the ones they
674 * are meant to replace.
676 assert(match_length>=repl_length);
677 if (match_length>=repl_length) {
678 strreplace(start,replace,match_length,repl_length,(int)(end-start));
679 end-=match_length-repl_length;
681 code_idx-=sequences[seq].savesize;
682 seq=0; /* restart search for matches */
685 /* actually, we should never get here (match_length<repl_length) */
693 assert(sequences[seq].find==NULL || *sequences[seq].find=='\0' && pc_optimize==sOPTIMIZE_NOMACRO);
694 start += strlen(start) + 1; /* to next string */
695 } /* while (start<end) */
697 } /* if (pc_optimize>sOPTIMIZE_NONE && sc_status==statWRITE) */
699 for (start=debut; start<end; start+=strlen(start)+1)