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