#define _POSIX_C_SOURCE 200809L #include "pu2.out.c" // #include #include #include #include #include #include #include #include typedef char *string; typedef unsigned char byte; typedef byte bool; const bool true = 1; const bool false = 0; string read_file(string path, int *out_size) { FILE *f = fopen(path, "rb"); string buf; int size; if (!f) return NULL; fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); buf = malloc(size + 1); if (!buf) { fclose(f); return NULL; } if (fread(buf, 1, size, f) != size) { free(buf); fclose(f); return NULL; } buf[ size ] = '\0'; fclose(f); if (out_size) *out_size = size; return buf; } string canonicalize(string input) { string buf = strdup(input); if (!buf) return NULL; size_t i; string tok; size_t cap = 16; size_t top = 0; char **stack = malloc(cap * sizeof(char *)); if (!stack) { free(buf); return NULL; } int absolute = (buf[ 0 ] == '/'); char *saveptr; for (tok = strtok_r(buf, "/", &saveptr); tok; tok = strtok_r(NULL, "/", &saveptr)) { if (!strcmp(tok, ".")) continue; else if (!strcmp(tok, "..")) { if (top > 0) top--; } else { if (top == cap) { cap *= 2; char **tmp = realloc(stack, cap * sizeof(char *)); if (!tmp) { free(stack); free(buf); return NULL; } stack = tmp; } stack[ top++ ] = tok; } } size_t outlen = absolute ? 1 : 0; if (top == 0) outlen++; for (i = 0; i < top; i++) outlen += strlen(stack[ i ]) + 1; char *out = malloc(outlen); if (!out) { free(stack); free(buf); return NULL; } char *p = out; if (absolute) *p++ = '/'; for (i = 0; i < top; i++) { size_t n = strlen(stack[ i ]); memcpy(p, stack[ i ], n); p += n; if (i < top - 1) *p++ = '/'; } if (p == out) { *p++ = '/'; } *p = '\0'; free(stack); free(buf); return out; } error_debug(string fail_point, string code, int sz) { int error_pos = (int) (fail_point - code); int i; int line = 1, col = 1; for (i = 0; i < error_pos && i < sz; i++) { if (code[ i ] == '\n') { line++; col = 1; } else { col++; } } int line_start = error_pos; while (line_start > 0 && code[ line_start - 1 ] != '\n') { line_start--; } int line_end = error_pos; while (line_end < sz && code[ line_end ] != '\n' && code[ line_end ] != '\0') { line_end++; } int tmp = line; int lineno_width = 1; while (tmp >= 10) { lineno_width++; tmp /= 10; } fprintf( stderr, "\x1b[1m\x1b[31merror:\x1b[0m \x1b[1massembly failed\x1b[0m " "at \x1b[36mline %d\x1b[0m, \x1b[36mcolumn %d\x1b[0m " "(\x1b[33mposition %d\x1b[0m):\n", line, col, error_pos); fprintf( stderr, " %*d | \x1b[1m%.*s\x1b[0m\n", lineno_width, line, line_end - line_start, code + line_start); /* print caret line aligned under the column */ fprintf(stderr, " %*s | ", lineno_width, ""); for (i = line_start; i < error_pos; i++) { fputc((code[ i ] == '\t') ? '\t' : ' ', stderr); } fprintf(stderr, "\x1b[1m\x1b[31m^\x1b[0m \x1b[90mparsing stopped here\x1b[0m\n"); } long powi(long x, unsigned n) { long p = x, r = 1; while (n > 0) { if (n & 1) r *= p; p *= p, n /= 2; } return p == 0 ? 0 : r; } typedef struct { int pid; int ring_level; int *memory; int mem_size; struct { bool spawn_process : 1; bool make_syscall : 1; } permissions; } emu_process; #define pid_count 512 emu_process processes[ pid_count ]; // maximum process count bool process_occupied[ pid_count ]; struct emu_return { bool time_finished; int e_code; } emu(int time_slice, int pid) { process_occupied[ pid ] = true; emu_process process = processes[ pid ]; int *m = process.memory; while (1) { int remaining_time = pu2(time_slice, m, process.mem_size); int e_code = m[ 0 ]; if (remaining_time <= 0 && time_slice != -1) { // process_occupied[ pid ] = false; return (struct emu_return) { true, e_code }; } if (e_code != 2) { process_occupied[ pid ] = false; return (struct emu_return) { false, e_code }; } // handle hardware interrupts int int_code = m[ 1 ]; // 0: output string if (int_code == 0) { int ptr = m[ 2 ]; int size = m[ 3 ], i = 0; while (size--) putchar(m[ ptr++ ]); } // 1: input character else if (int_code == 1) { m[ 2 ] = getchar(); } // 2: arithmetics unit else if (int_code == 2) { int op = m[ 2 ]; int val1 = m[ 3 ]; int val2 = m[ 4 ]; m[ 2 ] = 0; if (op == 0) { // multiply m[ 3 ] = val1 * val2; } else if (op == 1) { // divide m[ 3 ] = val1 / val2; } else if (op == 2) { // modulo m[ 3 ] = val1 % val2; } else if (op == 3) { // left shift m[ 3 ] = val1 << val2; } else if (op == 4) { // right shift m[ 3 ] = val1 >> val2; } else if (op == 5) { // pow if (val1 == 2) m[ 3 ] = 1 << val2; else { m[ 3 ] = powi(val1, val2); } } else { // unknown op, set m[2] m[ 2 ] = 1; } } // permission features else if (int_code == 3) { int op = m[ 2 ]; if (op == 0) { // spawn process int target_ring = m[ 10 ]; int memory_start = m[ 11 ]; int memory_length = m[ 12 ]; int can_spawn_processes = m[ 13 ]; int can_make_syscalls = m[ 14 ]; int max_time_limit = m[ 15 ]; if (target_ring < process.ring_level) { // error - process cannot spawn a process on a lower ring level m[ 2 ] = 2; goto HAD_ERROR; } if ((can_spawn_processes && !(process.permissions.spawn_process & 0b1)) || (can_make_syscalls && !(process.permissions.make_syscall & 0b1))) { // error - host process cannot elevate permissions of the spawning process m[ 2 ] = 3; goto HAD_ERROR; } int new_pid = 0; for (new_pid = 0; new_pid < pid_count; new_pid++) { if (!process_occupied[ new_pid ]) goto FOUND; } // error - process pool exhausted m[ 2 ] = 2; goto HAD_ERROR; FOUND:; int *new_process_memory = calloc(memory_length, sizeof(int)); process_occupied[ new_pid ] = true; processes[ new_pid ] = (emu_process) { .pid = new_pid, .ring_level = target_ring, .memory = new_process_memory, .mem_size = memory_length, .permissions = { .make_syscall = !!can_make_syscalls, .spawn_process = !!can_spawn_processes } }; // copy memory memcpy(new_process_memory, m + memory_start, memory_length * sizeof(int)); // run process struct emu_return result = emu(max_time_limit, new_pid); // store results m[ 2 ] = 0; m[ 3 ] = new_pid; m[ 4 ] = result.e_code; m[ 5 ] = result.time_finished; } else if (op == 1) { // resume process pid (if it's halted) int process_pid = m[ 10 ]; int max_time_limit = m[ 11 ]; if (!process_occupied[ process_pid ]) { // error - process is not running, start/initialize it first m[ 2 ] = 2; goto HAD_ERROR; } if (processes[ process_pid ].ring_level < processes[ pid ].ring_level) { // error - cannot interact with higher-privileged process m[ 2 ] = 3; goto HAD_ERROR; } // run process struct emu_return result = emu(max_time_limit, process_pid); // store results m[ 2 ] = 0; m[ 3 ] = result.e_code; m[ 4 ] = result.time_finished; } else if (op == 2) { // read memory from another process int process_pid = m[ 10 ]; int memory_address = m[ 11 ]; int read_length = m[ 12 ]; int target_location = m[ 13 ]; if (processes[ process_pid ].ring_level < processes[ pid ].ring_level || processes[ pid ].ring_level > 3) { // error - cannot interact with higher-privileged process m[ 2 ] = 2; goto HAD_ERROR; } // copy information int *memory = processes[ process_pid ].memory; memcpy(m + target_location, memory + memory_address, read_length * sizeof(int)); // done! m[ 2 ] = 0; } else if (op == 3) { // write memory to target process int process_pid = m[ 10 ]; int memory_address = m[ 11 ]; int write_length = m[ 12 ]; int target_location = m[ 13 ]; if (processes[ process_pid ].ring_level < processes[ pid ].ring_level || processes[ pid ].ring_level > 3) { // error - cannot interact with higher-privileged process m[ 2 ] = 2; goto HAD_ERROR; } // write information int *memory = processes[ process_pid ].memory; memcpy(memory + memory_address, m + target_location, write_length * sizeof(int)); // done! m[ 2 ] = 0; } else { // unknown op, set m[2] m[ 2 ] = 1; } } else { // unknown interrupt. what to do? } HAD_ERROR: m[ 0 ] = 0; } process_occupied[ pid ] = false; } skip_space(string *I) { while ((**I == '\n' || isspace(**I)) && (**I != 0)) (*I)++; } // IR parser for pu2's as2 // clang-format off struct { int hlt, imm, lod, str, swp, add, sub, and, not, acc, a2u, u2a, b2u, u2b, nop, cb1, cb2, cb3, cb4; } opcode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }; // clang-format on // use this instead of strdup'ing and allocating extra storage! typedef struct { int len; string ref; } slice_t; typedef struct { slice_t key; slice_t value; } entry_t; typedef struct { slice_t name; int location; } label_t; typedef struct { entry_t data; byte arg_count; } macro_t; typedef struct { slice_t name; int *replace_location; // where to put the resolved address string init_I; } label_delegated_t; clean_slice_ref(slice_t *val) { free(val->ref); } #define static_len(G) (sizeof(G) / sizeof((G)[ 0 ])) #define run_err(result, ...) \ ({ \ ERR_code = init_I; \ fprintf(stderr, "\n"); \ error_debug((result), ERR_code, strlen(ERR_code)); \ fprintf(stderr, "\n\x1b[1m\x1b[31mir2 error:\x1b[0m "); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n\n"); \ exit(1); \ }) #define checkskip(char, ...) \ ({ \ if (!*I) { run_err(I, __VA_ARGS__); } \ if (*I++ != (char) ) { run_err(I - 1, __VA_ARGS__); } \ }) #define scanword() \ ({ \ slice_t VAR = { 0, I }; \ while (isalnum(*I) || *I == '_') VAR.len++, I++; \ VAR; \ }) #define memslice_t slice_t __attribute__((cleanup(clean_slice_ref))) #define readd() ((used == new.len) ? (new.ref = realloc(new.ref, new.len *= 2)) : 0) #define scanvalue() \ ({ /* value scanner! numbers, characters, strings, variable derefs. */ \ int value; \ if (isdigit(*I)) { \ value = strtol(I, &I, 10); \ } else if (*I == '\'') { \ I++; \ value = *I++; \ checkskip('\'', "couldn't find closing quote on character"); \ } else if (*I == '"') { \ string str_start = ++I; \ bool T = false; \ int str_len = 0; \ while (*I != '"' || T) { \ if (*I == '\\') { \ T = !T; \ if (!T && I[ 1 ] == '"') str_len--; \ } \ str_len++; \ I++; \ } \ I++; \ str_len++; \ int *dest_start = pointers.data -= str_len + 1; \ int y; \ for (y = 0; y < str_len - 1; y++) { dest_start[ y ] = str_start[ y ]; } \ pointers.data[ -1 ] = 0; \ value = pointers.data - storage.assembly_target; \ } else if (*I == '&') { \ I++; \ string starting_I = I; \ slice_t name = scanword(); \ value = __ir2_get_var_loc(name, storage.variables, starting_I, init_I); \ } else { \ run_err(I, "expected compile-time value (number, character, string, dereference)"); \ } \ value; \ }) #define scanvar() \ ({ /* memory scanner! finds @10, var_name */ \ int value; \ if (*I == '@') { \ if (!isdigit(*++I)) run_err(I - 1, "expected memory index"); \ value = strtol(I, &I, 10); \ } else if (isalpha(*I) || *I == '_') { \ string starting_I = I; \ slice_t name = scanword(); \ value = __ir2_get_var_loc(name, storage.variables, starting_I, init_I); \ } else \ run_err(I, "expected memory index or variable"); \ value; \ }) #define varqual(F) ((F) == '@' || isalpha(F) || (F) == '_') #define litqual(F) (isdigit(F) || (F) == '\'' || (F) == '"' || (F) == '&') #define p_cat(a, b) a##b #define cat(a, b) p_cat(a, b) #define not_subpart_1(__a) #define not_subpart_(__a) __a #define not(val, ...) cat(not_subpart_, __VA_OPT__(1))(val) #define op(mode, ...) emit(opcode.mode, not(0, __VA_ARGS__) __VA_ARGS__) #define store() (op(acc, 1), op(str)) #define swap() op(swp) #define imm(value) op(imm, value) #define load() op(lod) #define imm_in_mem(__mem, __imm) (imm(__mem), op(cb3, __imm)) #define mov_mem_mem(__mem1, __mem2) (imm(__mem1), op(cb4, __mem2)) #define mem_in_a(__mem) op(cb2, __mem) #define a_in_mem(__mem) op(cb1, __mem) #define deref_a() (op(b2u), swap(), load(), op(u2b)) wowie() { op(acc); } #define check_stack() (0) #define perf_sp(__op) \ (mem_in_a(storage.stack_pointer), \ op(acc, 1), \ op(u2b), \ op(__op), \ a_in_mem(storage.stack_pointer)) #define inc_sp() perf_sp(add) #define dec_sp() perf_sp(sub) #define push_imm(__imm) (mem_in_a(storage.stack_pointer), op(cb3, __imm), dec_sp()) #define push_mem(__mem) (mem_in_a(storage.stack_pointer), op(cb4, __mem), dec_sp()) #define pop_stack_a() (inc_sp(), mem_in_a(storage.stack_pointer), deref_a()) // #define pop_stack_mem(__mem) #define perform_op(operation) \ ({ \ mem_in_a(mem2_index); \ swap(); \ mem_in_a(mem_index); \ op(operation); \ swap(); \ imm(mem_index); \ store(); \ }) string ERR; string ERR_code; struct ir2_pointers { int *code; int *data; int *stack; int stack_pointer; } pointers; init_ir2(int *target_mem, int max_size) { static const int starting_code = 5000; pointers.stack = &target_mem[ 4998 ]; // stack is downward growing pointers.stack_pointer = 4999; // stack pointer pointers.code = &target_mem[ starting_code ]; // code pointer pointers.data = &target_mem[ max_size - 1 ]; // data grows downwards return starting_code; } __attribute__((always_inline)) emit(int op, int val) { *pointers.code++ = op; *pointers.code++ = val; } int ir2_depth = 0; #define space skip_space(&I) bool has_jump_warned = false; bool has_useless_if_warned = false; bool has_false_while_warned = false; __ir2_get_var_loc(slice_t name, label_t variables[ 1 << 11 ], string error_I, string init_I) { int j; for (j = 0; j < 1 << 11; j++) { label_t cur = variables[ j ]; if (cur.name.ref == NULL) break; if (cur.name.len != name.len) continue; if (strncmp(cur.name.ref, name.ref, name.len) == 0) { return cur.location; } } run_err(error_I, "unknown variable name \"%.*s\"", name.len, name.ref); } typedef struct { int *assembly_target; macro_t *macros; label_t *labels; label_delegated_t *forward_refs; label_t *variables; } ir2_storage; string _ir2(string I, string filepath, ir2_storage storage) { char T; string init_I = I; ERR = "all good"; while (T = *I) { if (isspace(T)) { space; continue; } string start_I = I; if (T == '}') { if (ir2_depth == 0) run_err(I, "unexpected closing bracket"); ir2_depth--; return ++I; } if (T == '/' && I[ 1 ] == '/') { // comment I += 2; while (*I != '\n' && *I != '\0') I++; continue; } if (T == '/' && I[ 1 ] == '*') { // multi-line comment string old_I = I; I += 2; while (*I != '*' || I[ 1 ] != '/') { if (I[ 1 ] == '\0') { run_err(old_I, "reached EOF whilst looking for closing comment delimeter"); } I++; } I += 2; continue; } // define variable if (strncmp("let", I, 3) == 0 && isspace(I[ 3 ])) { I += 3; space; string backname_I = I; slice_t name = scanword(); space; int *m_index = --pointers.data; int start_index = m_index - storage.assembly_target; if (*I == ':') { I++; space; checkskip('@', "expected memory index in variable declaration"); if (!isdigit(*I)) { run_err( I, "expected memory index in variable declaration (i think i remember this " "one...)"); } pointers.data++; // free the taken space start_index = strtol(I, &I, 10); space; } // comment out this flag to initialize values at runtime instead of compile-time #define IR2_INIT_COMPILE_TIME if (*I == ';') { // no starter value, just initialize it to zero I++; #ifdef IR2_INIT_COMPILE_TIME *m_index = 0; #else imm_in_mem(start_index, 0); #endif } else { checkskip('=', "expected semicolon or assignment in variable declaration"); space; if (litqual(*I)) { // literal #ifdef IR2_INIT_COMPILE_TIME *m_index = scanvalue(); #else imm_in_mem(start_index, scanvalue()); #endif } else if (varqual(*I)) { // memory address int mem_index = scanvar(); mov_mem_mem(start_index, mem_index); } else { run_err(I, "expected compile-time constant, variable or memory index"); } space; checkskip(';', "expected semicolon after variable value"); } int j; for (j = 0; j < 1 << 11; j++) { label_t cur = storage.variables[ j ]; if (cur.name.ref == NULL) { storage.variables[ j ].name = name; storage.variables[ j ].location = start_index; goto VAR_DONE; } if (cur.name.len == name.len && strncmp(cur.name.ref, name.ref, name.len) == 0) { run_err(backname_I, "duplicate variable name \"%.*s\"", name.len, name.ref); } } run_err(backname_I, "variables count exceeded 1 << 11 (literally how)"); VAR_DONE: continue; } if (strncmp("import", I, 6) == 0 && isspace(I[ 6 ])) { I += 6; space; slice_t path = { 0, I }; while (*I != ';') { I++; path.len++; if (*I == 0 || *I == '\n') run_err(path.ref, "reached invalid character / EOF whilst reading import path"); } checkskip(';', "expected semicolon"); string full; if (path.ref[ 0 ] == '/') full = canonicalize(path.ref); else { string dir = strdup(filepath); // because dirname() mutates string dirpart = dirname(dir); string tmp; asprintf(&tmp, "%s/%.*s", dirpart, path.len, path.ref); full = canonicalize(tmp); free(tmp); free(dir); } int out_size; string text = read_file(full, &out_size); if (text == NULL) { printf("failed to import \"%s\"\n", full); perror("unable to use import"); free(full); exit(1); } _ir2(text, filepath, storage); free(full); free(text); continue; } // register macros if (strncmp("macro", I, 5) == 0 && isspace(I[ 5 ])) { I += 6; space; // parse macro stuff if (!isalpha(*I) && *I != '_') { run_err(I - 1, "expected letter or underscore at macro name"); } string old_I = I; slice_t name = scanword(); space; checkskip('(', "expected open paren in macro defintion"); space; string before = I; int arg_count = strtol(I, &I, 10); if (I == before) { run_err(I, "expected number (argument count) in macro definition parens"); } space; checkskip( ')', "expected close paren in macro definition (hint: macro defintions only take an " "argument count)"); space; checkskip('{', "expected open curly bracket in macro body"); // find open space in macros int x; for (x = 0; x < (1 << 9); x++) { macro_t g = storage.macros[ x ]; if (g.data.key.ref == NULL) goto DONE; if (g.data.key.len == name.len && strncmp(g.data.key.ref, name.ref, name.len) == 0) { run_err(old_I, "redefinition of macro"); } } run_err(I - 1, "registered macro count exceeded 1<<9 (how?)"); DONE:; space; // might aswell skip whitespace for funsies. macro_t *g = &storage.macros[ x ]; g->data.key = name; g->data.value.ref = I; g->data.value.len = 0; g->arg_count = arg_count; int dep = 0; while (dep != 0 || *I != '}') { if (*I == '{') dep++; if (*I == '}') dep--; g->data.value.len++; I++; if (*I == 0) { checkskip('}', "expected close curly bracket in macro body (met EOF instead)"); } } I++; continue; } // emit exit calls if (strncmp("exit", I, 4) == 0 && !varqual(I[ 4 ])) { I += 4; checkskip('(', "expected open paren on exit blob"); string old = I; int value = strtol(I, &I, 10); if (old == I && *I != ')') { run_err(I - 1, "invalid input to exit blob, expected number"); } space; checkskip(')', "expected open paren on exit blob"); space; checkskip(';', "expected semicolon after call"); // actually emit stuff if (old != I) imm(value); op(hlt); continue; } // emit system if (strncmp("system", I, 6) == 0 && !varqual(I[ 6 ])) { I += 6; space; checkskip('(', "expected open paren on system blob"); space; checkskip( ')', "expected closing paren on system blob (hint: system does not take any arguments)"); space; checkskip(';', "expected semicolon after system blob"); imm_in_mem(0, 2); op(hlt); continue; } if (strncmp("jump", I, 4) == 0 && !varqual(I[ 4 ])) { I += 4; goto PARSE_JUMP_BLOB; } if (strncmp("jmp", I, 3) == 0 && !varqual(I[ 3 ])) { I += 3; PARSE_JUMP_BLOB: space; checkskip('(', "expected open paren on jump blob"); space; if (!isalpha(*I)) run_err(I, "expected a label name for jump blob argument"); slice_t label = scanword(); space; checkskip(')', "expected closing paren after label name in jump blob"); space; checkskip(';', "excpected semicolon after jump blob"); // resolve right now if the label exists label_t *lbdb = storage.labels; while (lbdb->name.ref != NULL) { label_t cur = *lbdb++; if (cur.name.len == label.len && strncmp(cur.name.ref, label.ref, label.len) == 0) { imm_in_mem(7, cur.location); goto FINISH_JMP; } } // not resolved yet.. // find delegations slot label_delegated_t *del; int j; for (j = 0; j < 1 << 10; j++) { if (storage.forward_refs[ j ].name.ref == NULL) { // found one! del = &storage.forward_refs[ j ]; goto WRITE_DEL; } } run_err( label.ref, "couldn't reserve a spot for this label as the forward references table is full " "(how?)"); WRITE_DEL: imm_in_mem(7, 0xDEAD10C4); // DEADLOCK - placeholder for the resolver del->replace_location = pointers.code - 1; // place of imm in code del->name = label; del->init_I = init_I; FINISH_JMP: continue; } if ((strncmp("jnz", I, 3) == 0 && !varqual(I[ 3 ])) || (strncmp("jz", I, 2) == 0 && !varqual(I[ 2 ]))) { bool was_jnz = !!strncmp("jz", I, 2); string name = was_jnz ? "jnz" : "jz"; I += was_jnz ? 3 : 2; space; checkskip('(', "expected open paren on %s blob", name); space; int target_mem = scanvar(); space; checkskip( ',', "expected comma (second argument) in %s blob (example: `%s(@5, label_name);`)", name, name); space; if (!isalpha(*I)) run_err( I, "expected a label name for %s blob's second argument (example: `%s(@5, " "label_name);`)", name, name); slice_t label = scanword(); space; checkskip( ')', "expected closing paren after label name in %s blob (do i really need to explain " "this to you again?)", name); space; checkskip(';', "excpected semicolon after %s blob", name); // resolve right now if the label exists label_t *lbdb = storage.labels; while (lbdb->name.ref != NULL) { label_t cur = *lbdb++; if (cur.name.len == label.len && strncmp(cur.name.ref, label.ref, label.len) == 0) { // emit j(n)z mem_in_a(target_mem); if (!was_jnz) op(not); op(a2u); imm(cur.location); swap(); imm(7); op(str); goto FINISH_JNZ; } } // not resolved yet.. // find delegations slot label_delegated_t *del; int j; for (j = 0; j < 1 << 10; j++) { if (storage.forward_refs[ j ].name.ref == NULL) { // found one! del = &storage.forward_refs[ j ]; goto WRITE_DEL_JNZ; } } run_err( label.ref, "couldn't reserve a spot for this label as the forward references table is full " "(how?)"); WRITE_DEL_JNZ: // emit j(n)z mem_in_a(target_mem); if (!was_jnz) op(not); op(a2u); imm(0xDEAD10C4); // DEADLOCK - placeholder for the resolver del->replace_location = pointers.code - 1; // place of imm in code swap(); imm(7); op(str); del->name = label; del->init_I = init_I; FINISH_JNZ: continue; } if (strncmp("if", I, 2) == 0 && !varqual(I[ 2 ])) { // if statement I += 2; space; checkskip('(', "expected open paren for if statement"); space; ir2_depth++; if (isdigit(*I)) { if (!has_useless_if_warned) { has_useless_if_warned = true; printf("warn: redundant if statement\n"); } // redundant if statement: always true / always false int value = strtol(I, &I, 10); space; checkskip(')', "expected closing paren after if statement argument"); space; checkskip('{', "expected opening bracket for if statement body"); space; if (!value) { // just skip over everything inside struct ir2_pointers pointers_B = pointers; // completely eliminate code generated in useless if statement int *m2 = calloc(S, sizeof(int)); label_t *variables_G = calloc(1 << 5, sizeof(label_t)); pointers.data = pointers.code = m2; ir2_storage useless_storage = storage; useless_storage.variables = variables_G; useless_storage.assembly_target = m2; // get new I location string new_I = _ir2(I, filepath, useless_storage); I = new_I; // restore values pointers = pointers_B; free(m2); free(variables_G); } else { // just... i mean, i guess run the code..? string new_I = _ir2(I, filepath, storage); I = new_I; } continue; } bool was_negated = false; if (*I == '!') { was_negated = true; I++; space; } int mem_index = scanvar(); space; checkskip(')', "expected closing paren after if statement argument"); space; checkskip('{', "expected opening bracket for if statement body"); space; mem_in_a(mem_index); if (!was_negated) op(not); op(a2u); imm(0xDEAD10C4); // DEADLOCK - placeholder for the patchback int *patchback = pointers.code - 1; swap(); imm(7); op(str); // handle the code stuff string new_I = _ir2(I, filepath, storage); I = new_I; *patchback = pointers.code - storage.assembly_target; // patch the jump instruction continue; } if (strncmp("while", I, 5) == 0 && !varqual(I[ 5 ])) { I += 5; space; checkskip('(', "expected open paren for while loop"); space; ir2_depth++; if (isdigit(*I)) { int value = strtol(I, &I, 10); space; checkskip(')', "expected closing paren after while loop argument"); space; checkskip('{', "expected opening bracket for while loop body"); space; if (!value) { // non-running while loop if (!has_false_while_warned) { has_false_while_warned = true; printf("warn: dead code inside while loop\n"); } // just skip over everything inside struct ir2_pointers pointers_B = pointers; // completely eliminate code generated in useless while loop int *m2 = calloc(S, sizeof(int)); label_t *variables_G = calloc(1 << 5, sizeof(label_t)); pointers.data = pointers.code = m2; ir2_storage useless_storage = storage; useless_storage.assembly_target = m2; useless_storage.variables = variables_G; // get new I location string new_I = _ir2(I, filepath, useless_storage); I = new_I; // restore values pointers = pointers_B; free(m2); free(variables_G); } else { // run forever! int start = pointers.code - storage.assembly_target; string new_I = _ir2(I, filepath, storage); I = new_I; imm_in_mem(7, start); // always jump back to the start } continue; } bool was_negated = false; if (*I == '!') { was_negated = true; I++; space; } int mem_index = scanvar(); space; checkskip(')', "expected closing paren after while loop condition"); space; checkskip('{', "expected opening bracket for while loop body"); space; mem_in_a(mem_index); if (!was_negated) op(not); op(a2u); imm(0xDEAD10C4); // DEADLOCK - placeholder for the patchback int *patchback = pointers.code - 1; swap(); imm(7); op(str); int start_index = pointers.code - storage.assembly_target; // handle the code stuff string new_I = _ir2(I, filepath, storage); I = new_I; *patchback = pointers.code - storage.assembly_target; // patch the jump instruction // check the condition and jump back if true mem_in_a(mem_index); if (was_negated) op(not); op(a2u); imm(start_index); swap(); imm(7); op(str); continue; } // macro call, maybe? if (isalpha(*I)) { string old_I = I; slice_t name = scanword(); space; if (*I == ':') { I++; // this is a label! label_t *slot; int j; for (j = 0; j < 1 << 10; j++) { if (storage.labels[ j ].name.ref != NULL) { if (storage.labels[ j ].name.len == name.len && strncmp(storage.labels[ j ].name.ref, name.ref, name.len) == 0) { run_err(old_I, "redefinition of label"); } continue; } slot = &storage.labels[ j ]; goto FOUND_SLOT; } run_err(old_I, "registered label count exceeded 1<<10 (how?)"); FOUND_SLOT: slot->name = name; slot->location = pointers.code - storage.assembly_target; continue; } if (*I != '(') { I = old_I; goto REST_AFTER_MACRO_CALL; } checkskip('(', "expected open paren after alphanumeric word (assuming this is a blob)"); space; slice_t arguments[ 1 << 7 ]; int arg_count = 0; string fail_I = I; while (*I != ')') { if (!*I) run_err(fail_I, "reached EOF whilst looking for closing paren on blob"); checkskip('[', "expected bracket in blob argument (macro-type)"); arguments[ arg_count ].len = 0; arguments[ arg_count ].ref = I; int dep = 0; while (dep != 0 || *I != ']') { if (*I == '[') dep++; if (*I == ']') dep--; arguments[ arg_count ].len++; I++; if (*I == 0) { checkskip( ']', "expected closing bracket in macro argument (met EOF instead)"); } } space; checkskip(']', "expected closing bracket in macro arguments"); space; if (*I != ',' && *I != ')') { run_err(I, "expected comma or closing paren in blob"); } if (*I == ',') { I++; space; } arg_count++; } fail_I = I; checkskip(')', "expected closing paren after blob arguments"); space; checkskip(';', "expected semicolon after blob"); // done reading blob! check it and parse macro :) // find index int i; for (i = 0; i < (1 << 9); i++) { macro_t cur = storage.macros[ i ]; if (cur.data.key.len != name.len) continue; if (strncmp(name.ref, cur.data.key.ref, name.len) == 0) goto FOUND; } run_err(I, "could not find a macro by that name"); FOUND:; if (arg_count != storage.macros[ i ].arg_count) { run_err( fail_I, "macro argument count mismatch (expected %i, got %i)", storage.macros[ i ].arg_count, arg_count); } slice_t target = storage.macros[ i ].data.value; string N = target.ref; string oldN = N; memslice_t new = { target.len, malloc(target.len) }; int used = 0; bool needs_scan = true; while (needs_scan) { needs_scan = false; while (*N) { if (N - oldN >= target.len) break; if (*N == '/' && isdigit(N[ 1 ])) { N++; string old = N - 1; int index = strtol(N, &N, 10); if (index > arg_count) { N--; goto END; } if (*N++ != '/') { N = old + 1; while (*N != '/') { new.ref[ used++ ] = *N++; readd(); } goto END; } needs_scan = true; slice_t rep = arguments[ index - 1 ]; string val = rep.ref; for (i = 0; i < rep.len; i++) { new.ref[ used++ ] = *val++; readd(); } continue; } END: new.ref[ used++ ] = *N++; readd(); } } new.ref[ used ] = 0; _ir2(new.ref, filepath, storage); continue; } REST_AFTER_MACRO_CALL: // handle assignments! if (T == '*' || varqual(T)) { bool lhs_deref = T == '*'; if (lhs_deref) { I++; space; if (!varqual(*I)) run_err(I, "expected memory index or variable after dereference"); } string starting_qual = I; int mem_index = scanvar(); space; if (*I == '-' && I[ 1 ] == '-') { // decrement I += 2; space; checkskip(';', "expected semicolon"); mem_in_a(mem_index); if (lhs_deref) deref_a(); op(acc, 1); op(u2b); op(sub); swap(); if (lhs_deref) mem_in_a(mem_index); else { imm(mem_index); } store(); continue; } if (*I == '+' && I[ 1 ] == '+') { // increment I += 2; space; checkskip(';', "expected semicolon"); mem_in_a(mem_index); if (lhs_deref) deref_a(); op(acc, 1); op(u2b); op(add); swap(); if (lhs_deref) mem_in_a(mem_index); else { imm(mem_index); } store(); continue; } string expr_I = I; char extra_op = '='; if (*I == '+' || *I == '-' || *I == '&' || *I == '!') extra_op = *I++; checkskip('=', "expected assigment after memory blob (hint: only +-&! are supported)"); space; if (mem_index == 7 && !has_jump_warned) { has_jump_warned = true; printf("\nwarn: writing to memory index 7 is equivalent to a jump\n"); printf(" if this behavior is intended, use the jump blob instead.\n\n"); } if (*I == '*' || varqual(*I)) { bool rhs_deref = *I == '*'; if (rhs_deref) { I++; space; if (!varqual(*I)) run_err(I, "expected memory index or variable after dereference"); } if ((lhs_deref || rhs_deref) && extra_op != '=') { run_err(expr_I, "dereferencing is not possible with arithmetic. (i was lazy)"); } int mem2_index = scanvar(); space; if (extra_op == '=') { if (lhs_deref) { // m[m[a]] = m[b] // m[m[a]] = m[m[b]] mem_in_a(mem2_index); if (rhs_deref) deref_a(); swap(); mem_in_a(mem_index); store(); } else { if (rhs_deref) { // m[a] = m[m[b]] mem_in_a(mem2_index); deref_a(); a_in_mem(mem_index); } else { // m[a] = m[b] mov_mem_mem(mem_index, mem2_index); } } } else if (extra_op == '+') { perform_op(add); } else if (extra_op == '-') { perform_op(sub); } else if (extra_op == '&') { perform_op(and); } else if (extra_op == '!') { // @1 != @2; (@1 = !@2) mem_in_a(mem2_index); op(not); swap(); imm(mem_index); store(); } } else { int imm = scanvalue(); if (extra_op == '=') { imm_in_mem(mem_index, imm); } else if (extra_op == '+') { imm(imm); swap(); mem_in_a(mem_index); op(add); swap(); imm(mem_index); store(); } else if (extra_op == '-') { imm(imm); swap(); mem_in_a(mem_index); op(sub); swap(); imm(mem_index); store(); } else if (extra_op == '&') { imm(imm); swap(); mem_in_a(mem_index); op(and); swap(); imm(mem_index); store(); } else if (extra_op == '!') { // @1 != 5; (@1 = !5;) imm_in_mem(mem_index, !imm); } } space; checkskip(';', "expected semicolon"); continue; } // unknown run_err(I, "unknown token '%c' (%i)", T, T); } return I; } ir2(string I, string filepath, int *assembly_target) { const int macros_n = 1 << 9; const int labels_n = 1 << 10; const int vars_n = 1 << 11; const int fwd_n = 1 << 10; macro_t *macros = calloc(macros_n, sizeof(*macros)); label_t *labels = calloc(labels_n, sizeof(*labels)); label_t *variables = calloc(vars_n, sizeof(*variables)); label_delegated_t *forward_refs = calloc(fwd_n, sizeof(*forward_refs)); ir2_storage storage = { assembly_target, macros, labels, forward_refs, variables }; if (!macros || !labels || !variables || !forward_refs) { perror("calloc"); exit(1); } _ir2(I, filepath, storage); label_delegated_t *curf = forward_refs; // handle forward references! while (curf->name.ref != NULL) { label_delegated_t cur = *curf++; // find label int j; for (j = 0; j < labels_n; j++) { label_t clab = labels[ j ]; if (clab.name.len == cur.name.len && strncmp(clab.name.ref, cur.name.ref, cur.name.len) == 0) { // found it! *cur.replace_location = clab.location; goto REP_DONE; } } string init_I = cur.init_I; run_err(cur.name.ref, "could not find label %i %s", cur.name.len, cur.name.ref); REP_DONE:; } free(macros); free(labels); free(variables); free(forward_refs); } string op_names[] = { "hlt", "imm", "lod", "str", "swp", "add", "sub", "and", "not", "acc", "a2u", "u2a", "b2u", "u2b", "nop", "cb1", "cb2", "cb3", "cb4" }; unsigned long long rdtsc(void) { #ifndef PU2_NO_ASM unsigned hi, lo; __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); return ((unsigned long long) hi << 32) | lo; #else return 0; #endif } #ifndef PU2_BAD_CLOCK clock_t ntime() { struct timespec t; clock_gettime(CLOCK_MONOTONIC_RAW, &t); return t.tv_nsec + t.tv_sec * 1e9; } #else clock_t ntime() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000000 + tv.tv_usec * 1000; } #endif // bench: measure using clock() double bench_cycle(int instr, int *m, int mem_size) { int i; for (i = 16; i < S - 2; i += 2) { m[ i ] = instr; m[ i + 1 ] = 10; } m[ S - 2 ] = 0; m[ S - 1 ] = 0; m[ 7 ] = 16; c = 0; clock_t start = ntime(); int wstart = rdtsc(); pu2(-1, m, mem_size); int wend = rdtsc(); clock_t end = ntime(); double nanoseconds = (end - start); double inst_count = (double) m[ 7 ] / 2; double insts_per_ns = inst_count / nanoseconds; double insts_per_s = insts_per_ns * 1e9; printf( "%-8s | time: %.3fms | insts: %.0f (%li real) | ips: %.3fM inst/s\n", op_names[ instr ], nanoseconds / 1000000, inst_count, end - start, insts_per_ns * 1e3); return insts_per_s / 1e6; // return M inst/s } // bench2: measure using rdtsc double bench2_cycle(int instr, int *m, int mem_size) { unsigned long long start, end; double vminst_count, cpu_cycles; int i; for (i = 16; i < S - 2; i += 2) { m[ i ] = instr; m[ i + 1 ] = 5; } m[ S - 2 ] = 0; m[ S - 1 ] = 0; m[ 7 ] = 16; c = 0; start = rdtsc(); pu2(-1, m, mem_size); end = rdtsc(); cpu_cycles = (double) (end - start); vminst_count = (double) m[ 7 ] / 2; double cycles_per_inst = cpu_cycles / vminst_count; printf( "%-6s | cycles/inst: %8.3f | cycles: %14.0f | insts: %12.0f\n", op_names[ instr ], cycles_per_inst, cpu_cycles, vminst_count); return cycles_per_inst; } sleep_nano(int nanoseconds) { struct timespec req = { 0 }; req.tv_sec = nanoseconds / 1000000000L; // Whole seconds req.tv_nsec = nanoseconds % 1000000000L; // Remaining nanoseconds // nanosleep might be interrupted, so loop until it completes while (nanosleep(&req, &req) == -1 && errno == EINTR) { continue; // Continue with remaining time if interrupted } } #ifdef PU2_OLD_CLANG #define inline() __attribute__((always_inline)) #else #define inline() [[clang::always_inline]] #endif inline() sleep_micro(int microseconds) { sleep_nano(microseconds * 1e3); } inline() sleep_milli(int milliseconds) { sleep_nano(milliseconds * 1e6); } // benchmark: run target for all opcodes and print summary benchmark(double (*target)(int, int *, int), const char *mode_label, int *m, int mem_size) { double results[ 19 ]; double sum = 0; int count = 0; int i; printf("\n=== pu2 benchmark (%s) ===\n", mode_label); printf("op | result details\n"); printf("---------------------------------------------\n"); for (i = 1; i < 19; i++) { // printf("running: %s\n", op_names[ i ]); results[ i ] = target(i, m, mem_size); sum += results[ i ]; count++; } sum /= count; printf("---------------------------------------------\n"); int start = rdtsc(); sleep_milli(100); int end = rdtsc(); double clock_speed_mhz = ((end - start) / 1e6) * 10; if (strcmp(mode_label, "time") == 0) { // printf("average : %.3f M inst/s\n", sum); printf("cpu clock : ~%.3f MHz (measured via rdtsc & msleep(100))\n", clock_speed_mhz); printf("vm clock (i/s) : %.3fMHz (ipn, higher is better)\n", sum); // printf("estimated : %.3f cycles per instruction (cpi, lower is better)\n", // clock_speed_mhz / sum); } else { // mode_label == "rdtsc" printf("average cycles/inst : %.3f cycles\n", sum); // printf("cpu clock : ~%.3f MHz (measured via rdtsc & msleep(100))\n", // clock_speed_mhz); printf("estimated ipc : %.3f (1 / cycles_per_inst, higher is better)\n", 1.0 / sum); // printf("estimated cpi : %.3f (cycles_per_inst, lower is better)\n", sum); } printf("=== end benchmark ===\n\n"); } volatile long out; main(int argc, char **argv) { if (argc == 1) { printf( "\nusage:\n" " %s open and compile ir2-compliant code\n" " %s benchmark benchmark pu2 (two modes: time and rdtsc)\n\n", argv[ 0 ], argv[ 0 ]); return 1; } int *assembly_target = calloc(S, sizeof(int)); if (strcmp(argv[ 1 ], "benchmark") == 0) { int *m = init_pu2(); int mem_size = S; benchmark(bench_cycle, "time", m, mem_size); const long N = S / 2; // printably the same insts you report long sum = 0; unsigned aux; unsigned long long t0 = rdtsc(); int C; for (C = 0; C < N; ++C) { sum += 1; __asm__ volatile(""); } unsigned long long t1 = rdtsc(); out = sum; // single volatile write to make sum observable printf( "(absolute max) N=%llu cycles=%llu cycles/iter=%.6f\n", (unsigned long long) N, (unsigned long long) (t1 - t0), (t1 - t0) / (double) N); // benchmark(bench2_cycle, "rdtsc"); exit(0); } char *filename = argv[ 1 ]; int file_size; string contents = read_file(filename, &file_size); if (contents == NULL) { perror("unable to open file"); exit(1); } const int starting_code_ptr = init_ir2(assembly_target, S); int o_size; long before = ntime(); long cycles_bef = rdtsc(); ir2(contents, filename, assembly_target); long cycles_aft = rdtsc(); long after = ntime(); assembly_target[ 7 ] = starting_code_ptr; /*int j = 0; for(j = 0; j < 64; j++) { printf("%i: %i %i\n", j, m[5000+j], m[5001+j]); j++; }*/ // printf("assembly took %i milliseconds\n", (after - before)/1000); free(contents); // int res_size = code - &m[ 4000 ]; // run the kernel (ring zero) forever (time slice -1) int mem_size = S; // pid zero - kernel processes[ 0 ] = (emu_process) { .mem_size = S, .memory = assembly_target, .ring_level = 0, .permissions = { .make_syscall = true, .spawn_process = true } }; emu(-1, 0); }