ELF support for RMAC.
[rmac] / rmac.c
1 //
2 // RMAC - Reboot's Macro Assembler for the Atari Jaguar Console System
3 // RMAC.C - Main Application Code
4 // Copyright (C) 199x Landon Dyer, 2011 - 2016 Reboot and Friends
5 // RMAC derived from MADMAC v1.07 Written by Landon Dyer, 1986
6 // Source utilised with the kind permission of Landon Dyer
7 //
8
9 #include "rmac.h"
10 #include "error.h"
11 #include "listing.h"
12 #include "procln.h"
13 #include "token.h"
14 #include "expr.h"
15 #include "sect.h"
16 #include "mark.h"
17 #include "macro.h"
18 #include "riscasm.h"
19 #include "direct.h"
20 #include "version.h"
21 #include "debug.h"
22 #include "symbol.h"
23 #include "object.h"
24
25 int perm_verb_flag;                             // Permanently verbose, interactive mode
26 int list_flag;                                  // "-l" listing flag on command line
27 int verb_flag;                                  // Be verbose about what's going on
28 int as68_flag;                                  // as68 kludge mode
29 int glob_flag;                                  // Assume undefined symbols are global
30 int lsym_flag;                                  // Include local symbols in object file
31 int sbra_flag;                                  // Warn about possible short branches
32 int prg_flag;                                   // !=0, produce .PRG executable (2=symbols)
33 int legacy_flag;                                // Do stuff like insert code in RISC assembler
34 int obj_format;                                 // Object format flag
35 int debug;                                              // [1..9] Enable debugging levels
36 int err_flag;                                   // '-e' specified
37 int err_fd;                                             // File to write error messages to
38 int rgpu, rdsp;                                 // Assembling Jaguar GPU or DSP code
39 int list_fd;                                    // File to write listing to
40 int regbank;                                    // RISC register bank
41 int segpadsize;                                 // Segment padding size
42 int endian;                                             // Host processor endianess (0 = LE, 1 = BE)
43 char * objfname;                                // Object filename pointer
44 char * firstfname;                              // First source filename
45 char * cmdlnexec;                               // Executable name, pointer to ARGV[0]
46 char * searchpath;                              // Search path for include files
47 char defname[] = "noname.o";    // Default output filename
48 int optim_flags[OPT_COUNT];     // Specific optimisations on/off matrix
49
50 //
51 // Manipulate file extension.
52 //
53 // 'name' must be large enough to hold any possible filename. If 'stripp' is
54 // nonzero, any old extension is removed. If the file does not already have an
55 // extension, 'extension' is appended to the filename.
56 //
57 char * fext(char * name, char * extension, int stripp)
58 {
59         char * s;
60
61         // Find beginning of "real" name (strip off path)
62         char * beg = strrchr(name, SLASHCHAR);
63
64         if (beg == NULL)
65                 beg = name;
66
67         // Clobber any old extension, if requested
68         if (stripp)
69         {
70                 for(s=beg; *s && *s!='.'; ++s)
71                         ;
72
73                 *s = '\0';
74         }
75
76         if (strrchr(beg, '.') == NULL)
77                 strcat(beg, extension);
78
79         return name;
80 }
81
82
83 //
84 // Return 'item'nth element of semicolon-seperated pathnames specified in the
85 // enviroment string 's'. Copy the pathname to 'buf'.  Return 0 if the 'item'
86 // nth path doesn't exist.
87 //
88 // ['item' ranges from 0 to N-1, where N = #elements in search path]
89 //
90 int nthpath(char * env_var, int itemno, char * buf)
91 {
92         char * s = searchpath;
93
94         if (s == NULL)
95                 s = getenv(env_var);
96
97         if (s == NULL)
98                 return 0;
99
100         while (itemno--)
101                 while (*s != EOS && *s++ != ';')
102                         ;
103
104         if (*s == EOS)
105                 return 0;
106
107         while (*s != EOS && *s != ';')
108                 *buf++ = *s++;
109
110         *buf++ = EOS;
111
112         return 1;
113 }
114
115
116 //
117 // Display command line help
118 //
119 void DisplayHelp(void)
120 {
121         printf("Usage:\n"
122                 "    %s [options] srcfile\n"
123                 "\n"
124                 "Options:\n"
125                 "  -? or -h          Display usage information\n"
126                 "  -dsymbol[=value]  Define symbol\n"
127                 "  -e[errorfile]     Send error messages to file, not stdout\n"
128                 "  -f[format]        Output object file format\n"
129                 "                    a: ALCYON (use this for ST)\n"
130                 "                    b: BSD (use this for Jaguar)\n"
131                 "                    e: ELF\n"
132                 "  -i[path]          Directory to search for include files\n"
133                 "  -l[filename]      Create an output listing file\n"
134                 "  -n                Don't do things behind your back in RISC assembler\n"
135                 "  -o file           Output file name\n"
136                 "  +o[value]         Turn a specific optimisation on\n"
137                 "  ~o[value]         Turn a specific optimisation off\n"
138                 "  +oall             Turn all optimisations on\n"
139                 "  ~oall             Turn all optimisations off\n"
140                 "  -p                Create an ST .prg (without symbols)\n"
141                 "  -ps               Create an ST .prg (with symbols)\n"
142                 "                    Forces -fa\n"
143                 "  -r[size]          Pad segments to boundary size specified\n"
144                 "                    w: word (2 bytes, default alignment)\n"
145                 "                    l: long (4 bytes)\n"
146                 "                    p: phrase (8 bytes)\n"
147                 "                    d: double phrase (16 bytes)\n"
148                 "                    q: quad phrase (32 bytes)\n"
149                 "  -s                Warn about possible short branches\n"
150                 "                    and applied optimisations\n"
151                 "  -u                Force referenced and undefined symbols global\n"
152                 "  -v                Set verbose mode\n"
153                 "  -x                Turn on debugging mode\n"
154                 "  -y[pagelen]       Set page line length (default: 61)\n"
155                 "\n", cmdlnexec);
156 }
157
158
159 //
160 // Display version information
161 //
162 void DisplayVersion(void)
163 {
164         printf("\n"
165                 " _ __ _ __ ___   __ _  ___ \n"
166                 "| '__| '_ ` _ \\ / _` |/ __|\n"
167                 "| |  | | | | | | (_| | (__ \n"
168                 "|_|  |_| |_| |_|\\__,_|\\___|\n"
169                 "\nReboot's Macro Assembler\n"
170                 "Copyright (C) 199x Landon Dyer, 2011-2017 Reboot\n"
171                 "V%01i.%01i.%01i %s (%s)\n\n", MAJOR, MINOR, PATCH, __DATE__, PLATFORM);
172 }
173
174
175 //
176 // Parse optimisation options
177 //
178 int ParseOptimization(char * optstring)
179 {
180         int onoff = 0;
181
182         if (*optstring == '+')
183                 onoff = 1;
184         else if (*optstring != '~')
185                 return ERROR;
186
187         if ((optstring[2] == 'a' || optstring[2] == 'A')
188                 && (optstring[3] == 'l' || optstring[3] == 'L')
189                 && (optstring[4] == 'l' || optstring[4] == 'L'))
190         {
191                 memset(optim_flags, onoff, OPT_COUNT * sizeof(int));
192                 return OK;
193         }
194         else if (optstring[1] == 'o' || optstring[1] == 'O') // Turn on specific optimisation
195         {
196                 int opt_no = atoi(&optstring[2]);
197
198                 if ((opt_no >= 0) && (opt_no < OPT_COUNT))
199                 {
200                         optim_flags[opt_no] = onoff;
201                         return OK;
202                 }
203                 else
204                         return ERROR;
205         }
206         else
207         {
208                 return ERROR;
209         }
210 }
211
212
213 //
214 // Process command line arguments and do an assembly
215 //
216 int Process(int argc, char ** argv)
217 {
218         int argno;                                              // Argument number
219         SYM * sy;                                               // Pointer to a symbol record
220         char * s;                                               // String pointer
221         int fd;                                                 // File descriptor
222         char fnbuf[FNSIZ];                              // Filename buffer
223         int i;                                                  // Iterator
224
225         errcnt = 0;                                             // Initialise error count
226         listing = 0;                                    // Initialise listing level
227         list_flag = 0;                                  // Initialise listing flag
228         verb_flag = perm_verb_flag;             // Initialise verbose flag
229         as68_flag = 0;                                  // Initialise as68 kludge mode
230         glob_flag = 0;                                  // Initialise .globl flag
231         sbra_flag = 0;                                  // Initialise short branch flag
232         debug = 0;                                              // Initialise debug flag
233         searchpath = NULL;                              // Initialise search path
234         objfname = NULL;                                // Initialise object filename
235         list_fname = NULL;                              // Initialise listing filename
236         err_fname = NULL;                               // Initialise error filename
237         obj_format = BSD;                               // Initialise object format
238         firstfname = NULL;                              // Initialise first filename
239         err_fd = ERROUT;                                // Initialise error file descriptor
240         err_flag = 0;                                   // Initialise error flag
241         rgpu = 0;                                               // Initialise GPU assembly flag
242         rdsp = 0;                                               // Initialise DSP assembly flag
243         lsym_flag = 1;                                  // Include local symbols in object file
244         regbank = BANK_N;                               // No RISC register bank specified
245         orgactive = 0;                                  // Not in RISC org section
246         orgwarning = 0;                                 // No ORG warning issued
247         segpadsize = 2;                                 // Initialise segment padding size
248
249         // Initialise modules
250         InitSymbolTable();                              // Symbol table
251         InitTokenizer();                                // Tokenizer
252         InitLineProcessor();                    // Line processor
253         InitExpression();                               // Expression analyzer
254         InitSection();                                  // Section manager / code generator
255         InitMark();                                             // Mark tape-recorder
256         InitMacro();                                    // Macro processor
257         InitListing();                                  // Listing generator
258
259         // Process command line arguments and assemble source files
260         for(argno=0; argno<argc; ++argno)
261         {
262                 if (*argv[argno] == '-')
263                 {
264                         switch (argv[argno][1])
265                         {
266                         case 'd':                               // Define symbol
267                         case 'D':
268                                 for(s=argv[argno]+2; *s!=EOS;)
269                                 {
270                                         if (*s++ == '=')
271                                         {
272                                                 s[-1] = EOS;
273                                                 break;
274                                         }
275                                 }
276
277                                 if (argv[argno][2] == EOS)
278                                 {
279                                         printf("-d: empty symbol\n");
280                                         errcnt++;
281                                         return errcnt;
282                                 }
283
284                                 sy = lookup(argv[argno] + 2, 0, 0);
285
286                                 if (sy == NULL)
287                                 {
288                                         sy = NewSymbol(argv[argno] + 2, LABEL, 0);
289                                         sy->svalue = 0;
290                                 }
291
292                                 sy->sattr = DEFINED | EQUATED | ABS;
293                                 sy->svalue = (*s ? (VALUE)atoi(s) : 0);
294                                 break;
295                         case 'e':                               // Redirect error message output
296                         case 'E':
297                                 err_fname = argv[argno] + 2;
298                                 break;
299                         case 'f':                               // -f<format>
300                         case 'F':
301                                 switch (argv[argno][2])
302                                 {
303                                 case EOS:
304                                 case 'a':                       // -fa = Alcyon [the default]
305                                 case 'A':
306                                         obj_format = ALCYON;
307                                         break;
308                                 case 'b':                       // -fb = BSD (Jaguar Recommended: 3 out 4 jaguars recommend it!)
309                                 case 'B':
310                                         obj_format = BSD;
311                                         break;
312                                 case 'e':                       // -fe = ELF
313                                 case 'E':
314                                         obj_format = ELF;
315                                         break;
316                                 default:
317                                         printf("-f: unknown object format specified\n");
318                                         errcnt++;
319                                         return errcnt;
320                                 }
321                                 break;
322                         case 'g':                               // Debugging flag
323                         case 'G':
324                                 printf("Debugging flag (-g) not yet implemented\n");
325                                 break;
326                         case 'i':                               // Set directory search path
327                         case 'I':
328                                 searchpath = argv[argno] + 2;
329                                 break;
330                         case 'l':                               // Produce listing file
331                         case 'L':
332                                 list_fname = argv[argno] + 2;
333                                 listing = 1;
334                                 list_flag = 1;
335                                 lnsave++;
336                                 break;
337                         case 'o':                               // Direct object file output
338                         case 'O':
339                                 if (argv[argno][2] != EOS)
340                                         objfname = argv[argno] + 2;
341                                 else
342                                 {
343                                         if (++argno >= argc)
344                                         {
345                                                 printf("Missing argument to -o");
346                                                 errcnt++;
347                                                 return errcnt;
348                                         }
349
350                                         objfname = argv[argno];
351                                 }
352
353                                 break;
354                         case 'p':               /* -p: generate ".PRG" executable output */
355                         case 'P':
356                                 /*
357                                  * -p           .PRG generation w/o symbols
358                                  * -ps  .PRG generation with symbols
359                                  */
360                                 switch (argv[argno][2])
361                                 {
362                                         case EOS:
363                                                 prg_flag = 1;
364                                                 break;
365
366                                         case 's':
367                                         case 'S':
368                                                 prg_flag = 2;
369                                                 break;
370
371                                         default:
372                                                 printf("-p: syntax error\n");
373                                                 ++errcnt;
374                                                 return errcnt;
375                                 }
376
377                                 // Enforce Alcyon object format - kind of silly
378                                 // to ask for .prg output without it!
379                                 obj_format = ALCYON;
380                                 break;
381                         case 'r':                               // Pad seg to requested boundary size
382                         case 'R':
383                                 switch(argv[argno][2])
384                                 {
385                                 case 'w': case 'W': segpadsize = 2;  break;
386                                 case 'l': case 'L': segpadsize = 4;  break;
387                                 case 'p': case 'P': segpadsize = 8;  break;
388                                 case 'd': case 'D': segpadsize = 16; break;
389                                 case 'q': case 'Q': segpadsize = 32; break;
390                                 default: segpadsize = 2; break; // Effective autoeven();
391                                 }
392                                 break;
393                         case 's':                               // Warn about possible short branches
394                         case 'S':
395                                 sbra_flag = 1;
396                                 break;
397                         case 'u':                               // Make undefined symbols .globl
398                         case 'U':
399                                 glob_flag = 1;
400                                 break;
401                         case 'v':                               // Verbose flag
402                         case 'V':
403                                 verb_flag++;
404
405                                 if (verb_flag > 1)
406                                         DisplayVersion();
407
408                                 break;
409                         case 'x':                               // Turn on debugging
410                         case 'X':
411                                 debug = 1;
412                                 printf("~ Debugging ON\n");
413                                 break;
414                         case 'y':                               // -y<pagelen>
415                         case 'Y':
416                                 pagelen = atoi(argv[argno] + 2);
417
418                                 if (pagelen < 10)
419                                 {
420                                         printf("-y: bad page length\n");
421                                         ++errcnt;
422                                         return errcnt;
423                                 }
424
425                                 break;
426                         case EOS:                               // Input is stdin
427                                 if (firstfname == NULL) // Kludge first filename
428                                         firstfname = defname;
429
430                                 include(0, "(stdin)");
431                                 Assemble();
432                                 break;
433                         case 'h':                               // Display command line usage
434                         case 'H':
435                         case '?':
436                                 DisplayVersion();
437                                 DisplayHelp();
438                                 errcnt++;
439                                 break;
440                         case 'n':                               // Turn off legacy mode
441                         case 'N':
442                                 legacy_flag = 0;
443                                 printf("Legacy mode OFF\n");
444                                 break;
445                         default:
446                                 DisplayVersion();
447                                 printf("Unknown switch: %s\n\n", argv[argno]);
448                                 DisplayHelp();
449                                 errcnt++;
450                                 break;
451                         }
452                 }
453                 else if (*argv[argno] == '+' || *argv[argno] == '~')
454                 {
455                         if (ParseOptimization(argv[argno]) != OK)
456                         {
457                                 DisplayVersion();
458                                 printf("Unknown switch: %s\n\n", argv[argno]);
459                                 DisplayHelp();
460                                 errcnt++;
461                                 break;
462                         }
463                 }
464                 else
465                 {
466                         // Record first filename.
467                         if (firstfname == NULL)
468                                 firstfname = argv[argno];
469
470                         strcpy(fnbuf, argv[argno]);
471                         fext(fnbuf, ".s", 0);
472                         fd = open(fnbuf, 0);
473
474                         if (fd < 0)
475                         {
476                                 printf("Cannot open: %s\n", fnbuf);
477                                 errcnt++;
478                                 continue;
479                         }
480
481                         include(fd, fnbuf);
482                         Assemble();
483                 }
484         }
485
486         // Wind-up processing;
487         // o  save current section (no more code generation)
488         // o  do auto-even of all sections (or boundary alignment as requested
489         //    through '-r')
490         // o  determine name of object file:
491         //    -  "foo.o" for linkable output;
492         //    -  "foo.prg" for GEMDOS executable (-p flag).
493         SaveSection();
494
495         for(i=TEXT; i<=BSS; i<<=1)
496         {
497                 SwitchSection(i);
498
499                 switch(segpadsize)
500                 {
501                 case 2:  d_even();    break;
502                 case 4:  d_long();    break;
503                 case 8:  d_phrase();  break;
504                 case 16: d_dphrase(); break;
505                 case 32: d_qphrase(); break;
506                 }
507
508                 SaveSection();
509         }
510
511         if (objfname == NULL)
512         {
513                 if (firstfname == NULL)
514                         firstfname = defname;
515
516                 strcpy(fnbuf, firstfname);
517                 fext(fnbuf, (prg_flag ? ".prg" : ".o"), 1);
518                 objfname = fnbuf;
519         }
520
521         // With one pass finished, go back and:
522         // (1)   run through all the fixups and resolve forward references;
523         // (1.5) ensure that remaining fixups can be handled by the linker
524         //       (`lo68' format, extended (postfix) format....)
525         // (2)   generate the output file image and symbol table;
526         // (3)   generate relocation information from left-over fixups.
527         ResolveAllFixups();                                             // Do all fixups
528         StopMark();                                                             // Stop mark tape-recorder
529
530         if (errcnt == 0)
531         {
532                 if ((fd = open(objfname, _OPEN_FLAGS, _PERM_MODE)) < 0)
533                         cantcreat(objfname);
534
535                 if (verb_flag)
536                 {
537                         s = (prg_flag ? "executable" : "object");
538                         printf("[Writing %s file: %s]\n", s, objfname);
539                 }
540
541                 WriteObject(fd);
542                 close(fd);
543
544                 if (errcnt != 0)
545                         unlink(objfname);
546         }
547
548         if (list_flag)
549         {
550                 if (verb_flag)
551                         printf("[Wrapping-up listing file]\n");
552
553                 listing = 1;
554                 symtable();
555                 close(list_fd);
556         }
557
558         if (err_flag)
559                 close(err_fd);
560
561         DEBUG dump_everything();
562
563         return errcnt;
564 }
565
566
567 //
568 // Determine processor endianess
569 //
570 int GetEndianess(void)
571 {
572         int i = 1;
573         char * p = (char *)&i;
574
575         if (p[0] == 1)
576                 return 0;
577
578         return 1;
579 }
580
581
582 //
583 // Application entry point
584 //
585 int main(int argc, char ** argv)
586 {
587         perm_verb_flag = 0;                             // Clobber "permanent" verbose flag
588         legacy_flag = 1;                                // Default is legacy mode on (:-P)
589
590         // Set legacy optimisation flags to on
591         // and everything else to off
592         memset(optim_flags, 0, OPT_COUNT * sizeof(int));
593         optim_flags[OPT_ABS_SHORT] =
594                 optim_flags[OPT_MOVEL_MOVEQ] =
595                 optim_flags[OPT_BSR_BCC_S] = 1;
596
597         cmdlnexec = argv[0];                    // Obtain executable name
598
599         endian = GetEndianess();                // Get processor endianess
600
601         // If commands were passed in, process them
602         if (argc > 1)
603                 return Process(argc - 1, argv + 1);
604
605         DisplayVersion();
606         DisplayHelp();
607
608         return 0;
609 }
610