blob: 6163894fa54caa1877bff53ce392c94ff0e29be3 [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';
static int longbranch = 0;
static int for_little_endian = 0;
#if DEFAULT_TARGET_BITS == 64
static int for_64bit = 1;
#else
static int for_64bit = 0;
#endif
static int cell_size = 1;
static unsigned long int ncells = 65536;
static int pic = 0;
static const char *store_cell_opcode;
static const char *load_cell_opcode;
static const char *compare_cell_opcode;
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;
}
switch(*s) {
case 's':
case 'g':
optimize_level = *s;
return 0;
default:
assert((int)*s < '0' || (int)*s > '9');
fprintf(stderr, "error: invalid optimize level '%s'\n", s);
return -1;
}
}
static int parse_extended_option(const char *o) {
if(strncmp(o, "cellwidth=", 10) == 0) {
switch(o[10]) {
case '8':
if(!o[11]) {
cell_size = 1;
return 0;
}
break;
case '1':
if(o[11] == '6' && !o[12]) {
cell_size = 2;
return 0;
}
break;
case '3':
if(o[11] == '2' && !o[12]) {
cell_size = 4;
return 0;
}
break;
case '6':
if(o[11] == '4' && !o[12]) {
cell_size = 8;
return 0;
}
break;
}
fprintf(stderr, "error: invalid cellwidth '%s'\n", o + 10);
return -1;
}
if(strncmp(o, "ncells=", 7) == 0) {
char *end_p;
ncells = strtoul(o + 7, &end_p, 0);
if(*end_p || !ncells) {
fputs("error: invalid value for ncells\n", stderr);
return -1;
}
} else if(strcmp(o, "nooptimize") == 0) optimize_level = '0';
else if(strncmp(o, "optimize=", 9) == 0) {
if(optimize_level != 's' && set_optimize_level(o + 9) < 0) return -1;
} else if(strcmp(o, "compact") == 0) optimize_level = 's';
else if(strcmp(o, "longbranch") == 0) longbranch = 1;
else if(strcmp(o, "nopic") == 0) pic = 0;
else if(strcmp(o, "pic") == 0) pic = 1;
else if(strcmp(o, "32") == 0) for_64bit = 0;
else if(strcmp(o, "64") == 0) for_64bit = 1;
else {
fprintf(stderr, "error: unrecognized extended option '%s'\n", o);
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_load_ptr_to_r4(FILE *f) {
if(for_little_endian || cell_size == 1) fputs(" mr %r4, %r14\n", f);
else fprintf(f, " la %%r4, %d(%%r14)\n", cell_size - 1);
}
static void print_buffer_code(FILE *f) {
fprintf(f,
" .bss\n"
" .align 2\n",
"buffer:\n"
" .space %lu\n", ncells * cell_size);
}
static void print_startup_code(FILE *f) {
fputs(
" .text\n"
" .align 2\n",
f
);
if(optimize_level == 's') {
fputs(
"read_byte:\n"
" li %r3, 0\n"
" b rw\n"
"write_byte:\n"
" li %r3, 1\n"
"rw:\n"
" li %r5, 1\n",
f
);
print_load_ptr_to_r4(f);
fputs(
" addi %r0, %r3, 3\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"
: (pic ?
" .global _start\n"
"_start:\n"
" bl start\n"
"start:\n"
" mflr %r4\n"
" addis %r14, %r4, buffer-start@ha\n"
" la %r14, buffer-start@l(%r14)\n"
:
" .global _start\n"
"_start:\n"
" lis %r14, buffer@ha\n"
" la %r14, buffer@l(%r14)\n"
),
f
);
if(optimize_level != 's') {
fputs(for_64bit ?
" addis %r4, %r2, fail@toc@ha\n"
" la %r4, fail@toc@l(%r4)\n"
" mtlr %r4\n"
: (pic ?
" addis %r4, %r4, fail-start@ha\n"
" la %r4, fail-start@l(%r4)\n"
" mtlr %r4\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 * cell_size);
}
static void add_data(FILE *f, int offset) {
fprintf(f,
" %s %%r10, 0(%%r14)\n"
" addi %%r10, %%r10, %d\n"
" %s %%r10, 0(%%r14)\n",
load_cell_opcode, offset, store_cell_opcode);
}
static void read_data(FILE *f) {
if(optimize_level == 's') fputs(" bl read_byte\n", f);
else {
print_load_ptr_to_r4(f);
fputs(
" li %r5, 1\n"
" li %r3, 0\n"
" li %r0, 3\n"
" sc\n"
" bsolr-\n",
f
);
}
}
static void write_data(FILE *f) {
if(optimize_level == 's') fputs(" bl write_byte\n", f);
else {
print_load_ptr_to_r4(f);
fputs(
" li %r5, 1\n"
" li %r3, 1\n"
" li %r0, 4\n"
" sc\n"
" bsolr-\n",
f
);
}
}
static void wrap_or_load_data_then_compare(FILE *f, int data_cached) {
if(data_cached) {
if(cell_size < 4 || (cell_size == 4 && for_64bit)) {
fprintf(f, " rlwinm %%r10, %%r10, 0, 0x%s\n",
cell_size == 1 ? "ff" : (cell_size == 2 ? "ffff" : "ffffffff"));
}
} else {
fprintf(f, " %s %%r10, 0(%%r14)\n", load_cell_opcode);
}
fprintf(f, " %s %%cr7, %%r10, 0\n", compare_cell_opcode);
}
static int begin_loop(FILE *f, int data_cached, int *label_i, int *label_stack, int *label_stack_i, size_t label_stack_size, const char *input_file_name, int nlines) {
wrap_or_load_data_then_compare(f, data_cached);
if(longbranch) {
fprintf(f,
" bne %%cr7, .l%d_begin\n"
" b .l%d_end\n"
".l%d_begin:\n", *label_i, *label_i, *label_i);
} else {
fprintf(f,
" beq %%cr7, .l%d_end\n"
".l%d_begin:\n", *label_i, *label_i);
}
if(++(*label_stack_i) >= label_stack_size / sizeof(int)) {
fprintf(stderr, "%s:%d: error: too many '[' without ']'\n",
input_file_name, nlines);
return -1;
}
label_stack[*label_stack_i] = (*label_i)++;
return 0;
}
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(cell_size == 1) *state = (signed char)*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 zero_cell_state = 0;
int last_loop_begin_nlines = 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') {
fprintf(output_file, " subi %%r14, %%r14, %d\n", cell_size);
} else {
if(zero_cell_state) {
if(begin_loop(output_file, zero_cell_state - 1, &label_i, label_stack, &label_stack_i, sizeof label_stack, input_file_name, last_loop_begin_nlines) < 0) {
return -1;
}
zero_cell_state = 0;
}
if(check_data_state(output_file, &data_state)) data_available = 1;
ptr_state--;
}
break;
case '>':
if(optimize_level == '0') {
fprintf(output_file, " addi %%r14, %%r14, %d\n", cell_size);
} else {
if(zero_cell_state) {
if(begin_loop(output_file, zero_cell_state - 1, &label_i, label_stack, &label_stack_i, sizeof label_stack, input_file_name, last_loop_begin_nlines) < 0) {
return -1;
}
zero_cell_state = 0;
}
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') {
if(zero_cell_state) {
if(begin_loop(output_file, zero_cell_state - 1, &label_i, label_stack, &label_stack_i, sizeof label_stack, input_file_name, last_loop_begin_nlines) < 0) {
return -1;
}
zero_cell_state = 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') {
if(begin_loop(output_file, 0, &label_i, label_stack, &label_stack_i, sizeof label_stack, input_file_name, nlines)) {
return -1;
}
} else {
if(zero_cell_state &&
begin_loop(output_file, zero_cell_state - 1, &label_i, label_stack, &label_stack_i,
sizeof label_stack, input_file_name, last_loop_begin_nlines) < 0) {
return -1;
}
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;
}
zero_cell_state = 1 + data_cached;
last_loop_begin_nlines = nlines;
}
break;
case ']':
if(zero_cell_state) {
assert(optimize_level != '0');
assert(!ptr_state);
assert(data_available);
if(data_state) {
fprintf(output_file,
" li %%r10, 0\n"
" %s %%r10, 0(%%r14)\n",
store_cell_opcode);
data_state = 0;
} else {
fprintf(stderr,
"%s:%d: warning: endless loop without I/O\n",
input_file_name, nlines);
check_ptr_state(output_file, &ptr_state);
data_cached = check_data_state(output_file, &data_state);
wrap_or_load_data_then_compare(output_file, data_cached);
fprintf(output_file,
" beq %%cr7, .l%d_end\n"
".l%d_begin:\n"
" b .l%d_begin\n"
".l%d_end:\n",
label_i, label_i, label_i, label_i);
label_i++;
}
zero_cell_state = 0;
break;
}
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);
}
wrap_or_load_data_then_compare(output_file, data_cached);
int i = label_stack[label_stack_i];
if(longbranch) {
fprintf(output_file,
" beq %%cr7, .l%d_end\n"
" b .l%d_begin\n"
".l%d_end:\n", i, i, i);
} else {
fprintf(output_file,
" bne %%cr7, .l%d_begin\n"
".l%d_end:\n", 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(cell_size == 8 && !for_64bit) {
fprintf(stderr, "%s: error: cellwidth=64 requires 64-bit code generation\n", argv[0]);
return -1;
}
switch(cell_size) {
case 1:
store_cell_opcode = "stb";
load_cell_opcode = "lbz";
compare_cell_opcode = "cmpwi";
break;
case 2:
store_cell_opcode = "sth";
load_cell_opcode = "lhz";
compare_cell_opcode = "cmpwi";
break;
case 4:
store_cell_opcode = "stw";
load_cell_opcode = "lwz";
compare_cell_opcode = "cmpwi";
break;
case 8:
store_cell_opcode = "std";
load_cell_opcode = "ld";
compare_cell_opcode = "cmpdi";
break;
}
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;
}
print_buffer_code(output_file);
print_startup_code(output_file);
if(compile(input_file_name, input_file, output_file, debugging) < 0) {
#if 0
if(!output_path || unlink(output_path) < 0) fputs(" .error\n", output_file);
#endif
fclose(output_file);
return 1;
}
print_exit_code(output_file);
fclose(output_file);
return 0;
}