blob: 90e22dc63368c204cf92d7c8b510213cedaf2c8d [file] [log] [blame] [raw]
/*
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
static int optimize_level = '0';
#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 set_optimize_level(const char *s) {
if(isdigit(*s)) {
char *end_p;
unsigned long int n = strtoul(s, &end_p, 10);
if(!*end_p) {
if(n > 3) {
fprintf(stderr, "warning: optimize level '%lu' too high\n", n);
n = 3;
}
optimize_level = n + '0';
return 0;
}
}
if(!s[0] || s[1]) {
fprintf(stderr, "error: invalid optimize level '%s'\n", s);
return -1;
}
optimize_level = *s;
switch(optimize_level) {
case '0' ... '9':
case 's':
case 'g':
return 0;
default:
fprintf(stderr, "error: invalid optimize level '%s'\n", s);
return -1;
}
}
static int parse_extended_option(const char *o) {
if(strncmp(o, "optimize=", 9) == 0) {
if(set_optimize_level(o + 9) < 0) return -1;
} else if(strcmp(o, "compact") == 0) optimize_level = 's';
else 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(" .align 2\n", f);
if(optimize_level == 's') {
fputs(
"read_byte:\n"
" li %r5, 1\n"
" mr %r4, %r14\n"
" li %r3, 0\n"
" li %r0, 3\n"
" sc\n"
" bso fail\n"
" blr\n"
"write_byte:\n"
" li %r5, 1\n"
" mr %r4, %r14\n"
" li %r3, 1\n"
" li %r0, 4\n"
" sc\n"
" bso fail\n"
" blr\n"
"exit:\n"
" li %r0, 1\n"
" sc\n"
"fail:\n"
" li %r3, 1\n"
" b exit\n",
f
);
}
fputs(for_64bit ?
" .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"
:
" .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",
f
);
if(optimize_level != 's') fputs(" 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(optimize_level == 's' ?
" bl read_byte\n"
:
" 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(optimize_level == 's' ?
" bl write_byte\n"
:
" 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 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 orig_nlines = nlines;
int nest_count = 0;
do switch((c = fgetc(input_file))) {
case EOF:
fprintf(stderr,
"%s:%d: error: no ']' to match this '['\n",
input_file_name, orig_nlines);
return -1;
case '[':
nest_count++;
break;
case '\n':
nlines++;
break;
} while(c != ']' || nest_count--);
if(debug && nlines != orig_nlines) {
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);
if(++label_stack_i >= sizeof label_stack / sizeof(int)) {
fprintf(stderr, "%s:%d: error: too many '[' without ']'\n",
input_file_name, nlines);
return -1;
}
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);
if(check_data_state(output_file, &data_state)) data_available = 1;
}
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(optimize_level == 's' ?
" li %r3, 0\n"
" b exit\n"
:
" 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;
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]) {
if(set_optimize_level(o + 1) < 0) return -1;
} else if(++i >= argc) {
fprintf(stderr, "%s: Option '-O' requires an argument\n", argv[0]);
return -1;
} else if(set_optimize_level(argv[i]) < 0) {
return -1;
}
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) < 0) return 1;
print_exit_code(output_file);
fclose(output_file);
return 0;
}