commit 40d8b1b
zzo38
·
2023-06-27 20:00:14 +0000 UTC
parent d308176
Implement expanded memory extensions in assembler. (Not fully tested so far; only partially tested)
M
asm.doc
+72,
-1
1@@ -94,7 +94,8 @@ The following standard tokens are available:
2 the input operands in the stack. (You cannot code the JCI, JMI, and JSI
3 instructions in this way, although BRK can be coded in this way.)
4
5- A name by itself to make a JSI instruction.
6+ A name by itself to make a JSI instruction (if it does not match any
7+ of the other tokens mentioned above).
8
9 The following nonstandard tokens are available:
10
11@@ -120,6 +121,11 @@ The following nonstandard tokens are available:
12 about special calculations. These are executed immediately at compile
13 time, before the compiled program is written to the output file.
14
15+ \` is like \_ but the calculation is deferred until when references
16+ are being resolved. By this time, any references that come before this
17+ command will already be resolved, and the memory containing them can
18+ be read and overwritten by the calculation if desired.
19+
20 \- or \= followed by eight more characters and optionally ; will make
21 a graphical tile; you should have eight such commands per tile, and
22 a semicolon at the end of the last one. Use \- for 1bpp tiles and \=
23@@ -227,6 +233,8 @@ cannot write in the zero page or overwrite what is already written.
24 W ( value -- ) Write 16-bits at the current address and advance. You
25 cannot write in the zero page or overwrite what is already written.
26
27+x ( length -- ) Add padding into expanded memory.
28+
29 () ( condition -- ) Put other commands in the () block; it will be
30 executed only if the condition is zero, and skipped if nonzero. Conditional
31 blocks of the same kind cannot be nested.
32@@ -245,3 +253,66 @@ xxxx is four lowercase hex digits being the address of that label.
33
34 yyyy is the name of that label.
35
36+
37+=== Expanded memory ===
38+
39+A nonstandard feature allows writing large files that have preset data in
40+expanded memory. Any expanded memory data comes after the conventional
41+memory data. It is possible that some data will fit in conventional memory
42+in which case such data will be entered into conventional memory, but you
43+can override this using alignment and padding commands.
44+
45+The following nonstandard tokens are used for expanded memory:
46+
47+ \* followed by a name will make a label that refers to the address within
48+ expanded memory. The plain name refers to the low 16-bits of the address
49+ and the name followed by ^ refers to the high 16-bits (the page number).
50+ This also sets the initial scope name of any sub-assemblies that follow.
51+
52+ \^ followed by a file name is similar to \~ but writes the data into
53+ expanded memory.
54+
55+ \B \E \F followed by a digit 0 to 9 or a letter a to u (representing a
56+ number ten to thirty) will begin a new expanded memory block which is
57+ aligned to begin or end at an address divisible by two to the power of
58+ that number, or to fit into an aligned space with that size. For example,
59+ \Bg will require the data to begin on a page boundary, while \Fg will
60+ require this block to fit in a single page (which might be the same page
61+ as the previous block if it fits).
62+
63+ \A followed by a number (which can be a 32-bit number) will begin a new
64+ expanded memory block which begins at the specified absolute address. If
65+ everything before it is already beyond that address, then it is an error.
66+
67+ \C followed by a number will begin a new expanded memory block which must
68+ fit into the specified number of pages, otherwise it is an error. If
69+ necessary, it will align it to a new page, but if the data is small enough
70+ then this will be unnecessary and it will not do alignment.
71+
72+ \D followed by a number will begin a new expanded memory block which must
73+ start on the specified page but not necessarily at the beginning.
74+
75+ [^ ]^ surround a block which will be entered into expanded memory. This
76+ can contain assembly code which makes up to sixty-four bytes of data, and
77+ should not contain any labels nor references (including anonymous labels
78+ and references due to the [@ ]@ blocks). (However, \: is allowed.)
79+
80+ ^ followed by a file name makes a sub-assembly. This is similar to ~ but
81+ the data which is assembled from that file is written to expanded memory
82+ like \^ instead of like \~ does. This is parsed after the rest of the main
83+ file is parsed, instead of before like the ~ command does. The initial
84+ internal address in the sub-assembly, which is also the starting address
85+ that data will be copied from, is the same as the outer address; you can
86+ use | to change this if desired. (The intended use of this is for making
87+ overlays which contain absolute addressing within the overlay. If you
88+ only use relative addressing then this is not important.) A temporary file
89+ named ".uxn_subassembly" will be created; it can safely be deleted.
90+
91+ \+ followed by a name of a label or macro will export that label or macro
92+ to all sub-assemblies; inside of a sub-assembly it will export a name (you
93+ cannot export a macro from a sub-assembly) into the main code (which can
94+ be exported to later sub-assemblies, but not ones that come before). Even
95+ if it is exported from the main code, a label defined by \* cannot be
96+ referenced by sub-assemblies in the same or an earlier block than the \*
97+ is in, but can be referenced by sub-assemblies in any later block.
98+
M
uxnasm.c
+243,
-4
1@@ -4,6 +4,7 @@ exit
2 #endif
3
4 #include <stdio.h>
5+#include <string.h>
6
7 /*
8 Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick
9@@ -24,6 +25,7 @@ named in the above copyright notice.]
10 #define MAXLABELS 0x400
11 #define MAXMACROS 0x100
12 #define MAXREFS 0x800
13+#define MAXEXPS 0x80
14 #define MAXITEMS 0x40
15
16 #define TRIM 0x0100
17@@ -32,6 +34,7 @@ named in the above copyright notice.]
18 typedef unsigned char Uint8;
19 typedef signed char Sint8;
20 typedef unsigned short Uint16;
21+typedef unsigned int Uint32;
22
23 typedef struct {
24 char name[0x40], items[MAXITEMS][0x40];
25@@ -48,14 +51,22 @@ typedef struct {
26 Uint16 addr;
27 } Reference;
28
29+typedef struct {
30+ char name[0x40];
31+ Uint32 offset,len;
32+ Uint8 kind;
33+} ExpItem;
34+
35 typedef struct {
36 Uint8 data[LENGTH];
37 unsigned int ptr, length;
38- Uint16 llen, mlen, rlen;
39+ Uint16 llen, mlen, rlen, xlen;
40 Label labels[MAXLABELS];
41 Macro macros[MAXMACROS];
42 Reference refs[MAXREFS];
43+ ExpItem exps[MAXEXPS];
44 char scope[0x40];
45+ Uint32 total;
46 } Program;
47
48 static Program p;
49@@ -65,6 +76,8 @@ static Uint16 sstack[256];
50 static int bsptr,bcnt;
51 static Uint16 bstack[256];
52 static char spec_rune[128];
53+static Uint16 exstart;
54+static FILE*subas;
55
56 /* clang-format off */
57
58@@ -199,7 +212,7 @@ makereference(char *scope, char *label, Uint16 addr)
59 if(p.rlen == MAXREFS)
60 return error("References limit exceeded", label);
61 r = &p.refs[p.rlen++];
62- if(label[1] == '&')
63+ if(label[1] == '&' && scope)
64 scpy(sublabel(subw, scope, label + 2), r->name, 0x40);
65 else {
66 int pos = spos(label + 1, '/');
67@@ -215,6 +228,16 @@ makereference(char *scope, char *label, Uint16 addr)
68 return 1;
69 }
70
71+static int makeexp(Uint8 kind,char*name,Uint32 len,Uint8 misc) {
72+ ExpItem*e;
73+ if(p.xlen==MAXEXPS) return error("Expansion limit exceeded","");
74+ e=p.exps+(p.xlen++);
75+ if(name) scpy(name,e->name,0x40); else e->name[0]=misc,e->name[1]=0;
76+ e->kind=kind;
77+ e->len=len;
78+ return 1;
79+}
80+
81 static int
82 writebyte(Uint8 b)
83 {
84@@ -319,6 +342,7 @@ static int special_calc(const char*w) {
85 case 'P': if(ssptr<2) return 0; a=sstack[--ssptr]; b=sstack[--ssptr]; if(a==0xFFFF) return 0; p.data[a]=b>>8; p.data[a+1]=b&255; break;
86 case 'w': if(ssptr<1) return 0; a=sstack[--ssptr]; writebyte(a); break;
87 case 'W': if(ssptr<1) return 0; a=sstack[--ssptr]; writeshort(a,0); break;
88+ case 'x': if(ssptr<1) return 0; a=sstack[--ssptr]; if(!makeexp(0,0,a,0)) return 0; break;
89 case '(': if(ssptr<1) return 0; if(!sstack[--ssptr]) break; while(*w && *w!=')') w++; break;
90 case ')': /* do nothing */ break;
91 case '[': if(ssptr<1) return 0; if(sstack[--ssptr]) break; while(*w && *w!=']') w++; break;
92@@ -356,6 +380,15 @@ static int make_tile(int h,const char*w) {
93 return 1;
94 }
95
96+static int exp_file(char*w) {
97+ FILE*f=fopen(w,"rb");
98+ if(!f) return error("Cannot open file",w);
99+ fseek(f,0,SEEK_END);
100+ if(!makeexp(2,w,ftell(f),0)) return 0;
101+ fclose(f);
102+ return 1;
103+}
104+
105 static int
106 parse(char *w, FILE *f)
107 {
108@@ -415,6 +448,7 @@ parse(char *w, FILE *f)
109 }
110 break;
111 case '@': /* label */
112+ if(w[1]=='\\') break;
113 if(!makelabel(w + 1))
114 return error("Invalid label", w);
115 scpy(w + 1, p.scope, 0x40);
116@@ -491,9 +525,29 @@ parse(char *w, FILE *f)
117 break;
118 case '_': if(!special_calc(w+2)) return error("Special calculation error",w); break;
119 case '-': case '=': if(!make_tile(w[1]=='=',w+2)) return error("Tile error",w); break;
120+ case '`': makereference(0,w+1,p.ptr); break;
121+ case '*': if(!makeexp('*',w+2,0,0)) return 0; break;
122+ case 'A': case 'C': case 'D':
123+ if(!sihx(w+1)) return error("Invalid hex literal",w);
124+ if(!makeexp(w[1],w+2,shex(w+2),0)) return 0;
125+ break;
126+ case 'B': case 'E': case 'F':
127+ if(w[2]>='0' && w[2]<='9') i=w[2]-'0';
128+ else if(w[2]>='a' && w[2]<='u') i=w[2]+10-'a';
129+ else return error("Invalid expanded memory alignment",w);
130+ if(!makeexp(w[1],0,0,i)) return 0;
131+ break;
132+ case '^':
133+ if(!exp_file(w+2)) return 0;
134+ break;
135+ case '+': w[1]='x'; makereference(0,w+1,1); break;
136 default: return error("Invalid special",w);
137 }
138 break;
139+ case '^': /* sub-assembly */
140+ if(!makeexp('^',w+1,0,0)) return 0;
141+ p.exps[p.xlen-1].len=p.ptr;
142+ break;
143 case '[':
144 case ']':
145 if(slen(w) == 1) break;
146@@ -522,6 +576,16 @@ parse(char *w, FILE *f)
147 snprintf(w,10,"\\%x\\",i);
148 if(!makelabel(w)) return error("Invalid label",w);
149 break;
150+ } else if(w[1]=='^' && !w[2]) {
151+ if(*w=='[') {
152+ exstart=p.ptr;
153+ } else {
154+ if(p.ptr<exstart || p.ptr-exstart>0x40) return error("Too long inline expanded memory data",w);
155+ if(!makeexp(1,0,p.ptr-exstart,0)) return 0;
156+ memcpy(p.exps[p.xlen-1].name,p.data+exstart,p.ptr-exstart);
157+ p.ptr=p.length=exstart;
158+ }
159+ break;
160 }
161 /* fall through */
162 default: defa:
163@@ -613,6 +677,13 @@ resolve(void)
164 p.data[r->addr + 1] = a & 0xff;
165 l->refs++;
166 break;
167+ case '`':
168+ p.ptr=r->addr;
169+ if(!special_calc(r->name)) return error("Special calculation error", r->name);
170+ break;
171+ case 'x':
172+ if(l=findlabel(r->name)) l->refs++;
173+ break;
174 default:
175 return error("Unknown reference", r->name);
176 }
177@@ -620,6 +691,129 @@ resolve(void)
178 return 1;
179 }
180
181+static Uint32 expanded_block_length(int x) {
182+ ExpItem*e;
183+ Uint32 t=0;
184+ int i;
185+ for(i=x;i<p.xlen;i++) {
186+ e=p.exps+i;
187+ if(e->kind>='A' && e->kind<='F') break;
188+ t+=e->len;
189+ }
190+ return t;
191+}
192+
193+static int do_subassembly(ExpItem*e) {
194+ Program ps=p;
195+ Uint16 st=e->len;
196+ Label*la;
197+ int i,j;
198+ if(!subas) {
199+ subas=fopen(".uxn_subassembly","wb");
200+ if(!subas) return error("Cannot open subassembly temporary file",".uxn_subassembly");
201+ }
202+ p.ptr=p.length=st;
203+ p.llen=p.mlen=p.rlen=0;
204+ for(i=0;i<ps.rlen;i++) if(ps.refs[i].rune=='x') {
205+ for(j=0;j<ps.mlen;j++) if(scmp(ps.macros[j].name,ps.refs[i].name,0x40)) {
206+ if(p.mlen==MAXMACROS) return error("Too many exports","");
207+ p.macros[p.mlen++]=ps.macros[j];
208+ goto found;
209+ }
210+ for(j=0;j<ps.llen;j++) if(scmp(ps.labels[j].name,ps.refs[i].name,0x40)) {
211+ if(p.llen==MAXLABELS) return error("Too many exports","");
212+ ps.labels[j].refs=1;
213+ p.labels[p.llen++]=ps.labels[j];
214+ goto found;
215+ }
216+ found: ;
217+ }
218+ if(!doinclude(e->name)) return error("Invalid include",e->name);
219+ if(!resolve()) return error("Error resolving sub-assembly",e->name);
220+ e->len=p.length-st;
221+ if(e->len&~0xFFFF) return error("Length confusion error",e->name);
222+ fwrite(p.data+st,e->len,1,subas);
223+ for(i=0;i<p.rlen;i++) if(p.refs[i].rune=='x') {
224+ la=findlabel(p.refs[i].name);
225+ if(!la) return error("Invalid export from sub-assembly",p.refs[i].name);
226+ if(ps.llen==MAXLABELS) return error("Too many exports from sub-assembly",e->name);
227+ la->refs=1;
228+ ps.labels[ps.llen++]=*la;
229+ }
230+ for(i=ps.xlen;i<p.xlen;i++) ps.exps[i]=p.exps[i];
231+ ps.xlen=p.xlen;
232+ st=e->len;
233+ p=ps;
234+ e->kind=3;
235+ e->len=st;
236+ return 1;
237+}
238+
239+static int align_expanded(void) {
240+ ExpItem*e;
241+ Uint32 at=p.length-TRIM;
242+ Uint32 x;
243+ int i,j;
244+ for(i=0;i<p.xlen;i++) {
245+ e=p.exps+i;
246+ e->offset=at;
247+ if(e->kind>='A' && e->kind<='F') {
248+ for(j=i+1;j<p.xlen;j++) {
249+ if(p.exps[j].kind=='*') scpy(p.exps[j].name,p.scope,0x40);
250+ if(p.exps[j].kind=='^' && !do_subassembly(p.exps+j)) return 0;
251+ if(p.exps[j].kind>='A' && p.exps[j].kind<='F') break;
252+ }
253+ }
254+ switch(e->kind) {
255+ case '*': // label
256+ p.ptr=at;
257+ makelabel(e->name);
258+ p.ptr=at>>16;
259+ j=slen(e->name);
260+ if(j>=0x3F) return error("Too long expanded memory label name",e->name);
261+ e->name[j]='^';
262+ e->name[j+1]=0;
263+ makelabel(e->name);
264+ break;
265+ case '^': // sub-assembly
266+ return error("Sub-assembly outside of expanded memory block",e->name);
267+ case 'A': // absolute address
268+ if(at>e->len) return error("Expanded memory absolute address overlapping",e->name);
269+ at=e->len;
270+ break;
271+ case 'B': // align to beginning
272+ if(at&((1<<*e->name)-1)) at=(at+(1<<*e->name))&-(1<<*e->name);
273+ break;
274+ case 'C': // fit into number of pages
275+ x=expanded_block_length(i+1);
276+ if(x>(e->len<<16)) return error("Expanded memory block is too big",e->name);
277+ if(x+(at&0xFFFF)>(e->len<<16)) at=(at+0x10000)&-0x10000;
278+ break;
279+ case 'D': // start on page
280+ if((at>>16)>e->len) return error("Expanded memory page overlapping",e->name);
281+ if((at>>16)<e->len) at=e->len<<16;
282+ break;
283+ case 'E': // align to end
284+ x=expanded_block_length(i+1);
285+ if((at+x)&((1<<*e->name)-1)) at=((at+x+(1<<*e->name))&-(1<<*e->name))-x;
286+ break;
287+ case 'F': // align to fit
288+ x=expanded_block_length(i+1);
289+ if(x>(1<<*e->name)) return error("Expanded memory block does not fit in specified size","");
290+ if((at&((1<<*e->name)-1))!=((at+x)&((1<<*e->name)-1))) at=(at+((1<<*e->name)-1))&-(1<<*e->name);
291+ break;
292+ default: at+=e->len;
293+ }
294+ }
295+ p.total=at;
296+ if(subas) {
297+ fclose(subas);
298+ subas=fopen(".uxn_subassembly","rb");
299+ if(!subas) return error("Cannot open subassembly temporary file",".uxn_subassembly");
300+ }
301+ return 1;
302+}
303+
304 static int
305 assemble(FILE *f)
306 {
307@@ -634,6 +828,8 @@ assemble(FILE *f)
308 line_comment=0;
309 }
310 }
311+ p.total=p.length;
312+ if(p.xlen && !align_expanded()) return 0;
313 return resolve();
314 }
315
316@@ -647,12 +843,14 @@ review(char *filename)
317 else if(!p.labels[i].refs)
318 fprintf(stderr, "-- Unused label: %s\n", p.labels[i].name);
319 fprintf(stderr,
320- "Assembled %s in %d bytes(%.2f%% used), %d labels, %d macros.\n",
321+ "Assembled %s in %d bytes(%.2f%% used), %d labels, %d macros, %d references.\n",
322 filename,
323 p.length<TRIM?0:p.length-TRIM,
324 p.length<TRIM?0:((p.length - TRIM) / 652.80),
325 p.llen,
326- p.mlen);
327+ p.mlen,
328+ p.rlen);
329+ if(p.xlen) fprintf(stderr,"Expanded memory: %u bytes (%u pages), %d items.\nTotal: %u bytes.\n",p.total-p.length,(((p.total+0xFFFF)>>16)?:1)-1,p.xlen,p.total-TRIM);
330 }
331
332 static void send_name_output(FILE*dst) {
333@@ -660,10 +858,50 @@ static void send_name_output(FILE*dst) {
334 for(i=0;i<p.llen;i++) fprintf(dst,"|%04x @%s\n",p.labels[i].addr,p.labels[i].name);
335 }
336
337+static void send_expanded(FILE*dst) {
338+ FILE*f;
339+ ExpItem*e;
340+ Uint32 at=p.length;
341+ Uint32 x;
342+ int i;
343+ for(i=0;i<p.xlen;i++) {
344+ e=p.exps+i;
345+ if(e->offset>at) {
346+ x=e->offset-at;
347+ while(x--) fputc(0,dst);
348+ }
349+ at=e->offset;
350+ switch(e->kind) {
351+ case 1: // inline data
352+ fwrite(e->name,e->len,1,dst);
353+ at+=e->len;
354+ break;
355+ case 2: // data from file
356+ f=fopen(e->name,"rb");
357+ if(f) {
358+ x=e->len;
359+ while(x--) fputc(fgetc(f),dst);
360+ at+=e->len;
361+ fclose(f);
362+ } else {
363+ error("Cannot open file",e->name);
364+ }
365+ break;
366+ case 3: // sub-assembly
367+ if(!fread(p.data,e->len,1,subas)) error("Error reading temporary file",".uxn_subassembly");
368+ fwrite(p.data,e->len,1,dst);
369+ at+=e->len;
370+ break;
371+ }
372+ }
373+}
374+
375 int
376 main(int argc, char *argv[])
377 {
378 FILE *src, *dst;
379+ if(argc==2 && argv[1][0]=='@') printf("Program:%u Macro:%u Label:%u Reference:%u ExpItem:%u\n"
380+ ,(int)sizeof(Program),(int)sizeof(Macro),(int)sizeof(Label),(int)sizeof(Reference),(int)sizeof(ExpItem));
381 if(argc < 3)
382 return !error("usage", "input.tal output.rom [output.nam]");
383 if(!(src = fopen(argv[1], "r")))
384@@ -676,6 +914,7 @@ main(int argc, char *argv[])
385 error("Assembly", "Output rom is empty.");
386 else
387 fwrite(p.data + TRIM, p.length - TRIM, 1, dst);
388+ if(p.xlen) send_expanded(dst);
389 review(argv[2]);
390 if(argc>3 && argv[3][0]) {
391 fclose(dst);