EOY minor update.
[rmac] / 6502.c
1 //
2 // RMAC - Reboot's Macro Assembler for all Atari computers
3 // 6502.C - 6502 Assembler
4 // Copyright (C) 199x Landon Dyer, 2011-2020 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 //    Init6502  initialization
9 //    d_6502    handle ".6502" directive
10 //    m6502cg   generate code for a 6502 mnemonic
11 //    d_org     handle 6502 section's ".org" directive
12 //    m6502obj  generate 6502 object file
13 //
14 #include "direct.h"
15 #include "expr.h"
16 #include "error.h"
17 #include "mach.h"
18 #include "procln.h"
19 #include "riscasm.h"
20 #include "rmac.h"
21 #include "sect.h"
22 #include "token.h"
23
24 #define DEF_KW
25 #include "kwtab.h"
26
27 #define UPSEG_SIZE      0x10010L // size of 6502 code buffer, 64K+16bytes
28
29 // Internal vars
30 static uint16_t orgmap[1024][2];                // Mark all 6502 org changes
31
32 // Exported vars
33 const char in_6502mode[] = "directive illegal in .6502 section";
34 uint16_t * currentorg = &orgmap[0][0];  // Current org range
35 char strtoa8[128];      // ASCII to Atari 800 internal conversion table
36
37 //
38 // 6502 addressing modes;
39 // DO NOT CHANGE THESE VALUES.
40 //
41 #define A65_ABS         0
42 #define A65_ABSX        1
43 #define A65_ABSY        2
44 #define A65_IMPL        3
45 #define A65_IMMED       4
46 #define A65_INDX        5
47 #define A65_INDY        6
48 #define A65_IND         7
49 #define A65_REL         8
50 #define A65_ZP          9
51 #define A65_ZPX         10
52 #define A65_ZPY         11
53 #define A65_IMMEDH  12
54 #define A65_IMMEDL  13
55
56 #define NMACHOPS        56              // Number of machine ops
57 #define NMODES          14              // Number of addressing modes
58 #define NOP                     0xEA    // 6502 NOP instruction
59 #define ILLEGAL         0xFF    // 'Illegal instr' marker
60 #define END65           0xFF    // End-of-an-instr-list
61
62 static char imodes[] =
63 {
64         A65_IMMED, 0x69, A65_ABS, 0x6D, A65_ZP, 0x65, A65_INDX, 0x61, A65_INDY, 0x71,
65         A65_ZPX, 0x75, A65_ABSX, 0x7D, A65_ABSY, 0x79, END65,
66         A65_IMMED, 0x29, A65_ABS, 0x2D, A65_ZP, 0x25, A65_INDX, 0x21, A65_INDY, 0x31,
67         A65_ZPX, 0x35, A65_ABSX, 0x3D, A65_ABSY, 0x39, END65,
68         A65_ABS, 0x0E, A65_ZP, 0x06, A65_IMPL, 0x0A, A65_ZPX, 0x16, A65_ABSX,
69         0x1E, END65,
70         A65_REL, 0x90, END65,
71         A65_REL, 0xB0, END65,
72         A65_REL, 0xF0, END65,
73         A65_REL, 0xD0, END65,
74         A65_REL, 0x30, END65,
75         A65_REL, 0x10, END65,
76         A65_REL, 0x50, END65,
77         A65_REL, 0x70, END65,
78         A65_ABS, 0x2C, A65_ZP, 0x24, END65,
79         A65_IMPL, 0x00, END65,
80         A65_IMPL, 0x18, END65,
81         A65_IMPL, 0xD8, END65,
82         A65_IMPL, 0x58, END65,
83         A65_IMPL, 0xB8, END65,
84         A65_IMMED, 0xC9, A65_ABS, 0xCD, A65_ZP, 0xC5, A65_INDX, 0xC1, A65_INDY, 0xD1,
85         A65_ZPX, 0xD5, A65_ABSX, 0xDD, A65_ABSY, 0xD9, END65,
86         A65_IMMED, 0xE0, A65_ABS, 0xEC, A65_ZP, 0xE4, END65,
87         A65_IMMED, 0xC0, A65_ABS, 0xCC, A65_ZP, 0xC4, END65,
88         A65_ABS, 0xCE, A65_ZP, 0xC6, A65_ZPX, 0xD6, A65_ABSX, 0xDE, END65,
89         A65_IMPL, 0xCA, END65,
90         A65_IMPL, 0x88, END65,
91         A65_IMMED, 0x49, A65_ABS, 0x4D, A65_ZP, 0x45, A65_INDX, 0x41, A65_INDY, 0x51,
92         A65_ZPX, 0x55, A65_ABSX, 0x5D, A65_ABSY, 0x59, END65,
93         A65_ABS, 0xEE, A65_ZP, 0xE6, A65_ZPX, 0xF6, A65_ABSX, 0xFE, END65,
94         A65_IMPL, 0xE8, END65,
95         A65_IMPL, 0xC8, END65,
96         A65_ABS, 0x4C, A65_IND, 0x6C, END65,
97         A65_ABS, 0x20, END65,
98         A65_IMMED, 0xA9, A65_ABS, 0xAD, A65_ZP, 0xA5, A65_INDX, 0xA1, A65_INDY, 0xB1,
99         A65_ZPX, 0xB5, A65_ABSX, 0xBD, A65_ABSY, 0xB9, A65_IMMEDH, 0xA9, A65_IMMEDL, 0xA9, END65,
100         A65_IMMED, 0xA2, A65_ABS, 0xAE, A65_ZP, 0xA6, A65_ABSY, 0xBE,
101         A65_ZPY, 0xB6, A65_IMMEDH, 0xA2, A65_IMMEDL, 0xA2, END65,
102         A65_IMMED, 0xA0, A65_ABS, 0xAC, A65_ZP, 0xA4, A65_ZPX, 0xB4,
103         A65_ABSX, 0xBC, A65_IMMEDH, 0xA0, A65_IMMEDL, 0xA0, END65,
104         A65_ABS, 0x4E, A65_ZP, 0x46, A65_IMPL, 0x4A, A65_ZPX, 0x56,
105         A65_ABSX, 0x5E, END65,
106         A65_IMPL, 0xEA, END65,
107         A65_IMMED, 0x09, A65_ABS, 0x0D, A65_ZP, 0x05, A65_INDX, 0x01, A65_INDY, 0x11,
108         A65_ZPX, 0x15, A65_ABSX, 0x1D, A65_ABSY, 0x19, END65,
109         A65_IMPL, 0x48, END65,
110         A65_IMPL, 0x08, END65,
111         A65_IMPL, 0x68, END65,
112         A65_IMPL, 0x28, END65,
113         A65_ABS, 0x2E, A65_ZP, 0x26, A65_IMPL, 0x2A, A65_ZPX, 0x36,
114         A65_ABSX, 0x3E, END65,
115         A65_ABS, 0x6E, A65_ZP, 0x66, A65_IMPL, 0x6A, A65_ZPX, 0x76,
116         A65_ABSX, 0x7E, END65,
117         A65_IMPL, 0x40, END65,
118         A65_IMPL, 0x60, END65,
119         A65_IMMED, 0xE9, A65_ABS, 0xED, A65_ZP, 0xE5, A65_INDX, 0xE1, A65_INDY, 0xF1,
120         A65_ZPX, 0xF5, A65_ABSX, 0xFD, A65_ABSY, 0xF9, END65,
121         A65_IMPL, 0x38, END65,
122         A65_IMPL, 0xF8, END65,
123         A65_IMPL, 0x78, END65,
124         A65_ABS, 0x8D, A65_ZP, 0x85, A65_INDX, 0x81, A65_INDY, 0x91, A65_ZPX, 0x95,
125         A65_ABSX, 0x9D, A65_ABSY, 0x99, END65,
126         A65_ABS, 0x8E, A65_ZP, 0x86, A65_ZPY, 0x96, END65,
127         A65_ABS, 0x8C, A65_ZP, 0x84, A65_ZPX, 0x94, END65,
128         A65_IMPL, 0xAA, END65,
129         A65_IMPL, 0xA8, END65,
130         A65_IMPL, 0xBA, END65,
131         A65_IMPL, 0x8A, END65,
132         A65_IMPL, 0x9A, END65,
133         A65_IMPL, 0x98, END65
134 };
135
136 static char ops[NMACHOPS][NMODES];                      // Opcodes
137 static unsigned char inf[NMACHOPS][NMODES];     // Construction info
138
139 // Absolute-to-zeropage translation table
140 static int abs2zp[] =
141 {
142         A65_ZP,         // ABS
143         A65_ZPX,        // ABSX
144         A65_ZPY,        // ABSY
145         -1,                     // IMPL
146         -1,                     // IMMED
147         -1,                     // INDX
148         -1,                     // INDY
149         -1,                     // IND
150         -1,                     // REL
151         -1,                     // ZP
152         -1,                     // ZPX
153         -1                      // ZPY
154 };
155
156 static char a8internal[] =
157 {
158     ' ', 0,   '!', 1,   '"', 2,   '#', 3,   '$',  4,   '%', 5,   '&', 6,   '\'', 7,
159     '(', 8,   ')', 9,   '*', 10,  '+', 11,  ',',  12,  '-', 13,  '.', 14,  '/',  15,
160     '0', 16,  '1', 17,  '2', 18,  '3', 19,  '4',  20,  '5', 21,  '6', 22,  '7',  23,
161     '8', 24,  '9', 25,  ':', 26,  ';', 27,  '<',  28,  '=', 29,  '>', 30,  '?',  31,
162     '@', 32,  'A', 33,  'B', 34,  'C', 35,  'D',  36,  'E', 37,  'F', 38,  'G',  39,
163     'H', 40,  'I', 41,  'J', 42,  'K', 43,  'L',  44,  'M', 45,  'N', 46,  'O',  47,
164     'P', 48,  'Q', 49,  'R', 50,  'S', 51,  'T',  52,  'U', 53,  'V', 54,  'W',  55,
165     'X', 56,  'Y', 57,  'Z', 58,  '[', 59,  '\\', 60,  ']', 61,  '^', 62,  '_',  63,
166     'a', 97,  'b', 98,  'c', 99,  'd', 100, 'e',  101, 'f', 102, 'g', 103, 'h',  104,
167     'i', 105, 'j', 106, 'k', 107, 'l', 108, 'm',  109, 'n', 110, 'o', 111, 'p',  112,
168     'q', 113, 'r', 114, 's', 115, 't', 116, 'u',  117, 'v', 118, 'w', 119, 'x',  120,
169     'y', 121, 'z', 122
170 };
171
172
173 //
174 // Initialize 6502 assembler
175 //
176 void Init6502()
177 {
178         register int i;
179         register int j;
180
181         register char * s = imodes;
182
183         // Set all instruction slots to illegal
184         for(i=0; i<NMACHOPS; i++)
185                 for(j=0; j<NMODES; j++)
186                         inf[i][j] = ILLEGAL;
187
188         // Uncompress legal instructions into their slots
189         for(i=0; i<NMACHOPS; i++)
190         {
191                 do
192                 {
193                         j = *s & 0xFF;
194                         inf[i][j] = *s;
195                         ops[i][j] = s[1];
196
197                         /* hack A65_REL mode */
198                         if (*s == A65_REL)
199                         {
200                                 inf[i][A65_ABS] = A65_REL;
201                                 ops[i][A65_ABS] = s[1];
202                                 inf[i][A65_ZP] = A65_REL;
203                                 ops[i][A65_ZP] = s[1];
204                         }
205                 }
206                 while (*(s += 2) != (char)END65);
207
208                 s++;
209         }
210
211         // Set up first org section (set to zero)
212         orgmap[0][0] = 0;
213
214         SwitchSection(M6502);   // Switch to 6502 section
215
216         // Initialise string conversion table(s)
217         char * p = a8internal;
218         memset(strtoa8, 31, 128);   // 31=fallback value ("?")
219
220         for(; p<a8internal+sizeof(a8internal); p+=2)
221                 strtoa8[p[0]] = p[1];
222
223         if (challoc == 0)
224         {
225                 // Allocate and clear 64K of space for the 6502 section
226                 chcheck(UPSEG_SIZE);
227                 memset(sect[M6502].scode->chptr, 0, UPSEG_SIZE);
228         }
229
230         SwitchSection(TEXT);    // Go back to TEXT
231 }
232
233
234 //
235 // .6502 --- enter 6502 mode
236 //
237 int d_6502()
238 {
239         SaveSection();                  // Save curent section
240         SwitchSection(M6502);   // Switch to 6502 section
241
242         return 0;
243 }
244
245
246 //
247 // Do 6502 code generation
248 //
249 void m6502cg(int op)
250 {
251         register int amode;             // (Parsed) addressing mode
252         register int i;
253         uint64_t eval;                  // Expression value
254         WORD eattr;                             // Expression attributes
255         int zpreq;                              // 1, optimize instr to zero-page form
256         register char * p;              // (Temp) string usage
257         ch_size = 0;                    // Reset chunk size on every instruction
258
259         //
260         // Parse 6502 addressing mode
261         //
262         zpreq = 0;
263
264         switch (tok[0])
265         {
266         case EOL:
267                 amode = A65_IMPL;
268                 break;
269
270         case KW_A:
271                 if (tok[1] != EOL)
272                         goto badmode;
273
274                 amode = A65_IMPL;
275                 tok++;
276                 break;
277
278         case '#':
279                 tok++;
280
281                 if (*tok == '>')
282                 {
283                         tok++;
284
285                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
286                                 return;
287
288                         amode = A65_IMMEDH;
289                         break;
290                 }
291                 else if (*tok == '<')
292                 {
293                         tok++;
294
295                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
296                                 return;
297
298                         amode = A65_IMMEDL;
299                         break;
300                 }
301
302                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
303                         return;
304
305                 amode = A65_IMMED;
306                 break;
307
308         case '(':
309                 tok++;
310
311                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
312                         return;
313
314                 if (*tok == ')')
315                 {
316                         // (foo) or (foo),y
317                         if (*++tok == ',')
318                         {
319                                 // (foo),y
320                                 tok++;
321 #if 0
322                                 p = string[tok[1]];
323
324                                 // Sleazo tolower() -----------------vvvvvvvvvvv
325                                 if (*tok != SYMBOL || p[1] != EOS || (*p | 0x20) != 'y')
326                                         goto badmode;
327
328                                 tok += 2;
329                                 amode = A65_INDY;
330 #else
331                                 if (tok[0] == KW_Y)
332                                         amode = A65_INDY;
333
334                                 if (tok[1] != EOL)
335                                         goto badmode;
336
337                                 tok++;
338 #endif
339                         }
340                         else
341                                 amode = A65_IND;
342                 }
343                 else if (*tok == ',')
344                 {
345                         // (foo,x)
346                         tok++;
347 #if 0
348                         p = string[tok[1]];
349
350                         // Sleazo tolower() -----------------vvvvvvvvvvv
351                         if (*tok != SYMBOL || p[1] != EOS || (*p | 0x20) != 'x')
352                                 goto badmode;
353
354                         tok += 2;
355                         if (*tok++ != ')')
356                                 goto badmode;
357
358                         amode = A65_INDX;
359 #else
360                         if (tok[0] == KW_X)
361                                 amode = A65_INDX;
362
363                         if ((tok[1] != ')') || (tok[2] != EOL))
364                                 goto badmode;
365
366                         tok += 2;
367 #endif
368                 }
369                 else
370                         goto badmode;
371
372                 break;
373
374         // I'm guessing that the form of this is @<expr>(X) or @<expr>(Y), which
375         // I've *never* seen before.  :-/
376         case '@':
377                 tok++;
378
379                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
380                         return;
381
382                 if (*tok == '(')
383                 {
384                         tok++;
385 #if 0
386                         p = string[tok[1]];
387
388                         if (*tok != SYMBOL || p[1] != EOS || tok[2] != ')' || tok[3] != EOL)
389                                 goto badmode;
390
391                         i = (*p | 0x20);        // Sleazo tolower()
392
393                         if (i == 'x')
394                                 amode = A65_INDX;
395                         else if (i == 'y')
396                                 amode = A65_INDY;
397                         else
398                                 goto badmode;
399
400                         tok += 3;               // Past SYMBOL <string> ')' EOL
401 #else
402                         if ((tok[1] != ')') || (tok[2] != EOL))
403                                 goto badmode;
404
405                         if (tok[0] == KW_X)
406                                 amode = A65_INDX;
407                         else if (tok[0] == KW_Y)
408                                 amode = A65_INDY;
409                         else
410                                 goto badmode;
411
412                         tok += 2;
413 #endif
414                         zpreq = 1;              // Request zeropage optimization
415                 }
416                 else if (*tok == EOL)
417                         amode = A65_IND;
418                 else
419                         goto badmode;
420
421                 break;
422
423         default:
424                 //
425                 // Short-circuit
426                 //   x,foo
427                 //   y,foo
428                 //
429                 p = string[tok[1]];
430                 // ggn: the following code is effectively disabled as it would make
431                 //      single letter labels not work correctly (would not identify the
432                 //      label properly). And from what I understand it's something to
433                 //      keep compatibility with the coinop assembler which is probably
434                 //      something we don't care about much :D
435 #if 0
436                 if (*tok == SYMBOL && p[1] == EOS && tok[2] == ',')
437                 {
438                         tok += 3;               // Past: SYMBOL <string> ','
439                         i = (*p | 0x20);
440
441                         if (i == 'x')
442                                 amode = A65_ABSX;
443                         else if (i == 'y')
444                                 amode = A65_ABSY;
445                         else
446                                 goto not_coinop;
447
448                         if (expr(exprbuf, &eval, &eattr, NULL) < 0)
449                                 return;
450
451                         if (*tok != EOL)
452                                 goto badmode;
453
454                         zpreq = 1;
455                         break;
456                 }
457
458 not_coinop:
459 #endif
460                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
461                         return;
462
463                 zpreq = 1;
464
465                 if (*tok == EOL)
466                         amode = A65_ABS;
467                 else if (*tok == ',')
468                 {
469                         tok++;
470 #if 0
471                         p = string[tok[1]];
472
473                         if (*tok != SYMBOL || p[1] != EOS)
474                                 goto badmode;
475
476                         tok += 2;
477
478                         //
479                         // Check for X or Y index register;
480                         // the OR with 0x20 is a sleazo conversion
481                         // to lower-case that actually works.
482                         //
483                         i = *p | 0x20;  // Oooh, this is slimey (but fast!)
484
485                         if (i == 'x')
486                                 amode = A65_ABSX;
487                         else if (i == 'y')
488                                 amode = A65_ABSY;
489                         else
490                                 goto badmode;
491 #else
492                         if (tok[0] == KW_X)
493                         {
494                                 amode = A65_ABSX;
495                                 tok++;
496                         }
497                         else if (tok[0] == KW_Y)
498                         {
499                                 amode = A65_ABSY;
500                                 tok++;
501                         }
502
503                         if (tok[0] != EOL)
504                                 goto badmode;
505 #endif
506                 }
507                 else
508                         goto badmode;
509
510                 break;
511
512 badmode:
513                 error("bad 6502 addressing mode");
514                 return;
515         }
516
517         //
518         // Optimize ABS modes to zero-page when possible
519         //   o  ZPX or ZPY is illegal, or
520         //   o  expr is zeropage && zeropageRequest && expression is defined
521         //
522         if (inf[op][amode] == ILLEGAL   // If current op is illegal,
523                 || (eval < 0x100                        // or expr must be zero-page
524                 && zpreq != 0                           // amode must request zero-page opt.
525                 && (eattr & DEFINED)))          // and the expression must be defined
526         {
527                 i = abs2zp[amode];                      // i = zero-page translation of amode
528 #ifdef DO_DEBUG
529                 DEBUG printf(" OPT: op=%d amode=%d i=%d inf[op][i]=%d\n",
530                                          op, amode, i, inf[op][i]);
531 #endif
532                 if (i >= 0 && (inf[op][i] & 0xFF) != ILLEGAL) // Use it if it's legal
533                         amode = i;
534         }
535
536 #ifdef DO_DEBUG
537         DEBUG printf("6502: op=%d amode=%d ", op, amode);
538         DEBUG printf("inf[op][amode]=%d\n", (int)inf[op][amode]);
539 #endif
540
541         switch (inf[op][amode])
542         {
543                 case A65_IMPL:          // Just leave the instruction
544                         D_byte(ops[op][amode]);
545                         break;
546
547                 case A65_IMMEDH:
548                         D_byte(ops[op][amode]);
549
550                         if (!(eattr & DEFINED))
551                         {
552                                 AddFixup(FU_BYTEH, sloc, exprbuf);
553                                 eval = 0;
554                         }
555
556                         eval = (eval >> 8) & 0xFF; // Bring high byte to low
557                         D_byte(eval);                           // Deposit byte following instr
558                         break;
559
560                 case A65_IMMEDL:
561                         D_byte(ops[op][amode]);
562
563                         if (!(eattr & DEFINED))
564                         {
565                                 AddFixup(FU_BYTEL, sloc, exprbuf);
566                                 eval = 0;
567                         }
568
569                         eval = eval & 0xFF; // Mask high byte
570                         D_byte(eval);           // Deposit byte following instr
571                         break;
572
573                 case A65_IMMED:
574                 case A65_INDX:
575                 case A65_INDY:
576                 case A65_ZP:
577                 case A65_ZPX:
578                 case A65_ZPY:
579                         D_byte(ops[op][amode]);
580
581                         if (!(eattr & DEFINED))
582                         {
583                                 AddFixup(FU_BYTE, sloc, exprbuf);
584                                 eval = 0;
585                         }
586                         else if (eval + 0x100 >= 0x200)
587                         {
588                                 error(range_error);
589                                 eval = 0;
590                         }
591
592                         D_byte(eval);           // Deposit byte following instr
593                         break;
594
595                 case A65_REL:
596                         D_byte(ops[op][amode]);
597
598                         if (eattr & DEFINED)
599                         {
600                                 eval -= (sloc + 1);
601
602                                 if (eval + 0x80 >= 0x100)
603                                 {
604                                         error(range_error);
605                                         eval = 0;
606                                 }
607
608                                 D_byte(eval);
609                         }
610                         else
611                         {
612                                 AddFixup(FU_6BRA, sloc, exprbuf);
613                                 D_byte(0);
614                         }
615
616                         break;
617
618                 case A65_ABS:
619                 case A65_ABSX:
620                 case A65_ABSY:
621                 case A65_IND:
622                         D_byte(ops[op][amode]);
623
624                         if (!(eattr & DEFINED))
625                         {
626                                 AddFixup(FU_WORD, sloc, exprbuf);
627                                 eval = 0;
628                         }
629
630                         D_rword(eval);
631                         break;
632
633                 //
634                 // Deposit 3 NOPs for illegal things (why 3? why not 30? or zero?)
635                 //
636                 default:
637                 case ILLEGAL:
638                         for(i=0; i<3; i++)
639                                 D_byte(NOP);
640
641                         error("illegal 6502 addressing mode");
642         }
643
644         // Check for overflow of code region
645         if (sloc > 0x10000L)
646                 fatal("6502 code pointer > 64K");
647
648         ErrorIfNotAtEOL();
649 }
650
651
652 //
653 // Generate 6502 object output file.
654 // ggn: Converted to COM/EXE/XEX output format
655 //
656 void m6502obj(int ofd)
657 {
658         uint8_t header[4];
659
660         CHUNK * ch = sect[M6502].scode;
661
662         // If no 6502 code was generated, bail out
663         if ((ch == NULL) || (ch->challoc == 0))
664                 return;
665
666         register uint8_t * p = ch->chptr;
667
668         // Write out mandatory $FFFF header
669         header[0] = header[1] = 0xFF;
670         uint32_t unused = write(ofd, header, 2);
671
672         for(uint16_t * l=&orgmap[0][0]; l<currentorg; l+=2)
673         {
674                 SETLE16(header, 0, l[0]);
675                 SETLE16(header, 2, l[1] - 1);
676
677                 // Write header for segment
678                 unused = write(ofd, header, 4);
679                 // Write the segment data
680                 unused = write(ofd, p + l[0], l[1] - l[0]);
681         }
682 }
683