1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22
23 #include "../../qcommon/q_platform.h"
24 #include "cmdlib.h"
25 #include "mathlib.h"
26 #include "../../qcommon/qfiles.h"
27
28 /* 19079 total symbols in FI, 2002 Jan 23 */
29 #define DEFAULT_HASHTABLE_SIZE 2048
30
31 char outputFilename[MAX_OS_PATH];
32
33 // the zero page size is just used for detecting run time faults
34 #define ZERO_PAGE_SIZE 0 // 256
35
36 typedef enum {
37 OP_UNDEF,
38
39 OP_IGNORE,
40
41 OP_BREAK,
42
43 OP_ENTER,
44 OP_LEAVE,
45 OP_CALL,
46 OP_PUSH,
47 OP_POP,
48
49 OP_CONST,
50 OP_LOCAL,
51
52 OP_JUMP,
53
54 //-------------------
55
56 OP_EQ,
57 OP_NE,
58
59 OP_LTI,
60 OP_LEI,
61 OP_GTI,
62 OP_GEI,
63
64 OP_LTU,
65 OP_LEU,
66 OP_GTU,
67 OP_GEU,
68
69 OP_EQF,
70 OP_NEF,
71
72 OP_LTF,
73 OP_LEF,
74 OP_GTF,
75 OP_GEF,
76
77 //-------------------
78
79 OP_LOAD1,
80 OP_LOAD2,
81 OP_LOAD4,
82 OP_STORE1,
83 OP_STORE2,
84 OP_STORE4, // *(stack[top-1]) = stack[yop
85 OP_ARG,
86 OP_BLOCK_COPY,
87
88 //-------------------
89
90 OP_SEX8,
91 OP_SEX16,
92
93 OP_NEGI,
94 OP_ADD,
95 OP_SUB,
96 OP_DIVI,
97 OP_DIVU,
98 OP_MODI,
99 OP_MODU,
100 OP_MULI,
101 OP_MULU,
102
103 OP_BAND,
104 OP_BOR,
105 OP_BXOR,
106 OP_BCOM,
107
108 OP_LSH,
109 OP_RSHI,
110 OP_RSHU,
111
112 OP_NEGF,
113 OP_ADDF,
114 OP_SUBF,
115 OP_DIVF,
116 OP_MULF,
117
118 OP_CVIF,
119 OP_CVFI
120 } opcode_t;
121
122 typedef struct {
123 int imageBytes; // after decompression
124 int entryPoint;
125 int stackBase;
126 int stackSize;
127 } executableHeader_t;
128
129 typedef enum {
130 CODESEG,
131 DATASEG, // initialized 32 bit data, will be byte swapped
132 LITSEG, // strings
133 BSSSEG, // 0 filled
134 JTRGSEG, // pseudo-segment that contains only jump table targets
135 NUM_SEGMENTS
136 } segmentName_t;
137
138 #define MAX_IMAGE 0x400000
139
140 typedef struct {
141 byte image[MAX_IMAGE];
142 int imageUsed;
143 int segmentBase; // only valid on second pass
144 } segment_t;
145
146 typedef struct symbol_s {
147 struct symbol_s *next;
148 int hash;
149 segment_t *segment;
150 char *name;
151 int value;
152 } symbol_t;
153
154 typedef struct hashchain_s {
155 void *data;
156 struct hashchain_s *next;
157 } hashchain_t;
158
159 typedef struct hashtable_s {
160 int buckets;
161 hashchain_t **table;
162 } hashtable_t;
163
164 int symtablelen = DEFAULT_HASHTABLE_SIZE;
165 hashtable_t *symtable;
166 hashtable_t *optable;
167
168 segment_t segment[NUM_SEGMENTS];
169 segment_t *currentSegment;
170
171 int passNumber;
172
173 int numSymbols;
174 int errorCount;
175
176 typedef struct options_s {
177 qboolean verbose;
178 qboolean writeMapFile;
179 qboolean vanillaQ3Compatibility;
180 } options_t;
181
182 options_t options = { 0 };
183
184 symbol_t *symbols;
185 symbol_t *lastSymbol = 0; /* Most recent symbol defined. */
186
187
188 #define MAX_ASM_FILES 256
189 int numAsmFiles;
190 char *asmFiles[MAX_ASM_FILES];
191 char *asmFileNames[MAX_ASM_FILES];
192
193 int currentFileIndex;
194 char *currentFileName;
195 int currentFileLine;
196
197 //int stackSize = 16384;
198 int stackSize = 0x10000;
199
200 // we need to convert arg and ret instructions to
201 // stores to the local stack frame, so we need to track the
202 // characteristics of the current functions stack frame
203 int currentLocals; // bytes of locals needed by this function
204 int currentArgs; // bytes of largest argument list called from this function
205 int currentArgOffset; // byte offset in currentArgs to store next arg, reset each call
206
207 #define MAX_LINE_LENGTH 1024
208 char lineBuffer[MAX_LINE_LENGTH];
209 int lineParseOffset;
210 char token[MAX_LINE_LENGTH];
211
212 int instructionCount;
213
214 typedef struct {
215 char *name;
216 int opcode;
217 } sourceOps_t;
218
219 sourceOps_t sourceOps[] = {
220 #include "opstrings.h"
221 };
222
223 #define NUM_SOURCE_OPS ( sizeof( sourceOps ) / sizeof( sourceOps[0] ) )
224
225 int opcodesHash[ NUM_SOURCE_OPS ];
226
227
228
vreport(const char * fmt,va_list vp)229 static int vreport (const char* fmt, va_list vp)
230 {
231 if (options.verbose != qtrue)
232 return 0;
233 return vprintf(fmt, vp);
234 }
235
report(const char * fmt,...)236 static int report (const char *fmt, ...)
237 {
238 va_list va;
239 int retval;
240
241 va_start(va, fmt);
242 retval = vreport(fmt, va);
243 va_end(va);
244 return retval;
245 }
246
247 /* The chain-and-bucket hash table. -PH */
248
hashtable_init(hashtable_t * H,int buckets)249 static void hashtable_init (hashtable_t *H, int buckets)
250 {
251 H->buckets = buckets;
252 H->table = calloc(H->buckets, sizeof(*(H->table)));
253 return;
254 }
255
hashtable_new(int buckets)256 static hashtable_t *hashtable_new (int buckets)
257 {
258 hashtable_t *H;
259
260 H = malloc(sizeof(hashtable_t));
261 hashtable_init(H, buckets);
262 return H;
263 }
264
265 /* No destroy/destructor. No need. */
266
hashtable_add(hashtable_t * H,int hashvalue,void * datum)267 static void hashtable_add (hashtable_t *H, int hashvalue, void *datum)
268 {
269 hashchain_t *hc, **hb;
270
271 hashvalue = (abs(hashvalue) % H->buckets);
272 hb = &(H->table[hashvalue]);
273 if (*hb == 0)
274 {
275 /* Empty bucket. Create new one. */
276 *hb = calloc(1, sizeof(**hb));
277 hc = *hb;
278 }
279 else
280 {
281 /* Get hc to point to last node in chain. */
282 for (hc = *hb; hc && hc->next; hc = hc->next);
283 hc->next = calloc(1, sizeof(*hc));
284 hc = hc->next;
285 }
286 hc->data = datum;
287 hc->next = 0;
288 return;
289 }
290
hashtable_get(hashtable_t * H,int hashvalue)291 static hashchain_t *hashtable_get (hashtable_t *H, int hashvalue)
292 {
293 hashvalue = (abs(hashvalue) % H->buckets);
294 return (H->table[hashvalue]);
295 }
296
hashtable_stats(hashtable_t * H)297 static void hashtable_stats (hashtable_t *H)
298 {
299 int len, empties, longest, nodes;
300 int i;
301 float meanlen;
302 hashchain_t *hc;
303
304 report("Stats for hashtable %08X", H);
305 empties = 0;
306 longest = 0;
307 nodes = 0;
308 for (i = 0; i < H->buckets; i++)
309 {
310 if (H->table[i] == 0)
311 { empties++; continue; }
312 for (hc = H->table[i], len = 0; hc; hc = hc->next, len++);
313 if (len > longest) { longest = len; }
314 nodes += len;
315 }
316 meanlen = (float)(nodes) / (H->buckets - empties);
317 #if 0
318 /* Long stats display */
319 report(" Total buckets: %d\n", H->buckets);
320 report(" Total stored nodes: %d\n", nodes);
321 report(" Longest chain: %d\n", longest);
322 report(" Empty chains: %d\n", empties);
323 report(" Mean non-empty chain length: %f\n", meanlen);
324 #else //0
325 /* Short stats display */
326 report(", %d buckets, %d nodes", H->buckets, nodes);
327 report("\n");
328 report(" Longest chain: %d, empty chains: %d, mean non-empty: %f", longest, empties, meanlen);
329 #endif //0
330 report("\n");
331 }
332
333
334 /* Kludge. */
335 /* Check if symbol already exists. */
336 /* Returns 0 if symbol does NOT already exist, non-zero otherwise. */
hashtable_symbol_exists(hashtable_t * H,int hash,char * sym)337 static int hashtable_symbol_exists (hashtable_t *H, int hash, char *sym)
338 {
339 hashchain_t *hc;
340 symbol_t *s;
341
342 hash = (abs(hash) % H->buckets);
343 hc = H->table[hash];
344 if (hc == 0)
345 {
346 /* Empty chain means this symbol has not yet been defined. */
347 return 0;
348 }
349 for (; hc; hc = hc->next)
350 {
351 s = (symbol_t*)hc->data;
352 // if ((hash == s->hash) && (strcmp(sym, s->name) == 0))
353 /* We _already_ know the hash is the same. That's why we're probing! */
354 if (strcmp(sym, s->name) == 0)
355 {
356 /* Symbol collisions -- symbol already exists. */
357 return 1;
358 }
359 }
360 return 0; /* Can't find collision. */
361 }
362
363
364
365
366 /* Comparator function for quicksorting. */
symlist_cmp(const void * e1,const void * e2)367 static int symlist_cmp (const void *e1, const void *e2)
368 {
369 const symbol_t *a, *b;
370
371 a = *(const symbol_t **)e1;
372 b = *(const symbol_t **)e2;
373 //crumb("Symbol comparison (1) %d to (2) %d\n", a->value, b->value);
374 return ( a->value - b->value);
375 }
376
377 /*
378 Sort the symbols list by using QuickSort (qsort()).
379 This may take a LOT of memory (a few megabytes?), but memory is cheap these days.
380 However, qsort(3) already exists, and I'm really lazy.
381 -PH
382 */
sort_symbols()383 static void sort_symbols ()
384 {
385 int i, elems;
386 symbol_t *s;
387 symbol_t **symlist;
388
389 //crumb("sort_symbols: Constructing symlist array\n");
390 for (elems = 0, s = symbols; s; s = s->next, elems++) /* nop */ ;
391 symlist = malloc(elems * sizeof(symbol_t*));
392 for (i = 0, s = symbols; s; s = s->next, i++)
393 {
394 symlist[i] = s;
395 }
396 //crumbf("sort_symbols: Quick-sorting %d symbols\n", elems);
397 qsort(symlist, elems, sizeof(symbol_t*), symlist_cmp);
398 //crumbf("sort_symbols: Reconstructing symbols list\n");
399 s = symbols = symlist[0];
400 for (i = 1; i < elems; i++)
401 {
402 s->next = symlist[i];
403 s = s->next;
404 }
405 lastSymbol = s;
406 s->next = 0;
407 //crumbf("sort_symbols: verifying..."); fflush(stdout);
408 for (i = 0, s = symbols; s; s = s->next, i++) /*nop*/ ;
409 //crumbf(" %d elements\n", i);
410 free(symlist); /* d'oh. no gc. */
411 }
412
413
414 #ifdef _MSC_VER
415 #define INT64 __int64
416 #define atoi64 _atoi64
417 #else
418 #define INT64 long long int
419 #define atoi64 atoll
420 #endif
421
422 /*
423 Problem:
424 BYTE values are specified as signed decimal string. A properly functional
425 atoip() will cap large signed values at 0x7FFFFFFF. Negative word values are
426 often specified as very large decimal values by lcc. Therefore, values that
427 should be between 0x7FFFFFFF and 0xFFFFFFFF come out as 0x7FFFFFFF when using
428 atoi(). Bad.
429
430 This function is one big evil hack to work around this problem.
431 */
atoiNoCap(const char * s)432 static int atoiNoCap (const char *s)
433 {
434 INT64 l;
435 union {
436 unsigned int u;
437 signed int i;
438 } retval;
439
440 l = atoi64(s);
441 /* Now smash to signed 32 bits accordingly. */
442 if (l < 0) {
443 retval.i = (int)l;
444 } else {
445 retval.u = (unsigned int)l;
446 }
447 return retval.i; /* <- union hackage. I feel dirty with this. -PH */
448 }
449
450
451
452 /*
453 =============
454 HashString
455 =============
456 */
457 /* Default hash function of Kazlib 1.19, slightly modified. */
HashString(const char * key)458 static unsigned int HashString (const char *key)
459 {
460 static unsigned long randbox[] = {
461 0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U,
462 0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU,
463 0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU,
464 0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU,
465 };
466
467 const char *str = key;
468 unsigned int acc = 0;
469
470 while (*str) {
471 acc ^= randbox[(*str + acc) & 0xf];
472 acc = (acc << 1) | (acc >> 31);
473 acc &= 0xffffffffU;
474 acc ^= randbox[((*str++ >> 4) + acc) & 0xf];
475 acc = (acc << 2) | (acc >> 30);
476 acc &= 0xffffffffU;
477 }
478 return abs(acc);
479 }
480
481
482 /*
483 ============
484 CodeError
485 ============
486 */
CodeError(char * fmt,...)487 static void CodeError( char *fmt, ... ) {
488 va_list argptr;
489
490 errorCount++;
491
492 report( "%s:%i ", currentFileName, currentFileLine );
493
494 va_start( argptr,fmt );
495 vprintf( fmt,argptr );
496 va_end( argptr );
497 }
498
499 /*
500 ============
501 EmitByte
502 ============
503 */
EmitByte(segment_t * seg,int v)504 static void EmitByte( segment_t *seg, int v ) {
505 if ( seg->imageUsed >= MAX_IMAGE ) {
506 Error( "MAX_IMAGE" );
507 }
508 seg->image[ seg->imageUsed ] = v;
509 seg->imageUsed++;
510 }
511
512 /*
513 ============
514 EmitInt
515 ============
516 */
EmitInt(segment_t * seg,int v)517 static void EmitInt( segment_t *seg, int v ) {
518 if ( seg->imageUsed >= MAX_IMAGE - 4) {
519 Error( "MAX_IMAGE" );
520 }
521 seg->image[ seg->imageUsed ] = v & 255;
522 seg->image[ seg->imageUsed + 1 ] = ( v >> 8 ) & 255;
523 seg->image[ seg->imageUsed + 2 ] = ( v >> 16 ) & 255;
524 seg->image[ seg->imageUsed + 3 ] = ( v >> 24 ) & 255;
525 seg->imageUsed += 4;
526 }
527
528 /*
529 ============
530 DefineSymbol
531
532 Symbols can only be defined on pass 0
533 ============
534 */
DefineSymbol(char * sym,int value)535 static void DefineSymbol( char *sym, int value ) {
536 /* Hand optimization by PhaethonH */
537 symbol_t *s;
538 char expanded[MAX_LINE_LENGTH];
539 int hash;
540
541 if ( passNumber == 1 ) {
542 return;
543 }
544
545 // add the file prefix to local symbols to guarantee unique
546 if ( sym[0] == '$' ) {
547 sprintf( expanded, "%s_%i", sym, currentFileIndex );
548 sym = expanded;
549 }
550
551 hash = HashString( sym );
552
553 if (hashtable_symbol_exists(symtable, hash, sym)) {
554 CodeError( "Multiple definitions for %s\n", sym );
555 return;
556 }
557
558 s = malloc( sizeof( *s ) );
559 s->next = NULL;
560 s->name = copystring( sym );
561 s->hash = hash;
562 s->value = value;
563 s->segment = currentSegment;
564
565 hashtable_add(symtable, hash, s);
566
567 /*
568 Hash table lookup already speeds up symbol lookup enormously.
569 We postpone sorting until end of pass 0.
570 Since we're not doing the insertion sort, lastSymbol should always
571 wind up pointing to the end of list.
572 This allows constant time for adding to the list.
573 -PH
574 */
575 if (symbols == 0) {
576 lastSymbol = symbols = s;
577 } else {
578 lastSymbol->next = s;
579 lastSymbol = s;
580 }
581 }
582
583
584 /*
585 ============
586 LookupSymbol
587
588 Symbols can only be evaluated on pass 1
589 ============
590 */
LookupSymbol(char * sym)591 static int LookupSymbol( char *sym ) {
592 symbol_t *s;
593 char expanded[MAX_LINE_LENGTH];
594 int hash;
595 hashchain_t *hc;
596
597 if ( passNumber == 0 ) {
598 return 0;
599 }
600
601 // add the file prefix to local symbols to guarantee unique
602 if ( sym[0] == '$' ) {
603 sprintf( expanded, "%s_%i", sym, currentFileIndex );
604 sym = expanded;
605 }
606
607 hash = HashString( sym );
608
609 /*
610 Hand optimization by PhaethonH
611
612 Using a hash table with chain/bucket for lookups alone sped up q3asm by almost 3x for me.
613 -PH
614 */
615 for (hc = hashtable_get(symtable, hash); hc; hc = hc->next) {
616 s = (symbol_t*)hc->data; /* ugly typecasting, but it's fast! */
617 if ( (hash == s->hash) && !strcmp(sym, s->name) ) {
618 return s->segment->segmentBase + s->value;
619 }
620 }
621
622 CodeError( "error: symbol %s undefined\n", sym );
623 passNumber = 0;
624 DefineSymbol( sym, 0 ); // so more errors aren't printed
625 passNumber = 1;
626 return 0;
627 }
628
629
630 /*
631 ==============
632 ExtractLine
633
634 Extracts the next line from the given text block.
635 If a full line isn't parsed, returns NULL
636 Otherwise returns the updated parse pointer
637 ===============
638 */
ExtractLine(char * data)639 static char *ExtractLine( char *data ) {
640 /* Goal:
641 Given a string `data', extract one text line into buffer `lineBuffer' that
642 is no longer than MAX_LINE_LENGTH characters long. Return value is
643 remainder of `data' that isn't part of `lineBuffer'.
644 -PH
645 */
646 /* Hand-optimized by PhaethonH */
647 char *p, *q;
648
649 currentFileLine++;
650
651 lineParseOffset = 0;
652 token[0] = 0;
653 *lineBuffer = 0;
654
655 p = q = data;
656 if (!*q) {
657 return NULL;
658 }
659
660 for ( ; !((*p == 0) || (*p == '\n')); p++) /* nop */ ;
661
662 if ((p - q) >= MAX_LINE_LENGTH) {
663 CodeError( "MAX_LINE_LENGTH" );
664 return data;
665 }
666
667 memcpy( lineBuffer, data, (p - data) );
668 lineBuffer[(p - data)] = 0;
669 p += (*p == '\n') ? 1 : 0; /* Skip over final newline. */
670 return p;
671 }
672
673
674 /*
675 ==============
676 Parse
677
678 Parse a token out of linebuffer
679 ==============
680 */
Parse(void)681 static qboolean Parse( void ) {
682 /* Hand-optimized by PhaethonH */
683 const char *p, *q;
684
685 /* Because lineParseOffset is only updated just before exit, this makes this code version somewhat harder to debug under a symbolic debugger. */
686
687 *token = 0; /* Clear token. */
688
689 // skip whitespace
690 for (p = lineBuffer + lineParseOffset; *p && (*p <= ' '); p++) /* nop */ ;
691
692 // skip ; comments
693 /* die on end-of-string */
694 if ((*p == ';') || (*p == 0)) {
695 lineParseOffset = p - lineBuffer;
696 return qfalse;
697 }
698
699 q = p; /* Mark the start of token. */
700 /* Find separator first. */
701 for ( ; *p > 32; p++) /* nop */ ; /* XXX: unsafe assumptions. */
702 /* *p now sits on separator. Mangle other values accordingly. */
703 strncpy(token, q, p - q);
704 token[p - q] = 0;
705
706 lineParseOffset = p - lineBuffer;
707
708 return qtrue;
709 }
710
711
712 /*
713 ==============
714 ParseValue
715 ==============
716 */
ParseValue(void)717 static int ParseValue( void ) {
718 Parse();
719 return atoiNoCap( token );
720 }
721
722
723 /*
724 ==============
725 ParseExpression
726 ==============
727 */
ParseExpression(void)728 static int ParseExpression(void) {
729 /* Hand optimization, PhaethonH */
730 int i, j;
731 char sym[MAX_LINE_LENGTH];
732 int v;
733
734 /* Skip over a leading minus. */
735 for ( i = ((token[0] == '-') ? 1 : 0) ; i < MAX_LINE_LENGTH ; i++ ) {
736 if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) {
737 break;
738 }
739 }
740
741 memcpy( sym, token, i );
742 sym[i] = 0;
743
744 switch (*sym) { /* Resolve depending on first character. */
745 /* Optimizing compilers can convert cases into "calculated jumps". I think these are faster. -PH */
746 case '-':
747 case '0': case '1': case '2': case '3': case '4':
748 case '5': case '6': case '7': case '8': case '9':
749 v = atoiNoCap(sym);
750 break;
751 default:
752 v = LookupSymbol(sym);
753 break;
754 }
755
756 // parse add / subtract offsets
757 while ( token[i] != 0 ) {
758 for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) {
759 if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) {
760 break;
761 }
762 }
763
764 memcpy( sym, token+i+1, j-i-1 );
765 sym[j-i-1] = 0;
766
767 switch (token[i]) {
768 case '+':
769 v += atoiNoCap(sym);
770 break;
771 case '-':
772 v -= atoiNoCap(sym);
773 break;
774 }
775
776 i = j;
777 }
778
779 return v;
780 }
781
782
783 /*
784 ==============
785 HackToSegment
786
787 BIG HACK: I want to put all 32 bit values in the data
788 segment so they can be byte swapped, and all char data in the lit
789 segment, but switch jump tables are emited in the lit segment and
790 initialized strng variables are put in the data segment.
791
792 I can change segments here, but I also need to fixup the
793 label that was just defined
794
795 Note that the lit segment is read-write in the VM, so strings
796 aren't read only as in some architectures.
797 ==============
798 */
HackToSegment(segmentName_t seg)799 static void HackToSegment( segmentName_t seg ) {
800 if ( currentSegment == &segment[seg] ) {
801 return;
802 }
803
804 currentSegment = &segment[seg];
805 if ( passNumber == 0 ) {
806 lastSymbol->segment = currentSegment;
807 lastSymbol->value = currentSegment->imageUsed;
808 }
809 }
810
811
812
813
814
815
816
817 //#define STAT(L) report("STAT " L "\n");
818 #define STAT(L)
819 #define ASM(O) int TryAssemble##O ()
820
821
822 /*
823 These clauses were moved out from AssembleLine() to allow reordering of if's.
824 An optimizing compiler should reconstruct these back into inline code.
825 -PH
826 */
827
828 // call instructions reset currentArgOffset
ASM(CALL)829 ASM(CALL)
830 {
831 if ( !strncmp( token, "CALL", 4 ) ) {
832 STAT("CALL");
833 EmitByte( &segment[CODESEG], OP_CALL );
834 instructionCount++;
835 currentArgOffset = 0;
836 return 1;
837 }
838 return 0;
839 }
840
841 // arg is converted to a reversed store
ASM(ARG)842 ASM(ARG)
843 {
844 if ( !strncmp( token, "ARG", 3 ) ) {
845 STAT("ARG");
846 EmitByte( &segment[CODESEG], OP_ARG );
847 instructionCount++;
848 if ( 8 + currentArgOffset >= 256 ) {
849 CodeError( "currentArgOffset >= 256" );
850 return 1;
851 }
852 EmitByte( &segment[CODESEG], 8 + currentArgOffset );
853 currentArgOffset += 4;
854 return 1;
855 }
856 return 0;
857 }
858
859 // ret just leaves something on the op stack
ASM(RET)860 ASM(RET)
861 {
862 if ( !strncmp( token, "RET", 3 ) ) {
863 STAT("RET");
864 EmitByte( &segment[CODESEG], OP_LEAVE );
865 instructionCount++;
866 EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
867 return 1;
868 }
869 return 0;
870 }
871
872 // pop is needed to discard the return value of
873 // a function
ASM(POP)874 ASM(POP)
875 {
876 if ( !strncmp( token, "pop", 3 ) ) {
877 STAT("POP");
878 EmitByte( &segment[CODESEG], OP_POP );
879 instructionCount++;
880 return 1;
881 }
882 return 0;
883 }
884
885 // address of a parameter is converted to OP_LOCAL
ASM(ADDRF)886 ASM(ADDRF)
887 {
888 int v;
889 if ( !strncmp( token, "ADDRF", 5 ) ) {
890 STAT("ADDRF");
891 instructionCount++;
892 Parse();
893 v = ParseExpression();
894 v = 16 + currentArgs + currentLocals + v;
895 EmitByte( &segment[CODESEG], OP_LOCAL );
896 EmitInt( &segment[CODESEG], v );
897 return 1;
898 }
899 return 0;
900 }
901
902 // address of a local is converted to OP_LOCAL
ASM(ADDRL)903 ASM(ADDRL)
904 {
905 int v;
906 if ( !strncmp( token, "ADDRL", 5 ) ) {
907 STAT("ADDRL");
908 instructionCount++;
909 Parse();
910 v = ParseExpression();
911 v = 8 + currentArgs + v;
912 EmitByte( &segment[CODESEG], OP_LOCAL );
913 EmitInt( &segment[CODESEG], v );
914 return 1;
915 }
916 return 0;
917 }
918
ASM(PROC)919 ASM(PROC)
920 {
921 char name[1024];
922 if ( !strcmp( token, "proc" ) ) {
923 STAT("PROC");
924 Parse(); // function name
925 strcpy( name, token );
926
927 DefineSymbol( token, instructionCount ); // segment[CODESEG].imageUsed );
928
929 currentLocals = ParseValue(); // locals
930 currentLocals = ( currentLocals + 3 ) & ~3;
931 currentArgs = ParseValue(); // arg marshalling
932 currentArgs = ( currentArgs + 3 ) & ~3;
933
934 if ( 8 + currentLocals + currentArgs >= 32767 ) {
935 CodeError( "Locals > 32k in %s\n", name );
936 }
937
938 instructionCount++;
939 EmitByte( &segment[CODESEG], OP_ENTER );
940 EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
941 return 1;
942 }
943 return 0;
944 }
945
946
ASM(ENDPROC)947 ASM(ENDPROC)
948 {
949 int v, v2;
950 if ( !strcmp( token, "endproc" ) ) {
951 STAT("ENDPROC");
952 Parse(); // skip the function name
953 v = ParseValue(); // locals
954 v2 = ParseValue(); // arg marshalling
955
956 // all functions must leave something on the opstack
957 instructionCount++;
958 EmitByte( &segment[CODESEG], OP_PUSH );
959
960 instructionCount++;
961 EmitByte( &segment[CODESEG], OP_LEAVE );
962 EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
963
964 return 1;
965 }
966 return 0;
967 }
968
969
ASM(ADDRESS)970 ASM(ADDRESS)
971 {
972 int v;
973 if ( !strcmp( token, "address" ) ) {
974 STAT("ADDRESS");
975 Parse();
976 v = ParseExpression();
977
978 /* Addresses are 32 bits wide, and therefore go into data segment. */
979 HackToSegment( DATASEG );
980 EmitInt( currentSegment, v );
981 if( passNumber == 1 && token[ 0 ] == '$' ) // crude test for labels
982 EmitInt( &segment[ JTRGSEG ], v );
983 return 1;
984 }
985 return 0;
986 }
987
ASM(EXPORT)988 ASM(EXPORT)
989 {
990 if ( !strcmp( token, "export" ) ) {
991 STAT("EXPORT");
992 return 1;
993 }
994 return 0;
995 }
996
ASM(IMPORT)997 ASM(IMPORT)
998 {
999 if ( !strcmp( token, "import" ) ) {
1000 STAT("IMPORT");
1001 return 1;
1002 }
1003 return 0;
1004 }
1005
ASM(CODE)1006 ASM(CODE)
1007 {
1008 if ( !strcmp( token, "code" ) ) {
1009 STAT("CODE");
1010 currentSegment = &segment[CODESEG];
1011 return 1;
1012 }
1013 return 0;
1014 }
1015
ASM(BSS)1016 ASM(BSS)
1017 {
1018 if ( !strcmp( token, "bss" ) ) {
1019 STAT("BSS");
1020 currentSegment = &segment[BSSSEG];
1021 return 1;
1022 }
1023 return 0;
1024 }
1025
ASM(DATA)1026 ASM(DATA)
1027 {
1028 if ( !strcmp( token, "data" ) ) {
1029 STAT("DATA");
1030 currentSegment = &segment[DATASEG];
1031 return 1;
1032 }
1033 return 0;
1034 }
1035
ASM(LIT)1036 ASM(LIT)
1037 {
1038 if ( !strcmp( token, "lit" ) ) {
1039 STAT("LIT");
1040 currentSegment = &segment[LITSEG];
1041 return 1;
1042 }
1043 return 0;
1044 }
1045
ASM(LINE)1046 ASM(LINE)
1047 {
1048 if ( !strcmp( token, "line" ) ) {
1049 STAT("LINE");
1050 return 1;
1051 }
1052 return 0;
1053 }
1054
ASM(FILE)1055 ASM(FILE)
1056 {
1057 if ( !strcmp( token, "file" ) ) {
1058 STAT("FILE");
1059 return 1;
1060 }
1061 return 0;
1062 }
1063
ASM(EQU)1064 ASM(EQU)
1065 {
1066 char name[1024];
1067 if ( !strcmp( token, "equ" ) ) {
1068 STAT("EQU");
1069 Parse();
1070 strcpy( name, token );
1071 Parse();
1072 DefineSymbol( name, atoiNoCap(token) );
1073 return 1;
1074 }
1075 return 0;
1076 }
1077
ASM(ALIGN)1078 ASM(ALIGN)
1079 {
1080 int v;
1081 if ( !strcmp( token, "align" ) ) {
1082 STAT("ALIGN");
1083 v = ParseValue();
1084 currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
1085 return 1;
1086 }
1087 return 0;
1088 }
1089
ASM(SKIP)1090 ASM(SKIP)
1091 {
1092 int v;
1093 if ( !strcmp( token, "skip" ) ) {
1094 STAT("SKIP");
1095 v = ParseValue();
1096 currentSegment->imageUsed += v;
1097 return 1;
1098 }
1099 return 0;
1100 }
1101
ASM(BYTE)1102 ASM(BYTE)
1103 {
1104 int i, v, v2;
1105 if ( !strcmp( token, "byte" ) ) {
1106 STAT("BYTE");
1107 v = ParseValue();
1108 v2 = ParseValue();
1109
1110 if ( v == 1 ) {
1111 /* Character (1-byte) values go into lit(eral) segment. */
1112 HackToSegment( LITSEG );
1113 } else if ( v == 4 ) {
1114 /* 32-bit (4-byte) values go into data segment. */
1115 HackToSegment( DATASEG );
1116 } else if ( v == 2 ) {
1117 /* and 16-bit (2-byte) values will cause q3asm to barf. */
1118 CodeError( "16 bit initialized data not supported" );
1119 }
1120
1121 // emit little endien
1122 for ( i = 0 ; i < v ; i++ ) {
1123 EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing -PH */
1124 v2 >>= 8;
1125 }
1126 return 1;
1127 }
1128 return 0;
1129 }
1130
1131 // code labels are emited as instruction counts, not byte offsets,
1132 // because the physical size of the code will change with
1133 // different run time compilers and we want to minimize the
1134 // size of the required translation table
ASM(LABEL)1135 ASM(LABEL)
1136 {
1137 if ( !strncmp( token, "LABEL", 5 ) ) {
1138 STAT("LABEL");
1139 Parse();
1140 if ( currentSegment == &segment[CODESEG] ) {
1141 DefineSymbol( token, instructionCount );
1142 } else {
1143 DefineSymbol( token, currentSegment->imageUsed );
1144 }
1145 return 1;
1146 }
1147 return 0;
1148 }
1149
1150
1151
1152 /*
1153 ==============
1154 AssembleLine
1155
1156 ==============
1157 */
AssembleLine(void)1158 static void AssembleLine( void ) {
1159 hashchain_t *hc;
1160 sourceOps_t *op;
1161 int i;
1162 int hash;
1163
1164 Parse();
1165 if ( !token[0] ) {
1166 return;
1167 }
1168
1169 hash = HashString( token );
1170
1171 /*
1172 Opcode search using hash table.
1173 Since the opcodes stays mostly fixed, this may benefit even more from a tree.
1174 Always with the tree :)
1175 -PH
1176 */
1177 for (hc = hashtable_get(optable, hash); hc; hc = hc->next) {
1178 op = (sourceOps_t*)(hc->data);
1179 i = op - sourceOps;
1180 if ((hash == opcodesHash[i]) && (!strcmp(token, op->name))) {
1181 int opcode;
1182 int expression;
1183
1184 if ( op->opcode == OP_UNDEF ) {
1185 CodeError( "Undefined opcode: %s\n", token );
1186 }
1187 if ( op->opcode == OP_IGNORE ) {
1188 return; // we ignore most conversions
1189 }
1190
1191 // sign extensions need to check next parm
1192 opcode = op->opcode;
1193 if ( opcode == OP_SEX8 ) {
1194 Parse();
1195 if ( token[0] == '1' ) {
1196 opcode = OP_SEX8;
1197 } else if ( token[0] == '2' ) {
1198 opcode = OP_SEX16;
1199 } else {
1200 CodeError( "Bad sign extension: %s\n", token );
1201 return;
1202 }
1203 }
1204
1205 // check for expression
1206 Parse();
1207 if ( token[0] && op->opcode != OP_CVIF
1208 && op->opcode != OP_CVFI ) {
1209 expression = ParseExpression();
1210
1211 // code like this can generate non-dword block copies:
1212 // auto char buf[2] = " ";
1213 // we are just going to round up. This might conceivably
1214 // be incorrect if other initialized chars follow.
1215 if ( opcode == OP_BLOCK_COPY ) {
1216 expression = ( expression + 3 ) & ~3;
1217 }
1218
1219 EmitByte( &segment[CODESEG], opcode );
1220 EmitInt( &segment[CODESEG], expression );
1221 } else {
1222 EmitByte( &segment[CODESEG], opcode );
1223 }
1224
1225 instructionCount++;
1226 return;
1227 }
1228 }
1229
1230 /* This falls through if an assembly opcode is not found. -PH */
1231
1232 /* The following should be sorted in sequence of statistical frequency, most frequent first. -PH */
1233 /*
1234 Empirical frequency statistics from FI 2001.01.23:
1235 109892 STAT ADDRL
1236 72188 STAT BYTE
1237 51150 STAT LINE
1238 50906 STAT ARG
1239 43704 STAT IMPORT
1240 34902 STAT LABEL
1241 32066 STAT ADDRF
1242 23704 STAT CALL
1243 7720 STAT POP
1244 7256 STAT RET
1245 5198 STAT ALIGN
1246 3292 STAT EXPORT
1247 2878 STAT PROC
1248 2878 STAT ENDPROC
1249 2812 STAT ADDRESS
1250 738 STAT SKIP
1251 374 STAT EQU
1252 280 STAT CODE
1253 176 STAT LIT
1254 102 STAT FILE
1255 100 STAT BSS
1256 68 STAT DATA
1257
1258 -PH
1259 */
1260
1261 #undef ASM
1262 #define ASM(O) if (TryAssemble##O ()) return;
1263
1264 ASM(ADDRL)
1265 ASM(BYTE)
1266 ASM(LINE)
1267 ASM(ARG)
1268 ASM(IMPORT)
1269 ASM(LABEL)
1270 ASM(ADDRF)
1271 ASM(CALL)
1272 ASM(POP)
1273 ASM(RET)
1274 ASM(ALIGN)
1275 ASM(EXPORT)
1276 ASM(PROC)
1277 ASM(ENDPROC)
1278 ASM(ADDRESS)
1279 ASM(SKIP)
1280 ASM(EQU)
1281 ASM(CODE)
1282 ASM(LIT)
1283 ASM(FILE)
1284 ASM(BSS)
1285 ASM(DATA)
1286
1287 CodeError( "Unknown token: %s\n", token );
1288 }
1289
1290 /*
1291 ==============
1292 InitTables
1293 ==============
1294 */
InitTables(void)1295 void InitTables( void ) {
1296 int i;
1297
1298 symtable = hashtable_new(symtablelen);
1299 optable = hashtable_new(100); /* There's hardly 100 opcodes anyway. */
1300
1301 for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
1302 opcodesHash[i] = HashString( sourceOps[i].name );
1303 hashtable_add(optable, opcodesHash[i], sourceOps + i);
1304 }
1305 }
1306
1307
1308 /*
1309 ==============
1310 WriteMapFile
1311 ==============
1312 */
WriteMapFile(void)1313 static void WriteMapFile( void ) {
1314 FILE *f;
1315 symbol_t *s;
1316 char imageName[MAX_OS_PATH];
1317 int seg;
1318
1319 strcpy( imageName, outputFilename );
1320 StripExtension( imageName );
1321 strcat( imageName, ".map" );
1322
1323 report( "Writing %s...\n", imageName );
1324
1325 f = SafeOpenWrite( imageName );
1326 for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) {
1327 for ( s = symbols ; s ; s = s->next ) {
1328 if ( s->name[0] == '$' ) {
1329 continue; // skip locals
1330 }
1331 if ( &segment[seg] != s->segment ) {
1332 continue;
1333 }
1334 fprintf( f, "%i %8x %s\n", seg, s->value, s->name );
1335 }
1336 }
1337 fclose( f );
1338 }
1339
1340 /*
1341 ===============
1342 WriteVmFile
1343 ===============
1344 */
WriteVmFile(void)1345 static void WriteVmFile( void ) {
1346 char imageName[MAX_OS_PATH];
1347 vmHeader_t header;
1348 FILE *f;
1349 int headerSize;
1350
1351 report( "%i total errors\n", errorCount );
1352
1353 strcpy( imageName, outputFilename );
1354 StripExtension( imageName );
1355 strcat( imageName, ".qvm" );
1356
1357 remove( imageName );
1358
1359 report( "code segment: %7i\n", segment[CODESEG].imageUsed );
1360 report( "data segment: %7i\n", segment[DATASEG].imageUsed );
1361 report( "lit segment: %7i\n", segment[LITSEG].imageUsed );
1362 report( "bss segment: %7i\n", segment[BSSSEG].imageUsed );
1363 report( "instruction count: %i\n", instructionCount );
1364
1365 if ( errorCount != 0 ) {
1366 report( "Not writing a file due to errors\n" );
1367 return;
1368 }
1369
1370 if( !options.vanillaQ3Compatibility ) {
1371 header.vmMagic = VM_MAGIC_VER2;
1372 headerSize = sizeof( header );
1373 } else {
1374 header.vmMagic = VM_MAGIC;
1375
1376 // Don't write the VM_MAGIC_VER2 bits when maintaining 1.32b compatibility.
1377 // (I know this isn't strictly correct due to padding, but then platforms
1378 // that pad wouldn't be able to write a correct header anyway). Note: if
1379 // vmHeader_t changes, this needs to be adjusted too.
1380 headerSize = sizeof( header ) - sizeof( header.jtrgLength );
1381 }
1382
1383 header.instructionCount = instructionCount;
1384 header.codeOffset = headerSize;
1385 header.codeLength = segment[CODESEG].imageUsed;
1386 header.dataOffset = header.codeOffset + segment[CODESEG].imageUsed;
1387 header.dataLength = segment[DATASEG].imageUsed;
1388 header.litLength = segment[LITSEG].imageUsed;
1389 header.bssLength = segment[BSSSEG].imageUsed;
1390 header.jtrgLength = segment[JTRGSEG].imageUsed;
1391
1392 report( "Writing to %s\n", imageName );
1393
1394 #ifdef Q3_BIG_ENDIAN
1395 {
1396 int i;
1397
1398 // byte swap the header
1399 for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
1400 ((int *)&header)[i] = LittleLong( ((int *)&header)[i] );
1401 }
1402 }
1403 #endif
1404
1405 CreatePath( imageName );
1406 f = SafeOpenWrite( imageName );
1407 SafeWrite( f, &header, headerSize );
1408 SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed );
1409 SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed );
1410 SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed );
1411
1412 if( !options.vanillaQ3Compatibility ) {
1413 SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed );
1414 }
1415
1416 fclose( f );
1417 }
1418
1419 /*
1420 ===============
1421 Assemble
1422 ===============
1423 */
Assemble(void)1424 static void Assemble( void ) {
1425 int i;
1426 char filename[MAX_OS_PATH];
1427 char *ptr;
1428
1429 report( "outputFilename: %s\n", outputFilename );
1430
1431 for ( i = 0 ; i < numAsmFiles ; i++ ) {
1432 strcpy( filename, asmFileNames[ i ] );
1433 DefaultExtension( filename, ".asm" );
1434 LoadFile( filename, (void **)&asmFiles[i] );
1435 }
1436
1437 // assemble
1438 for ( passNumber = 0 ; passNumber < 2 ; passNumber++ ) {
1439 segment[LITSEG].segmentBase = segment[DATASEG].imageUsed;
1440 segment[BSSSEG].segmentBase = segment[LITSEG].segmentBase + segment[LITSEG].imageUsed;
1441 segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed;
1442 for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
1443 segment[i].imageUsed = 0;
1444 }
1445 segment[DATASEG].imageUsed = 4; // skip the 0 byte, so NULL pointers are fixed up properly
1446 instructionCount = 0;
1447
1448 for ( i = 0 ; i < numAsmFiles ; i++ ) {
1449 currentFileIndex = i;
1450 currentFileName = asmFileNames[ i ];
1451 currentFileLine = 0;
1452 report("pass %i: %s\n", passNumber, currentFileName );
1453 fflush( NULL );
1454 ptr = asmFiles[i];
1455 while ( ptr ) {
1456 ptr = ExtractLine( ptr );
1457 AssembleLine();
1458 }
1459 }
1460
1461 // align all segment
1462 for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
1463 segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3;
1464 }
1465 if (passNumber == 0) {
1466 sort_symbols();
1467 }
1468 }
1469
1470 // reserve the stack in bss
1471 DefineSymbol( "_stackStart", segment[BSSSEG].imageUsed );
1472 segment[BSSSEG].imageUsed += stackSize;
1473 DefineSymbol( "_stackEnd", segment[BSSSEG].imageUsed );
1474
1475 // write the image
1476 WriteVmFile();
1477
1478 // write the map file even if there were errors
1479 if( options.writeMapFile ) {
1480 WriteMapFile();
1481 }
1482 }
1483
1484
1485 /*
1486 =============
1487 ParseOptionFile
1488
1489 =============
1490 */
ParseOptionFile(const char * filename)1491 static void ParseOptionFile( const char *filename ) {
1492 char expanded[MAX_OS_PATH];
1493 char *text, *text_p;
1494
1495 strcpy( expanded, filename );
1496 DefaultExtension( expanded, ".q3asm" );
1497 LoadFile( expanded, (void **)&text );
1498 if ( !text ) {
1499 return;
1500 }
1501
1502 text_p = text;
1503
1504 while( ( text_p = COM_Parse( text_p ) ) != 0 ) {
1505 if ( !strcmp( com_token, "-o" ) ) {
1506 // allow output override in option file
1507 text_p = COM_Parse( text_p );
1508 if ( text_p ) {
1509 strcpy( outputFilename, com_token );
1510 }
1511 continue;
1512 }
1513
1514 asmFileNames[ numAsmFiles ] = copystring( com_token );
1515 numAsmFiles++;
1516 }
1517 }
1518
ShowHelp(char * argv0)1519 static void ShowHelp( char *argv0 ) {
1520 Error("Usage: %s [OPTION]... [FILES]...\n\
1521 Assemble LCC bytecode assembly to Q3VM bytecode.\n\
1522 \n\
1523 -o OUTPUT Write assembled output to file OUTPUT.qvm\n\
1524 -f LISTFILE Read options and list of files to assemble from LISTFILE.q3asm\n\
1525 -b BUCKETS Set symbol hash table to BUCKETS buckets\n\
1526 -v Verbose compilation report\n\
1527 -vq3 Produce a qvm file compatible with Q3 1.32b\n\
1528 -h --help -? Show this help\n\
1529 ", argv0);
1530 }
1531
1532 /*
1533 ==============
1534 main
1535 ==============
1536 */
main(int argc,char ** argv)1537 int main( int argc, char **argv ) {
1538 int i;
1539 double start, end;
1540
1541 // _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
1542
1543 if ( argc < 2 ) {
1544 ShowHelp( argv[0] );
1545 }
1546
1547 start = I_FloatTime ();
1548
1549 // default filename is "q3asm"
1550 strcpy( outputFilename, "q3asm" );
1551 numAsmFiles = 0;
1552
1553 for ( i = 1 ; i < argc ; i++ ) {
1554 if ( argv[i][0] != '-' ) {
1555 break;
1556 }
1557 if( !strcmp( argv[ i ], "-h" ) ||
1558 !strcmp( argv[ i ], "--help" ) ||
1559 !strcmp( argv[ i ], "-?") ) {
1560 ShowHelp( argv[0] );
1561 }
1562
1563 if ( !strcmp( argv[i], "-o" ) ) {
1564 if ( i == argc - 1 ) {
1565 Error( "-o must preceed a filename" );
1566 }
1567 /* Timbo of Tremulous pointed out -o not working; stock ID q3asm folded in the change. Yay. */
1568 strcpy( outputFilename, argv[ i+1 ] );
1569 i++;
1570 continue;
1571 }
1572
1573 if ( !strcmp( argv[i], "-f" ) ) {
1574 if ( i == argc - 1 ) {
1575 Error( "-f must preceed a filename" );
1576 }
1577 ParseOptionFile( argv[ i+1 ] );
1578 i++;
1579 continue;
1580 }
1581
1582 if (!strcmp(argv[i], "-b")) {
1583 if (i == argc - 1) {
1584 Error("-b requires an argument");
1585 }
1586 i++;
1587 symtablelen = atoiNoCap(argv[i]);
1588 continue;
1589 }
1590
1591 if( !strcmp( argv[ i ], "-v" ) ) {
1592 /* Verbosity option added by Timbo, 2002.09.14.
1593 By default (no -v option), q3asm remains silent except for critical errors.
1594 Verbosity turns on all messages, error or not.
1595 Motivation: not wanting to scrollback for pages to find asm error.
1596 */
1597 options.verbose = qtrue;
1598 continue;
1599 }
1600
1601 if( !strcmp( argv[ i ], "-m" ) ) {
1602 options.writeMapFile = qtrue;
1603 continue;
1604 }
1605
1606 if( !strcmp( argv[ i ], "-vq3" ) ) {
1607 options.vanillaQ3Compatibility = qtrue;
1608 continue;
1609 }
1610
1611 Error( "Unknown option: %s", argv[i] );
1612 }
1613
1614 // the rest of the command line args are asm files
1615 for ( ; i < argc ; i++ ) {
1616 asmFileNames[ numAsmFiles ] = copystring( argv[ i ] );
1617 numAsmFiles++;
1618 }
1619 // In some case it Segfault without this check
1620 if ( numAsmFiles == 0 ) {
1621 Error( "No file to assemble\n" );
1622 }
1623
1624 InitTables();
1625 Assemble();
1626
1627 {
1628 symbol_t *s;
1629
1630 for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ;
1631
1632 if (options.verbose)
1633 {
1634 report("%d symbols defined\n", i);
1635 hashtable_stats(symtable);
1636 hashtable_stats(optable);
1637 }
1638 }
1639
1640 end = I_FloatTime ();
1641 report ("%5.0f seconds elapsed\n", end-start);
1642
1643 return errorCount;
1644 }
1645
1646