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)
2 files changed,  +315, -5
+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+
+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);