| /* |
| Copyright 2015-2023 Rivoreo |
| |
| This Source Code Form is subject to the terms of the Mozilla Public |
| License, v. 2.0. If a copy of the MPL was not distributed with this |
| file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| */ |
| |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <assert.h> |
| |
| #define LABEL_STACK_SIZE 1024 |
| |
| #ifndef DEFAULT_TARGET_BITS |
| #ifdef __powerpc64__ |
| #define DEFAULT_TARGET_BITS 64 |
| #else |
| #define DEFAULT_TARGET_BITS 32 |
| #endif |
| #endif |
| |
| #if DEFAULT_TARGET_BITS == 64 |
| static int for_64bit = 1; |
| #else |
| static int for_64bit = 0; |
| #endif |
| static size_t buffer_size = 65536; |
| |
| static int parse_extended_option(const char *o) { |
| if(strcmp(o, "32") == 0) for_64bit = 0; |
| else if(strcmp(o, "64") == 0) for_64bit = 1; |
| else { |
| // TODO: print error message |
| return -1; |
| } |
| return 0; |
| } |
| |
| static FILE *stdfopen(const char *path, const char *mode, const char **nice_name) { |
| if(*path == '-') { |
| const char *fdn = path + 1; |
| if(!*fdn) { |
| if(nice_name) *nice_name = *mode == 'r' ? "<stdin>" : "<stdout>"; |
| return *mode == 'r' ? stdin : stdout; |
| } |
| if(isdigit(*fdn)) { |
| char *end_p; |
| int fd = strtol(fdn, &end_p, 10); |
| if(!*end_p && fd >= 0) { |
| if(nice_name) switch(fd) { |
| case STDIN_FILENO: |
| *nice_name = "<stdin>"; |
| break; |
| case STDOUT_FILENO: |
| *nice_name = "<stdout>"; |
| break; |
| case STDERR_FILENO: |
| *nice_name = "<stderr>"; |
| break; |
| default: |
| *nice_name = "<fd>"; |
| break; |
| } |
| return fdopen(fd, mode); |
| } |
| } |
| } |
| if(nice_name) *nice_name = path; |
| return fopen(path, mode); |
| } |
| |
| static void print_buffer_code(FILE *f) { |
| fprintf(f, " .lcomm buffer, %zu\n", buffer_size); |
| } |
| |
| static void print_startup_code(FILE *f) { |
| fputs(for_64bit ? |
| " .align 2\n" |
| " .section .opd, \"aw\"\n" |
| " .global _start\n" |
| "_start:\n" |
| " .quad start, .TOC.@tocbase, 0\n" |
| " .previous\n" |
| "start:\n" |
| " addis %r14, %r2, buffer@toc@ha\n" |
| " la %r14, buffer@toc@l(%r14)\n" |
| " addis %r4, %r2, fail@toc@ha\n" |
| " la %r4, fail@toc@l(%r4)\n" |
| " mtlr %r4\n" |
| : |
| " .align 2\n" |
| " .global _start\n" |
| "_start:\n" |
| " lis %r14, buffer@ha\n" |
| " la %r14, buffer@l(%r14)\n" |
| " lis %r4, fail@ha\n" |
| " la %r4, fail@l(%r4)\n" |
| " mtlr %r4\n", |
| f |
| ); |
| } |
| |
| static void add_ptr(FILE *f, int offset) { |
| fprintf(f, " addi %%r14, %%r14, %d\n", offset); |
| } |
| |
| static void add_data(FILE *f, int offset) { |
| fprintf(f, |
| " lbz %%r10, 0(%%r14)\n" |
| " addi %%r10, %%r10, %d\n" |
| " stb %%r10, 0(%%r14)\n", |
| offset); |
| } |
| |
| static void read_data(FILE *f) { |
| fputs( |
| " li %r5, 1\n" |
| " mr %r4, %r14\n" |
| " li %r3, 0\n" |
| " li %r0, 3\n" |
| " sc\n" |
| " bsolr\n", |
| f |
| ); |
| } |
| |
| static void write_data(FILE *f) { |
| fputs( |
| " li %r5, 1\n" |
| " mr %r4, %r14\n" |
| " li %r3, 1\n" |
| " li %r0, 4\n" |
| " sc\n" |
| " bsolr\n", |
| f |
| ); |
| } |
| |
| static void check_ptr_state(FILE *f, int *state) { |
| if(!*state) return; |
| add_ptr(f, *state); |
| *state = 0; |
| } |
| |
| static int check_data_state(FILE *f, int *state) { |
| if(!*state) return 0; |
| add_data(f, *state); |
| *state = 0; |
| return 1; |
| } |
| |
| static int compile(const char *input_file_name, FILE *input_file, FILE *output_file, int debug, int optimize_level) { |
| int ptr_state = 0; |
| int data_state = 0; |
| int label_stack[LABEL_STACK_SIZE]; |
| int label_stack_i = -1; |
| int label_i = 0; |
| int data_available = 0; |
| int nlines = 1; |
| int c; |
| if(debug) { |
| fprintf(output_file, |
| " .file 1 \"%s\"\n" |
| " .loc 1 1\n", |
| input_file_name); |
| } |
| while((c = fgetc(input_file)) != EOF) switch(c) { |
| int data_cached; |
| case '<': |
| if(optimize_level == '0') { |
| fputs(" subi %r14, %r14, 1\n", output_file); |
| } else { |
| if(check_data_state(output_file, &data_state)) data_available = 1; |
| ptr_state--; |
| } |
| break; |
| case '>': |
| if(optimize_level == '0') { |
| fputs(" addi %r14, %r14, 1\n", output_file); |
| } else { |
| if(check_data_state(output_file, &data_state)) data_available = 1; |
| ptr_state++; |
| } |
| break; |
| case '-': |
| case '+': |
| if(optimize_level == '0') { |
| add_data(output_file, c == '-' ? -1 : 1); |
| //data_available = 1; |
| } else { |
| check_ptr_state(output_file, &ptr_state); |
| data_state += c == '-' ? -1 : 1; |
| } |
| break; |
| case ',': |
| case '.': |
| if(optimize_level != '0') { |
| check_ptr_state(output_file, &ptr_state); |
| if(check_data_state(output_file, &data_state)) data_available = 1; |
| } |
| (c == ',' ? read_data : write_data)(output_file); |
| if(optimize_level != '0' && c == ',') data_available = 1; |
| break; |
| case '[': |
| if(optimize_level == '0') { |
| data_cached = 0; |
| } else { |
| check_ptr_state(output_file, &ptr_state); |
| data_cached = check_data_state(output_file, &data_state); |
| if(data_cached) data_available = 1; |
| else if(!data_available) { |
| int new_line_found = 0; |
| while((c = fgetc(input_file)) != EOF && c != ']') { |
| if(c != '\n') continue; |
| nlines++; |
| new_line_found = 1; |
| } |
| if(c == ']' && debug && new_line_found) { |
| fprintf(output_file, " .loc 1 %d\n", nlines); |
| } |
| break; |
| } |
| } |
| fprintf(output_file, |
| " %s\n" |
| " cmpwi %%cr7, %%r10, 0\n" |
| " beq %%cr7, .l%d_end\n" |
| ".l%d_begin:\n", |
| data_cached ? "rlwinm %r10, %r10, 0, 0xff" : "lbz %r10, 0(%r14)", |
| label_i, label_i); |
| label_stack[++label_stack_i] = label_i++; |
| break; |
| case ']': |
| if(label_stack_i < 0) { |
| fprintf(stderr, "%s:%d: error: no '[' to match ']'\n", input_file_name, nlines); |
| return -1; |
| } |
| if(optimize_level == '0') { |
| data_cached = 0; |
| } else { |
| assert(data_available); |
| check_ptr_state(output_file, &ptr_state); |
| data_cached = check_data_state(output_file, &data_state); |
| } |
| int i = label_stack[label_stack_i]; |
| fprintf(output_file, |
| " %s\n" |
| " cmpwi %%cr7, %%r10, 0\n" |
| " bne %%cr7, .l%d_begin\n" |
| ".l%d_end:\n", |
| data_cached ? "rlwinm %r10, %r10, 0, 0xff" : "lbz %r10, 0(%r14)", |
| i, i); |
| label_stack_i--; |
| break; |
| case '\n': |
| nlines++; |
| if(debug) { |
| if(optimize_level == 'g') { |
| check_ptr_state(output_file, &ptr_state); |
| check_data_state(output_file, &data_state); |
| } |
| fprintf(output_file, " .loc 1 %d\n", nlines); |
| } |
| break; |
| } |
| if(label_stack_i >= 0) { |
| fprintf(stderr, "%s: error: no ']' to match %d previous '['\n", input_file_name, label_stack_i + 1); |
| return -1; |
| } |
| /* Ignoring unfinished states, they are being eliminated as a part of |
| * optimization. |
| */ |
| return 0; |
| } |
| |
| static void print_exit_code(FILE *f) { |
| fputs( |
| " li %r3, 0\n" |
| "exit:\n" |
| " li %r0, 1\n" |
| " sc\n" |
| "fail:\n" |
| " li %r3, 1\n" |
| " b exit\n", |
| f |
| ); |
| } |
| |
| int main(int argc, char **argv) { |
| int debugging = 0; |
| int optimize_level = '0'; |
| const char *output_path = NULL; |
| int verbose = 0; |
| const char *input_file_name; |
| FILE *input_file = NULL; |
| int end_of_options = 0; |
| int i = 1; |
| while(i < argc) { |
| if(!end_of_options && argv[i][0] == '-') { |
| const char *o = argv[i] + 1; |
| if(!*o) goto not_an_option; |
| if(*o == '-') { |
| if(o[1]) { |
| fprintf(stderr, "%s: Invalid option '%s'\n", argv[0], argv[i]); |
| return -1; |
| } |
| end_of_options = 1; |
| } else switch(*o) { |
| case '0' ... '9': |
| goto not_an_option; |
| case 'g': |
| debugging = 1; |
| break; |
| case 'O': |
| if(o[1]) optimize_level = o[1]; |
| else if(++i >= argc) { |
| fprintf(stderr, "%s: Option '-O' requires an argument\n", argv[0]); |
| return -1; |
| } else optimize_level = *argv[i]; |
| break; |
| case 'q': |
| if(++i >= argc) { |
| fprintf(stderr, "%s: Option '-q' requires an argument\n", argv[0]); |
| return -1; |
| } |
| if(parse_extended_option(argv[i]) < 0) return -1; |
| break; |
| case 'o': |
| if(++i >= argc) { |
| fprintf(stderr, "%s: Option '-o' requires an argument\n", argv[0]); |
| return -1; |
| } |
| output_path = argv[i]; |
| break; |
| case 'v': |
| verbose = 1; |
| break; |
| default: |
| fprintf(stderr, "%s: Invalid option '-%c'\n", argv[0], *o); |
| return -1; |
| } |
| } else { |
| not_an_option: |
| if(input_file) { |
| fprintf(stderr, "%s: Only 1 input file can be specified\n", argv[0]); |
| return -1; |
| } |
| input_file = stdfopen(argv[i], "r", &input_file_name); |
| if(!input_file) { |
| perror(argv[i]); |
| return 1; |
| } |
| } |
| i++; |
| } |
| |
| if(!input_file) { |
| input_file_name = "<stdin>"; |
| input_file = stdin; |
| } |
| |
| FILE *output_file; |
| if(output_path) { |
| output_file = stdfopen(output_path, "w", NULL); |
| if(!output_file) { |
| perror(output_path); |
| return 1; |
| } |
| } else { |
| output_file = stdout; |
| } |
| |
| fputs(" .text\n", output_file); |
| print_buffer_code(output_file); |
| print_startup_code(output_file); |
| if(compile(input_file_name, input_file, output_file, debugging, optimize_level) < 0) return 1; |
| print_exit_code(output_file); |
| fclose(output_file); |
| return 0; |
| } |