Version bump for last commit; now at v2.0.23.
[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         uint64_t eval = -1;             // Expression value
253         WORD eattr = 0;                 // Expression attributes
254         int zpreq = 0;                  // 1 = optimize instr to zero-page form
255         ch_size = 0;                    // Reset chunk size on every instruction
256
257         //
258         // Parse 6502 addressing mode
259         //
260         switch (tok[0])
261         {
262         case EOL:
263                 amode = A65_IMPL;
264                 break;
265
266         case KW_A:
267                 if (tok[1] != EOL)
268                         goto badmode;
269
270                 tok++;
271                 amode = A65_IMPL;
272                 break;
273
274         case '#':
275                 tok++;
276
277                 if (*tok == '>')
278                 {
279                         tok++;
280                         amode = A65_IMMEDH;
281                 }
282                 else if (*tok == '<')
283                 {
284                         tok++;
285                         amode = A65_IMMEDL;
286                 }
287                 else
288                         amode = A65_IMMED;
289
290                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
291                         return;
292
293                 break;
294
295         case '(':
296                 tok++;
297
298                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
299                         return;
300
301                 if (*tok == ')')
302                 {
303                         // (foo) or (foo),y
304                         if (*++tok == ',')
305                         {
306                                 // (foo),y
307                                 tok++;
308                                 amode = A65_INDY;
309
310                                 if (tok[0] != KW_Y)
311                                         goto badmode;
312
313                                 tok++;
314                         }
315                         else
316                                 amode = A65_IND;
317                 }
318                 else if ((tok[0] == ',') && (tok[1] == KW_X) && (tok[2] == ')'))
319                 {
320                         // (foo,x)
321                         tok += 3;
322                         amode = A65_INDX;
323                 }
324                 else
325                         goto badmode;
326
327                 break;
328
329         // I'm guessing that the form of this is @<expr>(X) or @<expr>(Y), which
330         // I've *never* seen before.  :-/
331         case '@':
332                 tok++;
333
334                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
335                         return;
336
337                 if (*tok == '(')
338                 {
339                         tok++;
340
341                         if ((tok[1] != ')') || (tok[2] != EOL))
342                                 goto badmode;
343
344                         if (tok[0] == KW_X)
345                                 amode = A65_INDX;
346                         else if (tok[0] == KW_Y)
347                                 amode = A65_INDY;
348                         else
349                                 goto badmode;
350
351                         tok += 2;
352                         zpreq = 1;              // Request zeropage optimization
353                 }
354                 else if (*tok == EOL)
355                         amode = A65_IND;
356                 else
357                         goto badmode;
358
359                 break;
360
361         default:
362                 //   <expr>
363                 //   <expr>,x
364                 //   <expr>,y
365                 if (expr(exprbuf, &eval, &eattr, NULL) < 0)
366                         return;
367
368                 zpreq = 1;              // Request zeropage optimization
369
370                 if (tok[0] == EOL)
371                         amode = A65_ABS;
372                 else if (tok[0] == ',')
373                 {
374                         tok++;
375
376                         if (tok[0] == KW_X)
377                         {
378                                 tok++;
379                                 amode = A65_ABSX;
380                         }
381                         else if (tok[0] == KW_Y)
382                         {
383                                 tok++;
384                                 amode = A65_ABSY;
385                         }
386                 }
387                 else
388                         goto badmode;
389
390                 break;
391
392 badmode:
393                 error("bad 6502 addressing mode");
394                 return;
395         }
396
397         //
398         // Optimize ABS modes to zero-page when possible
399         //   o  ZPX or ZPY is illegal, or
400         //   o  expr is zeropage && zeropageRequest && expression is defined
401         //
402         if ((inf[op][amode] == ILLEGAL) // If current op is illegal OR
403                 || (zpreq                                       // amode requested a zero-page optimize
404                         && (eval < 0x100)               // and expr is zero-page
405                         && (eattr & DEFINED)))  // and the expression is defined
406         {
407                 int i = abs2zp[amode];          // Get zero-page translation of amode
408 #ifdef DO_DEBUG
409                 DEBUG printf(" OPT: op=%d amode=%d i=%d inf[op][i]=%d\n",
410                                          op, amode, i, inf[op][i]);
411 #endif
412                 if (i >= 0 && (inf[op][i] & 0xFF) != ILLEGAL) // & use it if it's legal
413                         amode = i;
414         }
415
416 #ifdef DO_DEBUG
417         DEBUG printf("6502: op=%d amode=%d ", op, amode);
418         DEBUG printf("inf[op][amode]=%d\n", (int)inf[op][amode]);
419 #endif
420
421         switch (inf[op][amode])
422         {
423                 case A65_IMPL:          // Just leave the instruction
424                         D_byte(ops[op][amode]);
425                         break;
426
427                 case A65_IMMEDH:
428                         D_byte(ops[op][amode]);
429
430                         if (!(eattr & DEFINED))
431                         {
432                                 AddFixup(FU_BYTEH, sloc, exprbuf);
433                                 eval = 0;
434                         }
435
436                         eval = (eval >> 8) & 0xFF; // Bring high byte to low
437                         D_byte(eval);                           // Deposit byte following instr
438                         break;
439
440                 case A65_IMMEDL:
441                         D_byte(ops[op][amode]);
442
443                         if (!(eattr & DEFINED))
444                         {
445                                 AddFixup(FU_BYTEL, sloc, exprbuf);
446                                 eval = 0;
447                         }
448
449                         eval = eval & 0xFF; // Mask high byte
450                         D_byte(eval);           // Deposit byte following instr
451                         break;
452
453                 case A65_IMMED:
454                 case A65_INDX:
455                 case A65_INDY:
456                 case A65_ZP:
457                 case A65_ZPX:
458                 case A65_ZPY:
459                         D_byte(ops[op][amode]);
460
461                         if (!(eattr & DEFINED))
462                         {
463                                 AddFixup(FU_BYTE, sloc, exprbuf);
464                                 eval = 0;
465                         }
466                         else if (eval + 0x100 >= 0x200)
467                         {
468                                 error(range_error);
469                                 eval = 0;
470                         }
471
472                         D_byte(eval);           // Deposit byte following instr
473                         break;
474
475                 case A65_REL:
476                         D_byte(ops[op][amode]);
477
478                         if (eattr & DEFINED)
479                         {
480                                 eval -= (sloc + 1);
481
482                                 if (eval + 0x80 >= 0x100)
483                                 {
484                                         error(range_error);
485                                         eval = 0;
486                                 }
487
488                                 D_byte(eval);
489                         }
490                         else
491                         {
492                                 AddFixup(FU_6BRA, sloc, exprbuf);
493                                 D_byte(0);
494                         }
495
496                         break;
497
498                 case A65_ABS:
499                 case A65_ABSX:
500                 case A65_ABSY:
501                 case A65_IND:
502                         D_byte(ops[op][amode]);
503
504                         if (!(eattr & DEFINED))
505                         {
506                                 AddFixup(FU_WORD, sloc, exprbuf);
507                                 eval = 0;
508                         }
509
510                         D_rword(eval);
511                         break;
512
513                 //
514                 // Deposit 3 NOPs for illegal things (why 3? why not 30? or zero?)
515                 //
516                 default:
517                 case ILLEGAL:
518                         D_byte(NOP);
519                         D_byte(NOP);
520                         D_byte(NOP);
521
522                         error("illegal 6502 addressing mode");
523         }
524
525         // Check for overflow of code region
526         if (sloc > 0x10000L)
527                 fatal("6502 code pointer > 64K");
528
529         ErrorIfNotAtEOL();
530 }
531
532
533 //
534 // Generate 6502 object output file.
535 // ggn: Converted to COM/EXE/XEX output format
536 //
537 void m6502obj(int ofd)
538 {
539         uint8_t header[4];
540
541         CHUNK * ch = sect[M6502].scode;
542
543         // If no 6502 code was generated, bail out
544         if ((ch == NULL) || (ch->challoc == 0))
545                 return;
546
547         register uint8_t * p = ch->chptr;
548
549         // Write out mandatory $FFFF header
550         header[0] = header[1] = 0xFF;
551         uint32_t unused = write(ofd, header, 2);
552
553         for(uint16_t * l=&orgmap[0][0]; l<currentorg; l+=2)
554         {
555                 SETLE16(header, 0, l[0]);
556                 SETLE16(header, 2, l[1] - 1);
557
558                 // Write header for segment
559                 unused = write(ofd, header, 4);
560                 // Write the segment data
561                 unused = write(ofd, p + l[0], l[1] - l[0]);
562         }
563 }
564