1#include "asm.h"
2
3#include "vm.h"
4
5#include <errno.h>
6#include <stdarg.h>
7#include <stdbool.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12enum {
13 ASM_IMMEDIATE_SIZE = 4,
14 ASM_LOCAL_COUNT = 16
15};
16
17typedef struct {
18 const char *start;
19 size_t length;
20} Token;
21
22typedef struct {
23 const char *current;
24 const char *end;
25} LineParser;
26
27typedef struct {
28 const char *name;
29 uint8_t opcode;
30 enum {
31 OPERAND_NONE,
32 OPERAND_INTEGER,
33 OPERAND_TARGET,
34 OPERAND_LOCAL
35 } operand_kind;
36} Instruction;
37
38typedef struct {
39 char *name;
40 size_t length;
41 size_t offset;
42 size_t line;
43} Label;
44
45typedef struct {
46 Label *items;
47 size_t count;
48 size_t capacity;
49} LabelTable;
50
51
52static const Instruction INSTRUCTIONS[] = {
53 {"PUSH", VM_OP_PUSH, OPERAND_INTEGER},
54 {"POP", VM_OP_POP, OPERAND_NONE},
55 {"DUP", VM_OP_DUP, OPERAND_NONE},
56 {"SWAP", VM_OP_SWAP, OPERAND_NONE},
57 {"ADD", VM_OP_ADD, OPERAND_NONE},
58 {"SUB", VM_OP_SUB, OPERAND_NONE},
59 {"MUL", VM_OP_MUL, OPERAND_NONE},
60 {"DIV", VM_OP_DIV, OPERAND_NONE},
61 {"MOD", VM_OP_MOD, OPERAND_NONE},
62 {"NEG", VM_OP_NEG, OPERAND_NONE},
63 {"EQ", VM_OP_EQ, OPERAND_NONE},
64 {"LT", VM_OP_LT, OPERAND_NONE},
65 {"GT", VM_OP_GT, OPERAND_NONE},
66 {"JMP", VM_OP_JMP, OPERAND_TARGET},
67 {"JZ", VM_OP_JZ, OPERAND_TARGET},
68 {"JNZ", VM_OP_JNZ, OPERAND_TARGET},
69 {"CALL", VM_OP_CALL, OPERAND_TARGET},
70 {"RET", VM_OP_RET, OPERAND_NONE},
71 {"LOAD", VM_OP_LOAD, OPERAND_LOCAL},
72 {"STORE", VM_OP_STORE, OPERAND_LOCAL},
73 {"PRINT", VM_OP_PRINT, OPERAND_NONE},
74 {"HALT", VM_OP_HALT, OPERAND_NONE},
75};
76
77static void set_error(char *err, size_t err_cap, const char *format, ...) {
78 va_list args;
79
80 if (err == NULL || err_cap == 0) {
81 return;
82 }
83
84 va_start(args, format);
85 (void)vsnprintf(err, err_cap, format, args);
86 va_end(args);
87}
88
89static void reset_outputs(uint8_t **code_out, size_t *length_out) {
90 if (code_out != NULL) {
91 *code_out = NULL;
92 }
93 if (length_out != NULL) {
94 *length_out = 0;
95 }
96}
97
98static int validate_outputs(uint8_t **code_out, size_t *length_out,
99 char *err, size_t err_cap) {
100 reset_outputs(code_out, length_out);
101 if (code_out == NULL || length_out == NULL) {
102 set_error(err, err_cap,
103 "assembler requires non-NULL code_out and length_out");
104 return -1;
105 }
106 return 0;
107}
108
109static unsigned char ascii_lower(unsigned char value) {
110 if (value >= (unsigned char)'A' && value <= (unsigned char)'Z') {
111 return (unsigned char)(value + ((unsigned char)'a' - (unsigned char)'A'));
112 }
113 return value;
114}
115
116static int token_equals_ci(Token token, const char *text) {
117 size_t index;
118
119 for (index = 0; text[index] != '\0'; ++index) {
120 if (index >= token.length ||
121 ascii_lower((unsigned char)token.start[index]) !=
122 ascii_lower((unsigned char)text[index])) {
123 return 0;
124 }
125 }
126 return index == token.length;
127}
128
129static int is_space(unsigned char value) {
130 return value == (unsigned char)' ' || value == (unsigned char)'\t' ||
131 value == (unsigned char)'\r' || value == (unsigned char)'\v' ||
132 value == (unsigned char)'\f';
133}
134
135static int starts_comment(const char *current, const char *end) {
136 return current < end &&
137 (*current == '#' || *current == ';' ||
138 (*current == '/' && current + 1 < end && current[1] == '/'));
139}
140
141static int next_token(LineParser *parser, Token *token) {
142 const char *start;
143
144 while (parser->current < parser->end &&
145 is_space((unsigned char)*parser->current)) {
146 ++parser->current;
147 }
148 if (parser->current >= parser->end ||
149 starts_comment(parser->current, parser->end)) {
150 return 0;
151 }
152
153 start = parser->current;
154 while (parser->current < parser->end &&
155 !is_space((unsigned char)*parser->current) &&
156 !starts_comment(parser->current, parser->end)) {
157 ++parser->current;
158 }
159
160 token->start = start;
161 token->length = (size_t)(parser->current - start);
162 return 1;
163}
164
165static int is_label_start(unsigned char value) {
166 return (value >= (unsigned char)'A' && value <= (unsigned char)'Z') ||
167 (value >= (unsigned char)'a' && value <= (unsigned char)'z') ||
168 value == (unsigned char)'_';
169}
170
171static int is_label_continue(unsigned char value) {
172 return is_label_start(value) ||
173 (value >= (unsigned char)'0' && value <= (unsigned char)'9');
174}
175
176static int valid_label_name(Token token) {
177 size_t index;
178
179 if (token.length == 0 || !is_label_start((unsigned char)token.start[0])) {
180 return 0;
181 }
182 for (index = 1; index < token.length; ++index) {
183 if (!is_label_continue((unsigned char)token.start[index])) {
184 return 0;
185 }
186 }
187 return 1;
188}
189
190static int token_is_label_definition(Token token) {
191 return token.length > 0 && token.start[token.length - 1] == ':';
192}
193
194static int token_contains_colon(Token token) {
195 size_t index;
196
197 for (index = 0; index < token.length; ++index) {
198 if (token.start[index] == ':') {
199 return 1;
200 }
201 }
202 return 0;
203}
204
205static const Instruction *find_instruction(Token mnemonic) {
206 size_t index;
207
208 for (index = 0; index < sizeof(INSTRUCTIONS) / sizeof(INSTRUCTIONS[0]);
209 ++index) {
210 if (token_equals_ci(mnemonic, INSTRUCTIONS[index].name)) {
211 return &INSTRUCTIONS[index];
212 }
213 }
214 return NULL;
215}
216
217static size_t instruction_size(const Instruction *instruction) {
218 return instruction->operand_kind == OPERAND_NONE
219 ? 1U
220 : 1U + (size_t)ASM_IMMEDIATE_SIZE;
221}
222
223static int parse_i32(Token token, int32_t *value_out) {
224 size_t index = 0;
225 int negative = 0;
226 uint64_t magnitude = 0;
227 uint64_t limit;
228
229 if (token.length == 0) {
230 return -1;
231 }
232 if (token.start[index] == '+' || token.start[index] == '-') {
233 negative = token.start[index] == '-';
234 ++index;
235 }
236 if (index == token.length) {
237 return -1;
238 }
239
240 limit = negative ? (uint64_t)INT32_MAX + 1U : (uint64_t)INT32_MAX;
241 for (; index < token.length; ++index) {
242 unsigned char digit;
243
244 if (token.start[index] < '0' || token.start[index] > '9') {
245 return -1;
246 }
247 digit = (unsigned char)(token.start[index] - '0');
248 if (magnitude > (limit - digit) / 10U) {
249 return -1;
250 }
251 magnitude = magnitude * 10U + digit;
252 }
253
254 if (negative) {
255 if (magnitude == (uint64_t)INT32_MAX + 1U) {
256 *value_out = INT32_MIN;
257 } else {
258 *value_out = -(int32_t)magnitude;
259 }
260 } else {
261 *value_out = (int32_t)magnitude;
262 }
263 return 0;
264}
265
266static int begins_like_number(Token token) {
267 if (token.length == 0) {
268 return 0;
269 }
270 return (token.start[0] >= '0' && token.start[0] <= '9') ||
271 token.start[0] == '+' || token.start[0] == '-';
272}
273
274static void token_for_error(Token token, char *buffer, size_t buffer_cap) {
275 size_t amount;
276
277 if (buffer_cap == 0) {
278 return;
279 }
280 amount = token.length;
281 if (amount >= buffer_cap) {
282 amount = buffer_cap - 1;
283 }
284 if (amount != 0) {
285 memcpy(buffer, token.start, amount);
286 }
287 buffer[amount] = '\0';
288}
289
290static int get_operand(LineParser *parser, Token *operand, size_t line,
291 char *err, size_t err_cap) {
292 Token extra;
293
294 if (!next_token(parser, operand)) {
295 set_error(err, err_cap, "line %zu: missing instruction operand", line);
296 return -1;
297 }
298 if (next_token(parser, &extra)) {
299 char text[65];
300 token_for_error(extra, text, sizeof(text));
301 set_error(err, err_cap, "line %zu: unexpected token '%s'", line, text);
302 return -1;
303 }
304 return 0;
305}
306
307static int ensure_no_operand(LineParser *parser, size_t line, char *err,
308 size_t err_cap) {
309 Token extra;
310
311 if (next_token(parser, &extra)) {
312 char text[65];
313 token_for_error(extra, text, sizeof(text));
314 set_error(err, err_cap,
315 "line %zu: instruction does not take an operand ('%s')", line,
316 text);
317 return -1;
318 }
319 return 0;
320}
321
322static int validate_operand(const Instruction *instruction, LineParser *parser,
323 size_t line, Token *operand_out, char *err,
324 size_t err_cap) {
325 Token operand;
326 int32_t value;
327
328 if (instruction->operand_kind == OPERAND_NONE) {
329 return ensure_no_operand(parser, line, err, err_cap);
330 }
331 if (get_operand(parser, &operand, line, err, err_cap) != 0) {
332 return -1;
333 }
334
335 switch (instruction->operand_kind) {
336 case OPERAND_INTEGER:
337 if (parse_i32(operand, &value) != 0) {
338 set_error(err, err_cap, "line %zu: expected a signed 32-bit integer",
339 line);
340 return -1;
341 }
342 break;
343 case OPERAND_LOCAL:
344 if (parse_i32(operand, &value) != 0 || value < 0 ||
345 value >= ASM_LOCAL_COUNT) {
346 set_error(err, err_cap,
347 "line %zu: local index must be an integer from 0 to %d",
348 line, ASM_LOCAL_COUNT - 1);
349 return -1;
350 }
351 break;
352 case OPERAND_TARGET:
353 if (parse_i32(operand, &value) != 0) {
354 if (begins_like_number(operand) || !valid_label_name(operand)) {
355 set_error(err, err_cap,
356 "line %zu: expected a signed 32-bit address or label",
357 line);
358 return -1;
359 }
360 }
361 break;
362 case OPERAND_NONE:
363 default:
364
365 set_error(err, err_cap, "line %zu: internal assembler operand error",
366 line);
367 return -1;
368 }
369
370 if (operand_out != NULL) {
371 *operand_out = operand;
372 }
373 return 0;
374}
375
376static void labels_destroy(LabelTable *labels) {
377 size_t index;
378
379 for (index = 0; index < labels->count; ++index) {
380 free(labels->items[index].name);
381 }
382 free(labels->items);
383 labels->items = NULL;
384 labels->count = 0;
385 labels->capacity = 0;
386}
387
388static const Label *labels_find(const LabelTable *labels, Token name) {
389 size_t index;
390
391 for (index = 0; index < labels->count; ++index) {
392 const Label *label = &labels->items[index];
393 if (label->length == name.length &&
394 memcmp(label->name, name.start, name.length) == 0) {
395 return label;
396 }
397 }
398 return NULL;
399}
400
401static int labels_add(LabelTable *labels, Token name, size_t offset,
402 size_t line, char *err, size_t err_cap) {
403 Label *new_items;
404 char *copy;
405 size_t new_capacity;
406
407 if (labels_find(labels, name) != NULL) {
408 char text[65];
409 token_for_error(name, text, sizeof(text));
410 set_error(err, err_cap, "line %zu: duplicate label '%s'", line, text);
411 return -1;
412 }
413 if (name.length == SIZE_MAX) {
414 set_error(err, err_cap, "line %zu: label is too long", line);
415 return -1;
416 }
417 copy = malloc(name.length + 1U);
418 if (copy == NULL) {
419 set_error(err, err_cap, "out of memory while recording labels");
420 return -1;
421 }
422 memcpy(copy, name.start, name.length);
423 copy[name.length] = '\0';
424
425 if (labels->count == labels->capacity) {
426 new_capacity = labels->capacity == 0 ? 16U : labels->capacity * 2U;
427 if (new_capacity < labels->capacity ||
428 new_capacity > SIZE_MAX / sizeof(*labels->items)) {
429 free(copy);
430 set_error(err, err_cap, "too many labels");
431 return -1;
432 }
433 new_items = realloc(labels->items,
434 new_capacity * sizeof(*labels->items));
435 if (new_items == NULL) {
436 free(copy);
437 set_error(err, err_cap, "out of memory while recording labels");
438 return -1;
439 }
440 labels->items = new_items;
441 labels->capacity = new_capacity;
442 }
443
444 labels->items[labels->count].name = copy;
445 labels->items[labels->count].length = name.length;
446 labels->items[labels->count].offset = offset;
447 labels->items[labels->count].line = line;
448 ++labels->count;
449 return 0;
450}
451
452static int consume_labels(LineParser *parser, Token *first_non_label,
453 int *has_instruction, LabelTable *labels,
454 size_t offset, size_t line, char *err,
455 size_t err_cap) {
456 Token token;
457 int has_token = next_token(parser, &token);
458
459 while (has_token && token_is_label_definition(token)) {
460 Token label_name;
461
462 label_name.start = token.start;
463 label_name.length = token.length - 1U;
464 if (!valid_label_name(label_name)) {
465 set_error(err, err_cap, "line %zu: invalid label definition", line);
466 return -1;
467 }
468 if (labels != NULL &&
469 labels_add(labels, label_name, offset, line, err, err_cap) != 0) {
470 return -1;
471 }
472 has_token = next_token(parser, &token);
473 }
474
475 if (!has_token) {
476 *has_instruction = 0;
477 return 0;
478 }
479 if (token_contains_colon(token)) {
480 set_error(err, err_cap, "line %zu: malformed label definition", line);
481 return -1;
482 }
483 *first_non_label = token;
484 *has_instruction = 1;
485 return 0;
486}
487
488static int next_line(const char **cursor, size_t *line_number,
489 const char **start_out, const char **end_out) {
490 const char *start = *cursor;
491 const char *end = start;
492
493 if (*start == '\0') {
494 return 0;
495 }
496 while (*end != '\0' && *end != '\n') {
497 ++end;
498 }
499 *start_out = start;
500 *end_out = end;
501 if (*end == '\n') {
502 *cursor = end + 1;
503 } else {
504 *cursor = end;
505 }
506 ++*line_number;
507 return 1;
508}
509
510static int first_pass(const char *source, LabelTable *labels,
511 size_t *code_length, char *err, size_t err_cap) {
512 const char *cursor = source;
513 const char *line_start;
514 const char *line_end;
515 size_t line = 0;
516 size_t offset = 0;
517
518 while (next_line(&cursor, &line, &line_start, &line_end)) {
519 LineParser parser = {line_start, line_end};
520 Token mnemonic;
521 int has_instruction;
522 const Instruction *instruction;
523 size_t encoded_size;
524
525 if (consume_labels(&parser, &mnemonic, &has_instruction, labels, offset,
526 line, err, err_cap) != 0) {
527 return -1;
528 }
529 if (!has_instruction) {
530 continue;
531 }
532 instruction = find_instruction(mnemonic);
533 if (instruction == NULL) {
534 char text[65];
535 token_for_error(mnemonic, text, sizeof(text));
536 set_error(err, err_cap, "line %zu: unknown instruction '%s'", line,
537 text);
538 return -1;
539 }
540 if (validate_operand(instruction, &parser, line, NULL, err, err_cap) !=
541 0) {
542 return -1;
543 }
544 encoded_size = instruction_size(instruction);
545 if (offset > (size_t)INT32_MAX - encoded_size) {
546 set_error(err, err_cap, "assembled bytecode exceeds 32-bit address space");
547 return -1;
548 }
549 offset += encoded_size;
550 }
551
552 *code_length = offset;
553 return 0;
554}
555
556static void write_i32_le(uint8_t *destination, int32_t value) {
557 uint32_t bits = (uint32_t)value;
558
559 destination[0] = (uint8_t)(bits & UINT32_C(0xff));
560 destination[1] = (uint8_t)((bits >> 8U) & UINT32_C(0xff));
561 destination[2] = (uint8_t)((bits >> 16U) & UINT32_C(0xff));
562 destination[3] = (uint8_t)((bits >> 24U) & UINT32_C(0xff));
563}
564
565static int resolve_operand(const Instruction *instruction, Token operand,
566 const LabelTable *labels, size_t line,
567 int32_t *value_out, char *err, size_t err_cap) {
568 int32_t value;
569
570 if (instruction->operand_kind == OPERAND_INTEGER ||
571 instruction->operand_kind == OPERAND_LOCAL) {
572 if (parse_i32(operand, &value) != 0) {
573 set_error(err, err_cap, "line %zu: internal operand parsing error", line);
574 return -1;
575 }
576 *value_out = value;
577 return 0;
578 }
579
580 if (instruction->operand_kind == OPERAND_TARGET) {
581 const Label *label;
582
583 if (parse_i32(operand, &value) == 0) {
584 *value_out = value;
585 return 0;
586 }
587 label = labels_find(labels, operand);
588 if (label == NULL) {
589 char text[65];
590 token_for_error(operand, text, sizeof(text));
591 set_error(err, err_cap, "line %zu: undefined label '%s'", line, text);
592 return -1;
593 }
594 if (label->offset > (size_t)INT32_MAX) {
595 set_error(err, err_cap, "line %zu: label address is out of range", line);
596 return -1;
597 }
598 *value_out = (int32_t)label->offset;
599 return 0;
600 }
601
602 set_error(err, err_cap, "line %zu: internal assembler operand error", line);
603 return -1;
604}
605
606static int second_pass(const char *source, const LabelTable *labels,
607 uint8_t *code, size_t code_length, char *err,
608 size_t err_cap) {
609 const char *cursor = source;
610 const char *line_start;
611 const char *line_end;
612 size_t line = 0;
613 size_t offset = 0;
614
615 while (next_line(&cursor, &line, &line_start, &line_end)) {
616 LineParser parser = {line_start, line_end};
617 Token mnemonic;
618 Token operand;
619 int has_instruction;
620 const Instruction *instruction;
621
622 if (consume_labels(&parser, &mnemonic, &has_instruction, NULL, offset,
623 line, err, err_cap) != 0) {
624 return -1;
625 }
626 if (!has_instruction) {
627 continue;
628 }
629 instruction = find_instruction(mnemonic);
630 if (instruction == NULL ||
631 validate_operand(instruction, &parser, line, &operand, err, err_cap) !=
632 0) {
633 if (instruction == NULL) {
634 set_error(err, err_cap, "line %zu: internal instruction lookup error",
635 line);
636 }
637 return -1;
638 }
639 if (offset > code_length || instruction_size(instruction) > code_length - offset) {
640 set_error(err, err_cap, "internal assembler size mismatch");
641 return -1;
642 }
643 code[offset++] = instruction->opcode;
644 if (instruction->operand_kind != OPERAND_NONE) {
645 int32_t value;
646
647 if (resolve_operand(instruction, operand, labels, line, &value, err,
648 err_cap) != 0) {
649 return -1;
650 }
651 write_i32_le(code + offset, value);
652 offset += ASM_IMMEDIATE_SIZE;
653 }
654 }
655
656 if (offset != code_length) {
657 set_error(err, err_cap, "internal assembler size mismatch");
658 return -1;
659 }
660 return 0;
661}
662
663static int assemble_source(const char *source, uint8_t **code_out,
664 size_t *length_out, char *err, size_t err_cap) {
665 LabelTable labels = {0};
666 uint8_t *code = NULL;
667 size_t code_length = 0;
668 size_t allocation_size;
669 int result = -1;
670
671 if (first_pass(source, &labels, &code_length, err, err_cap) != 0) {
672 goto done;
673 }
674 allocation_size = code_length == 0 ? 1U : code_length;
675 code = malloc(allocation_size);
676 if (code == NULL) {
677 set_error(err, err_cap, "out of memory while allocating bytecode");
678 goto done;
679 }
680 if (second_pass(source, &labels, code, code_length, err, err_cap) != 0) {
681 goto done;
682 }
683
684 *code_out = code;
685 *length_out = code_length;
686 code = NULL;
687 result = 0;
688
689done:
690 free(code);
691 labels_destroy(&labels);
692 return result;
693}
694
695int asm_assemble(const char *source, uint8_t **code_out, size_t *length_out,
696 char *err, size_t err_cap) {
697 if (validate_outputs(code_out, length_out, err, err_cap) != 0) {
698 return -1;
699 }
700 if (source == NULL) {
701 set_error(err, err_cap, "assembler source must not be NULL");
702 return -1;
703 }
704 return assemble_source(source, code_out, length_out, err, err_cap);
705}
706
707int asm_assemble_file(const char *path, uint8_t **code_out,
708 size_t *length_out, char *err, size_t err_cap) {
709 FILE *file;
710 long file_size;
711 char *source = NULL;
712 size_t source_length;
713 int result;
714
715 if (validate_outputs(code_out, length_out, err, err_cap) != 0) {
716 return -1;
717 }
718 if (path == NULL) {
719 set_error(err, err_cap, "assembler file path must not be NULL");
720 return -1;
721 }
722 file = fopen(path, "rb");
723 if (file == NULL) {
724 set_error(err, err_cap, "cannot open '%s': %s", path, strerror(errno));
725 return -1;
726 }
727 if (fseek(file, 0L, SEEK_END) != 0 || (file_size = ftell(file)) < 0 ||
728 fseek(file, 0L, SEEK_SET) != 0) {
729 int saved_errno = errno;
730 (void)fclose(file);
731 set_error(err, err_cap, "cannot read '%s': %s", path,
732 strerror(saved_errno));
733 return -1;
734 }
735 if ((uintmax_t)file_size > (uintmax_t)SIZE_MAX - 1U) {
736 (void)fclose(file);
737 set_error(err, err_cap, "source file '%s' is too large", path);
738 return -1;
739 }
740 source_length = (size_t)file_size;
741 source = malloc(source_length + 1U);
742 if (source == NULL) {
743 (void)fclose(file);
744 set_error(err, err_cap, "out of memory while reading '%s'", path);
745 return -1;
746 }
747 if (source_length != 0 && fread(source, 1U, source_length, file) != source_length) {
748 int saved_errno = ferror(file) ? errno : 0;
749 free(source);
750 (void)fclose(file);
751 set_error(err, err_cap, "cannot read '%s'%s%s", path,
752 saved_errno != 0 ? ": " : "",
753 saved_errno != 0 ? strerror(saved_errno) : "");
754 return -1;
755 }
756 if (fclose(file) != 0) {
757 int saved_errno = errno;
758 free(source);
759 set_error(err, err_cap, "cannot close '%s': %s", path,
760 strerror(saved_errno));
761 return -1;
762 }
763 source[source_length] = '\0';
764 result = assemble_source(source, code_out, length_out, err, err_cap);
765 free(source);
766 return result;
767}
768
769void asm_free(uint8_t *code) {
770 free(code);
771}
772
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.