5 #include "Script_internal.h"
12 #include "Assembler.h"
35 static int add_label(AS
*a
, char* name
, size_t insno
) {
36 char* tmp
= strdup(name
);
37 return hbmap_insert(a
->label_map
, tmp
, (unsigned) insno
) != -1;
40 static unsigned get_label_offset(AS
*a
, char* name
) {
41 return *hbmap_get(a
->label_map
, name
);
44 static int add_label_ref(AS
*a
, char * name
, size_t insno
) {
45 /* add reference to named label to a list. after the first pass
46 * over the code these locations have to be fixed with the offset
48 struct label item
= { .name
= strdup(name
), .insno
= insno
};
50 return List_add(a
->label_ref_list
, &item
);
53 static int add_function_ref(AS
*a
, char* name
, size_t insno
) {
54 /* add reference to named function to a list. after the first pass
55 * over the code these locations have to be fixed with the offset
57 struct label item
= { .name
= strdup(name
), .insno
= insno
};
59 return List_add(a
->function_ref_list
, &item
);
62 static int add_export(AS
*a
, int type
, char* name
, size_t offset
) {
63 struct export item
= { .fn
= strdup(name
), .instr
= offset
, .type
= type
};
65 return List_add(a
->export_list
, &item
);
68 static int add_fixup(AS
*a
, int type
, size_t offset
) {
69 struct fixup item
= {.type
= type
, .offset
= offset
};
70 /* offset equals instruction number for non-DATADATA fixups */
71 return List_add(a
->fixup_list
, &item
);
74 static int add_or_get_string(AS
* a
, char* str
) {
75 /* return index of string in string table
76 * add to string table if not yet existing */
77 str
++; /* leading '"' */
78 size_t l
= strlen(str
);
80 str
[l
] = 0; /* trailing '"' */
81 struct string item
= {.ptr
= str
, .len
= l
}, iter
;
83 for(; i
< List_size(a
->string_list
); i
++) {
84 assert(List_get(a
->string_list
, i
, &iter
));
85 if(iter
.len
== item
.len
&& !strcmp(iter
.ptr
, str
)) {
89 item
.ptr
= strdup(str
);
90 List_add(a
->string_list
, &item
);
91 return List_size(a
->string_list
) -1;
94 static unsigned get_string_offset(AS
*a
, unsigned index
) {
95 assert(index
< List_size(a
->string_list
));
96 unsigned i
= 0, ret
= 0;
98 for(; i
< index
; i
++) {
99 assert(List_get(a
->string_list
, i
, &item
));
105 static size_t get_string_section_length(AS
* a
) {
108 for(; i
< List_size(a
->string_list
); i
++) {
109 assert(List_get(a
->string_list
, i
, &item
));
115 static int add_variable(AS
*a
, char* name
, unsigned vs
, size_t offset
) {
116 struct variable item
= { .name
= strdup(name
), .vs
= vs
, .offset
= offset
};
117 return List_add(a
->variable_list
, &item
);
120 static int get_variable_offset(AS
* a
, char* name
) {
121 /* return globaldata offset of named variable */
123 struct variable item
;
124 for(; i
< List_size(a
->variable_list
); i
++) {
125 assert(List_get(a
->variable_list
, i
, &item
));
126 if(!strcmp(item
.name
, name
))
133 static ssize_t
find_section(FILE* in
, char* name
, size_t *lineno
) {
135 size_t off
= 0, l
= strlen(name
);
137 fseek(in
, 0, SEEK_SET
);
138 while(fgets(buf
, sizeof buf
, in
)) {
139 *lineno
= *lineno
+1;
141 if(buf
[0] == '.' && memcmp(name
, buf
+ 1, l
) == 0)
147 static int asm_data(AS
* a
) {
149 ssize_t start
= find_section(a
->in
, "data", &lineno
);
150 if(start
== -1) return 1; // it is valid for .s file to only have .text
151 fseek(a
->in
, start
, SEEK_SET
);
154 while(fgets(buf
, sizeof buf
, a
->in
) && buf
[0] != '.') {
155 if(buf
[0] == '\n') continue;
156 char* p
= buf
, *pend
= buf
+ sizeof buf
, *var
;
159 if(*p
== '#' || *p
== ';') continue;
160 while(isspace(*p
) && p
< pend
) p
++;
161 if(!memcmp(p
, "export", 6) && isspace(p
[6])) {
164 while(isspace(*p
) && p
< pend
) p
++;
166 if(memcmp(p
, "int", 3) == 0)
168 else if(memcmp(p
, "short", 5) == 0)
170 else if(memcmp(p
, "char", 4) == 0) {
175 while(isdigit(*q
) && q
< pend
) q
++;
176 if(vs
== 0 || *q
!= ']') {
177 dprintf(2, "error: expected number > 0 and ']' after '['\n");
182 } else if(memcmp(p
, "string", 6) == 0)
185 dprintf(2, "error: expected int, short, char, or string\n");
188 while(!isspace(*p
) && p
< pend
) p
++;
189 while(isspace(*p
) && p
< pend
) p
++;
191 while(!isspace(*p
) && p
< pend
) p
++;
193 assert(p
< pend
&& *p
== '=');
194 p
++; while(isspace(*p
) && p
< pend
) p
++;
200 if(memcmp(p
, "data", 4) == 0) {
202 while(isspace(*p
) && p
< pend
) p
++;
203 assert(p
< pend
&& *p
== '+');
205 while(isspace(*p
) && p
< pend
) p
++;
207 add_fixup(a
, FIXUP_DATADATA
, data_pos
);
210 dprintf(2, "error: expected \"data\"\n");
218 for(value
= vs
; value
>= 10; value
-=10)
219 ByteArray_writeMem(a
->data
, (void*)"\0\0\0\0\0\0\0\0\0\0", 10);
220 while(value
--) ByteArray_writeUnsignedByte(a
->data
, 0);
223 ByteArray_writeInt(a
->data
, value
);
226 ByteArray_writeShort(a
->data
, value
);
229 ByteArray_writeUnsignedByte(a
->data
, value
);
233 if(exportflag
) add_export(a
, EXPORT_DATA
, var
, data_pos
);
234 add_variable(a
, var
, vs
, data_pos
);
240 ssize_t
get_import_index(AS
* a
, char* name
, size_t len
) {
243 for(i
= 0; i
< List_size(a
->import_list
); i
++) {
244 assert(List_get(a
->import_list
, i
, &item
));
245 if(len
== item
.len
&& !strcmp(name
, item
.ptr
)) return i
;
250 void add_import(AS
*a
, char* name
) {
251 size_t l
= strlen(name
);
252 if(get_import_index(a
, name
, l
) != -1) return;
254 item
.ptr
= strdup(name
);
256 List_add(a
->import_list
, &item
);
259 static int find_export(AS
*a
, int type
, char* name
, unsigned *offset
) {
262 for(i
= 0; i
< List_size(a
->export_list
); i
++) {
263 assert(List_get(a
->export_list
, i
, &item
));
264 if(item
.type
== type
&& !strcmp(name
, item
.fn
)) {
265 *offset
= item
.instr
;
272 void generate_import_table(AS
*a
) {
276 for(i
= 0; i
< List_size(a
->function_ref_list
); i
++) {
277 assert(List_get(a
->function_ref_list
, i
, &item
));
278 if(!find_export(a
, EXPORT_FUNCTION
, item
.name
, &off
))
279 add_import(a
, item
.name
);
285 int get_reg(char* regname
) {
287 for(; i
< AR_MAX
; i
++)
288 if(strcmp(regnames
[i
], regname
) == 0)
293 static size_t mnemolen
[SCMD_MAX
];
294 static int mnemolen_initdone
= 0;
296 static void init_mnemolen(void) {
298 for(; i
< SCMD_MAX
; i
++)
299 mnemolen
[i
] = strlen(opcodes
[i
].mnemonic
);
300 mnemolen_initdone
= 1;
303 static unsigned find_insn(char* sym
) {
304 if(!mnemolen_initdone
) init_mnemolen();
305 size_t i
= 0, l
= strlen(sym
);
306 for(; i
< SCMD_MAX
; i
++)
307 if(l
== mnemolen
[i
] && memcmp(sym
, opcodes
[i
].mnemonic
, l
) == 0)
312 #include "StringEscape.h"
313 /* expects a pointer to the first char after a opening " in a string,
314 * converts the string into convbuf, and returns the length of that string */
315 static size_t get_length_and_convert(char* x
, char* end
, char* convbuf
, size_t convbuflen
) {
317 char* e
= x
+ strlen(x
);
318 assert(e
> x
&& e
< end
&& *e
== 0);
320 while(isspace(*e
)) e
--;
321 if(*e
!= '"') return (size_t) -1;
323 result
= unescape(x
, convbuf
, convbuflen
);
327 /* sets lets char in arg to 0, and advances pointer till the next argstart */
328 static char* finalize_arg(char **p
, char* pend
, char* convbuf
, size_t convbuflen
) {
331 size_t l
= get_length_and_convert(*p
+ 1, pend
, convbuf
+1, convbuflen
- 1);
332 if(l
== (size_t) -1) return 0;
335 *p
= 0; /* make it crash if its accessed again, since a string should always be the last arg */
339 while(*p
< pend
&& **p
!= ',' && !isspace(**p
)) (*p
)++;
342 while(*p
< pend
&& isspace(**p
)) (*p
)++;
348 static int asm_strings(AS
*a
) {
349 /* add strings in .strings section, even when they're not used from .text */
351 ssize_t start
= find_section(a
->in
, "strings", &lineno
);
352 if(start
== -1) return 1;
353 fseek(a
->in
, start
, SEEK_SET
);
355 while(fgets(buf
, sizeof buf
, a
->in
) && buf
[0] != '.') {
357 if(*p
== '#' || *p
== ';') continue;
359 size_t l
= strlen(p
);
360 assert(l
>1 && p
[l
-1] == '\n' && p
[l
-2] == '"');
362 add_or_get_string(a
, p
);
367 static int asm_text(AS
*a
) {
369 ssize_t start
= find_section(a
->in
, "text", &lineno
);
370 if(start
== -1) return 1;
371 fseek(a
->in
, start
, SEEK_SET
);
373 char convbuf
[sizeof(buf
)]; /* to convert escaped string into non-escaped version */
375 while(fgets(buf
, sizeof buf
, a
->in
) && buf
[0] != '.') {
377 char* p
= buf
, *pend
= buf
+ sizeof buf
;
378 if(*p
== '#' || *p
== ';') continue;
379 while(isspace(*p
) && p
< pend
) p
++;
383 while(!isspace(*p
) && p
< pend
) p
++;
385 size_t l
= strlen(sym
);
386 if(l
> 1 && sym
[l
-1] == ':') {
387 // functionstart or label
389 if(memcmp(sym
, "label", 5) == 0)
390 add_label(a
, sym
, pos
);
392 add_export(a
, EXPORT_FUNCTION
, sym
, pos
);
393 ByteArray_writeUnsignedInt(a
->code
, SCMD_THISBASE
);
394 ByteArray_writeUnsignedInt(a
->code
, pos
);
399 unsigned instr
= find_insn(sym
);
401 dprintf(2, "line %zu: error: unknown instruction '%s'\n", lineno
, sym
);
404 if(instr
== SCMD_THISBASE
) continue; /* we emit this instruction ourselves when a new function starts. */
406 ByteArray_writeUnsignedInt(a
->code
, instr
);
409 for(arg
= 0; arg
< opcodes
[instr
].argcount
; arg
++) {
410 sym
= finalize_arg(&p
, pend
, convbuf
, sizeof(convbuf
));
412 dprintf(2, "line %zu: error: expected \"\n", lineno
);
416 if(arg
< opcodes
[instr
].regcount
) {
418 if(instr
== SCMD_REGTOREG
) {
419 /* fix reversed order of arguments */
422 while(p
< pend
&& *p
!= ',' && !isspace(*p
)) p
++;
426 ByteArray_writeInt(a
->code
, value
);
427 ByteArray_writeInt(a
->code
, dst
);
434 /* immediate can be function name, string,
435 * variable name, stack fixup, or numeric value */
437 value
= get_string_offset(a
, add_or_get_string(a
, sym
));
438 add_fixup(a
, FIXUP_STRING
, pos
);
439 } else if(sym
[0] == '@') {
440 value
= get_variable_offset(a
, sym
+1);
441 add_fixup(a
, FIXUP_GLOBALDATA
, pos
);
442 } else if(sym
[0] == '.') {
443 if(memcmp(sym
+1, "stack", 5)) {
444 dprintf(2, "error: expected stack\n");
448 while(isspace(*sym
) && sym
< pend
) sym
++;
449 assert(sym
< pend
&& *sym
== '+');
451 while(isspace(*sym
) && sym
< pend
) sym
++;
452 add_fixup(a
, FIXUP_STACK
, pos
);
454 } else if(isdigit(sym
[0]) || sym
[0] == '-') {
455 if(sym
[0] == '-') assert(isdigit(sym
[1]));
458 add_function_ref(a
, sym
, pos
);
460 case SCMD_JMP
: case SCMD_JZ
: case SCMD_JNZ
:
461 add_label_ref(a
, sym
, pos
);
467 ByteArray_writeInt(a
->code
, value
);
474 for(i
= 0; i
< List_size(a
->label_ref_list
); i
++) {
475 assert(List_get(a
->label_ref_list
, i
, &item
));
476 ByteArray_set_position(a
->code
, item
.insno
* 4);
477 int lbl
= get_label_offset(a
, item
.name
);
478 assert(lbl
>= 0 && lbl
< pos
);
479 int label_insno
= lbl
- (item
.insno
+1); /* offset is calculated from next instruction */
480 ByteArray_writeInt(a
->code
, label_insno
);
482 generate_import_table(a
);
483 for(i
= 0; i
< List_size(a
->function_ref_list
); i
++) {
484 assert(List_get(a
->function_ref_list
, i
, &item
));
485 ssize_t imp
= get_import_index(a
, item
.name
, strlen(item
.name
));
488 assert(find_export(a
, EXPORT_FUNCTION
, item
.name
, &off
));
490 add_fixup(a
, FIXUP_FUNCTION
, item
.insno
);
492 add_fixup(a
, FIXUP_IMPORT
, item
.insno
);
495 ByteArray_set_position(a
->code
, item
.insno
* 4);
496 ByteArray_writeInt(a
->code
, imp
);
501 #include "endianness.h"
502 static void write_int(FILE* o
, int val
) {
503 #ifndef IS_LITTLE_ENDIAN
504 val
= byteswap32(val
);
506 fwrite(&val
, 4, 1, o
);
509 static int fixup_comparefunc(const void *a
, const void* b
) {
510 const struct fixup
* fa
= a
, *fb
= b
;
511 if(fa
->type
== FIXUP_DATADATA
&& fb
->type
!= FIXUP_DATADATA
)
513 if(fb
->type
== FIXUP_DATADATA
&& fa
->type
!= FIXUP_DATADATA
)
515 if(fa
->offset
< fb
->offset
) return -1;
516 if(fa
->offset
== fb
->offset
) return 0;
520 static void sort_fixup_list(AS
* a
) {
521 List_sort(a
->fixup_list
, fixup_comparefunc
);
524 static void write_fixup_list(AS
* a
, FILE *o
) {
527 for(i
= 0; i
< List_size(a
->fixup_list
); i
++) {
528 assert(List_get(a
->fixup_list
, i
, &item
));
529 char type
= item
.type
;
530 fwrite(&type
, 1, 1, o
);
532 for(i
= 0; i
< List_size(a
->fixup_list
); i
++) {
533 assert(List_get(a
->fixup_list
, i
, &item
));
534 write_int(o
, item
.offset
);
538 static void write_string_section(AS
* a
, FILE* o
) {
541 for(; i
< List_size(a
->string_list
); i
++) {
542 assert(List_get(a
->string_list
, i
, &item
));
543 fwrite(item
.ptr
, item
.len
+ 1, 1, o
);
547 static void write_import_section(AS
* a
, FILE* o
) {
550 for(; i
< List_size(a
->import_list
); i
++) {
551 assert(List_get(a
->import_list
, i
, &item
));
552 fwrite(item
.ptr
, item
.len
+ 1, 1, o
);
556 static void write_export_section(AS
* a
, FILE* o
) {
559 for(; i
< List_size(a
->export_list
); i
++) {
560 assert(List_get(a
->export_list
, i
, &item
));
561 fwrite(item
.fn
, strlen(item
.fn
) + 1, 1, o
);
562 unsigned encoded
= (item
.type
<< 24) | (item
.instr
&0x00FFFFFF);
563 write_int(o
, encoded
);
567 static void write_sections_section(AS
* a
, FILE *o
) {
571 static int write_object(AS
*a
, char *out
) {
573 if(!(o
= fopen(out
, "w"))) return 0;
575 write_int(o
, 83); //version
576 write_int(o
, ByteArray_get_length(a
->data
)); // globaldatasize
577 write_int(o
, ByteArray_get_length(a
->code
) / 4); // codesize
578 write_int(o
, get_string_section_length(a
)); // stringssize
579 size_t l
= ByteArray_get_length(a
->data
);
582 p
= mem_getptr(&a
->data
->source
.mem
, 0, l
); // FIXME dont access directly, use some getter method
584 fwrite(p
,l
,1,o
); // globaldata
586 l
= ByteArray_get_length(a
->code
);
588 p
= mem_getptr(&a
->code
->source
.mem
, 0, l
);
590 fwrite(p
,l
,1,o
); // code
592 write_string_section(a
, o
);
593 write_int(o
, List_size(a
->fixup_list
));
595 write_fixup_list(a
, o
);
596 if(!List_size(a
->import_list
)) {
597 /* AGS declares object files with 0 imports as invalid */
600 write_int(o
, List_size(a
->import_list
));
601 write_import_section(a
, o
);
602 write_int(o
, List_size(a
->export_list
));
603 write_export_section(a
, o
);
604 write_int(o
, 0); // FIXME sectioncount
605 write_sections_section(a
, o
);
606 write_int(o
, 0xbeefcafe); // magic end marker.
611 int AS_assemble(AS
* a
, char* out
) {
612 if(!asm_data(a
)) return 0;
613 if(!asm_text(a
)) return 0;
614 // if(!asm_strings(a)) return 0; // emitting unneeded strings is not necessary
615 if(!write_object(a
, out
)) return 0;
619 static int strptrcmp(const void *a
, const void *b
) {
620 const char * const *x
= a
;
621 const char * const *y
= b
;
622 return strcmp(*x
, *y
);
624 static unsigned string_hash(const char* s
) {
630 return h
& 0xfffffff;
633 void AS_open_stream(AS
* a
, FILE* f
) {
634 memset(a
, 0, sizeof *a
);
636 a
->data
= &a
->data_b
;
637 a
->code
= &a
->code_b
;
638 ByteArray_ctor(a
->obj
);
639 ByteArray_open_mem(a
->obj
, 0, 0);
640 ByteArray_ctor(a
->data
);
641 ByteArray_set_endian(a
->data
, BAE_LITTLE
);
642 ByteArray_set_flags(a
->data
, BAF_CANGROW
);
643 ByteArray_open_mem(a
->data
, 0, 0);
644 ByteArray_ctor(a
->code
);
645 ByteArray_set_endian(a
->code
, BAE_LITTLE
);
646 ByteArray_set_flags(a
->code
, BAF_CANGROW
);
647 ByteArray_open_mem(a
->code
, 0, 0);
649 a
->export_list
= &a
->export_list_b
;
650 a
->fixup_list
= &a
->fixup_list_b
;
651 a
->string_list
= &a
->string_list_b
;
652 a
->label_ref_list
= &a
->label_ref_list_b
;
653 a
->function_ref_list
= &a
->function_ref_list_b
;
654 a
->variable_list
= &a
->variable_list_b
;
655 a
->import_list
= &a
->import_list_b
;
656 a
->label_map
= (void*) &a
->label_map_b
;
658 List_init(a
->export_list
, sizeof(struct export
));
659 List_init(a
->fixup_list
, sizeof(struct fixup
));
660 List_init(a
->string_list
, sizeof(struct string
));
661 List_init(a
->label_ref_list
, sizeof(struct label
));
662 List_init(a
->function_ref_list
, sizeof(struct label
));
663 List_init(a
->variable_list
, sizeof(struct variable
));
664 List_init(a
->import_list
, sizeof(struct string
));
665 hbmap_init(a
->label_map
, strptrcmp
, string_hash
);
670 int AS_open(AS
* a
, char* fn
) {
671 FILE *f
= fopen(fn
, "r");
673 AS_open_stream(a
, f
);
678 void AS_close(AS
* a
) {