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