/* * runtime.c - Unlambda runtime * * Copyright (C) 2020 CismonX * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "runtime.h" #include "logging.h" #include "vm_defs.h" #include "vm_stack.h" #include "vm_pool.h" #include #include #include static struct u6a_vm_ins* text; static uint32_t text_len; static char* rodata; static uint32_t rodata_len; static bool force_exec; static const struct u6a_vm_ins text_subst[] = { { .opcode = u6a_vo_la }, { .opcode = u6a_vo_xch }, { .opcode = u6a_vo_la }, { .opcode = u6a_vo_la }, { .opcode = u6a_vo_la } }; static const uint32_t text_subst_len = sizeof(text_subst) / sizeof(struct u6a_vm_ins); static const char* err_runtime = "runtime error"; static const char* info_runtime = "runtime"; #define CHECK_BC_HEADER_VER(file_header) \ ( (file_header).ver_major == U6A_VER_MAJOR && (file_header).ver_minor == U6A_VER_MINOR ) // Addref before free, for acc may equal to fn #define ACC_FN(fn_) \ vm_var_fn_addref(fn_); \ vm_var_fn_free(acc); \ acc = fn_ #define ACC_FN_INIT(fn_) \ vm_var_fn_free(acc); \ acc = fn_ #define ACC_FN_REF(fn_, ref_) \ vm_var_fn_free(acc); \ acc = U6A_VM_VAR_FN_REF(fn_, ref_); \ if (UNLIKELY(acc.ref == UINT32_MAX)) { \ goto runtime_error; \ } #define CHECK_FORCE(log_func, err_val) \ if (!force_exec) { \ log_func(err_runtime, err_val); \ goto runtime_error; \ } #define STACK_PUSH1(fn_0) \ vm_var_fn_addref(fn_0); \ if (UNLIKELY(!u6a_vm_stack_push1(fn_0))) { \ goto runtime_error; \ } #define STACK_PUSH2(fn_0, fn_1) \ if (UNLIKELY(!u6a_vm_stack_push2(fn_0, fn_1))) { \ goto runtime_error; \ } #define STACK_PUSH3(fn_0, fn_12) \ if (UNLIKELY(!u6a_vm_stack_push3(fn_0, fn_12))) { \ goto runtime_error; \ } #define STACK_PUSH4(fn_0, fn_1, fn_23) \ if (UNLIKELY(!u6a_vm_stack_push4(fn_0, fn_1, fn_23))) { \ goto runtime_error; \ } #define STACK_POP() \ vm_var_fn_free(top); \ top = u6a_vm_stack_top(); \ if (UNLIKELY(!u6a_vm_stack_pop())) { \ goto runtime_error; \ } static inline bool read_bc_header(struct u6a_bc_header* restrict header, FILE* restrict input_stream) { int ch; do { ch = fgetc(input_stream); if (UNLIKELY(ch == EOF)) { return false; } } while (ch != U6A_MAGIC); if (UNLIKELY(ch != ungetc(ch, input_stream))) { return false; } if (UNLIKELY(1 != fread(&header->file, U6A_BC_FILE_HEADER_SIZE, 1, input_stream))) { return false; } if (LIKELY(header->file.prog_header_size >= U6A_BC_FILE_HEADER_SIZE)) { return 1 == fread(&header->prog, header->file.prog_header_size, 1, input_stream); } return true; } static inline struct u6a_vm_var_fn vm_var_fn_addref(struct u6a_vm_var_fn var) { if (var.token.fn & U6A_VM_FN_REF) { u6a_vm_pool_addref(var.ref); } return var; } static inline void vm_var_fn_free(struct u6a_vm_var_fn var) { if (var.token.fn & U6A_VM_FN_REF) { u6a_vm_pool_free(var.ref); } } bool u6a_runtime_info(FILE* restrict input_stream, const char* file_name) { struct u6a_bc_header header; if (UNLIKELY(!read_bc_header(&header, input_stream))) { u6a_err_invalid_bc_file(err_runtime, file_name); return false; } printf("Version: %d.%d.X\n", header.file.ver_major, header.file.ver_minor); if (LIKELY(CHECK_BC_HEADER_VER(header.file))) { if (LIKELY(header.file.prog_header_size == U6A_BC_FILE_HEADER_SIZE)) { printf("Size of section .text (bytes): 0x%08X\n", ntohl(header.prog.text_size)); printf("Size of section .rodata (bytes): 0x%08X\n", ntohl(header.prog.rodata_size)); } else { printf("Program header unrecognizable (%d bytes)", header.file.prog_header_size); } } return true; } bool u6a_runtime_init(struct u6a_runtime_options* options) { struct u6a_bc_header header; if (UNLIKELY(!read_bc_header(&header, options->istream))) { u6a_err_invalid_bc_file(err_runtime, options->file_name); return false; } if (UNLIKELY(!CHECK_BC_HEADER_VER(header.file))) { if (!options->force_exec || header.file.prog_header_size != U6A_BC_FILE_HEADER_SIZE) { u6a_err_bad_bc_ver(err_runtime, options->file_name, header.file.ver_major, header.file.ver_minor); return false; } } header.prog.text_size = ntohl(header.prog.text_size); header.prog.rodata_size = ntohl(header.prog.rodata_size); text = malloc(header.prog.text_size + sizeof(text_subst)); if (UNLIKELY(text == NULL)) { u6a_err_bad_alloc(err_runtime, header.prog.text_size + sizeof(text_subst)); return false; } text_len = header.prog.text_size / sizeof(struct u6a_vm_ins); rodata = malloc(header.prog.rodata_size); if (UNLIKELY(rodata == NULL)) { u6a_err_bad_alloc(err_runtime, header.prog.rodata_size); free(text); return false; } rodata_len = header.prog.rodata_size / sizeof(char); memcpy(text, text_subst, sizeof(text_subst)); if (UNLIKELY(text_len != fread(text + text_subst_len, sizeof(struct u6a_vm_ins), text_len, options->istream))) { goto runtime_init_failed; } if (UNLIKELY(rodata_len != fread(rodata, sizeof(char), rodata_len, options->istream))) { goto runtime_init_failed; } if (UNLIKELY(!u6a_vm_stack_init(options->stack_segment_size, err_runtime))) { goto runtime_init_failed; } if (UNLIKELY(!u6a_vm_pool_init(options->pool_size, text_len, err_runtime))) { goto runtime_init_failed; } for (struct u6a_vm_ins* ins = text + text_subst_len; ins < text + text_len; ++ins) { if (ins->opcode & U6A_VM_OP_OFFSET) { ins->operand.offset = ntohl(ins->operand.offset); } } force_exec = options->force_exec; return true; runtime_init_failed: u6a_runtime_destroy(); return false; } U6A_HOT union u6a_vm_var u6a_runtime_execute(FILE* restrict istream, FILE* restrict ostream) { struct u6a_vm_var_fn acc = { 0 }, top = { 0 }; struct u6a_vm_ins* ins = text + text_subst_len; int current_char = EOF; struct u6a_vm_var_fn func = { 0 }, arg = { 0 }; struct u6a_vm_var_tuple tuple; void* ptr; while (true) { switch (ins->opcode) { case u6a_vo_app: if (ins->operand.fn.first.fn) { func.token = ins->operand.fn.first; } else { func = acc; goto arg_from_ins; } if (ins->operand.fn.second.fn) { arg_from_ins: arg.token = ins->operand.fn.second; } else { arg = acc; } goto do_apply; case u6a_vo_la: STACK_POP(); func = top; arg = acc; do_apply: switch (func.token.fn) { case u6a_vf_s: vm_var_fn_addref(arg); ACC_FN_REF(u6a_vf_s1, u6a_vm_pool_alloc1(arg)); break; case u6a_vf_s1: vm_var_fn_addref(arg); vm_var_fn_addref(u6a_vm_pool_get1(func.ref).fn); ACC_FN_REF(u6a_vf_s2, u6a_vm_pool_alloc2(u6a_vm_pool_get1(func.ref).fn, arg)); break; case u6a_vf_s2: tuple = u6a_vm_pool_get2(func.ref); vm_var_fn_addref(tuple.v1.fn); vm_var_fn_addref(tuple.v2.fn); vm_var_fn_addref(arg); if (ins - text == 0x03) { STACK_PUSH3(arg, tuple); } else { STACK_PUSH4(U6A_VM_VAR_FN_REF(u6a_vf_j, ins - text), arg, tuple); } ACC_FN(arg); ins = text; continue; case u6a_vf_k: vm_var_fn_addref(arg); ACC_FN_REF(u6a_vf_k1, u6a_vm_pool_alloc1(arg)); break; case u6a_vf_k1: ACC_FN(u6a_vm_pool_get1(func.ref).fn); break; case u6a_vf_i: ACC_FN(arg); break; case u6a_vf_out: ACC_FN(arg); fputc(func.token.ch, ostream); break; case u6a_vf_j: ACC_FN(arg); ins = text + func.ref; break; case u6a_vf_f: // Safe to assign IP here before jumping, as func won't be `j` or `f` ins = text + func.ref; STACK_POP(); func = acc; arg = top; goto do_apply; case u6a_vf_c: ptr = u6a_vm_stack_save(); if (UNLIKELY(ptr == NULL)) { goto runtime_error; } ACC_FN_REF(u6a_vf_c1, u6a_vm_pool_alloc2_ptr(ptr, ins)); break; case u6a_vf_d: ACC_FN_REF(u6a_vf_d1_c, u6a_vm_pool_alloc1(arg)); break; case u6a_vf_c1: tuple = u6a_vm_pool_get2_separate(func.ref); u6a_vm_stack_resume(tuple.v1.ptr); ins = tuple.v2.ptr; ACC_FN(arg); break; case u6a_vf_d1_c: func = u6a_vm_pool_get1(func.ref).fn; goto do_apply; case u6a_vf_d1_s: tuple = u6a_vm_pool_get2(func.ref); STACK_PUSH1(tuple.v1.fn); ACC_FN(tuple.v2.fn); ins = text + 0x03; continue; case u6a_vf_d1_d: STACK_PUSH2(vm_var_fn_addref(arg), U6A_VM_VAR_FN_REF(u6a_vf_f, ins - text)); ins = text + func.ref; continue; case u6a_vf_v: vm_var_fn_free(acc); acc.token.fn = u6a_vf_v; break; case u6a_vf_p: ACC_FN(arg); fputs(rodata + func.ref, ostream); break; case u6a_vf_in: current_char = fgetc(istream); func = arg; if (UNLIKELY(current_char == EOF)) { arg.token.fn = u6a_vf_v; } else { arg.token.fn = u6a_vf_i; } goto do_apply; case u6a_vf_cmp: if (func.token.ch == current_char) { func = arg; arg.token.fn = u6a_vf_i; } else { func = arg; arg.token.fn = u6a_vf_v; } goto do_apply; case u6a_vf_pipe: func = arg; if (UNLIKELY(current_char == EOF)) { arg.token.fn = u6a_vf_v; } else { arg.token = U6A_TOKEN(u6a_vf_out, current_char); } goto do_apply; case u6a_vf_e: // Every program should terminate with explicit `e` function return U6A_VM_VAR_FN(arg); default: CHECK_FORCE(u6a_err_invalid_vm_func, func.token.fn); } break; case u6a_vo_sa: if (UNLIKELY(acc.token.fn == u6a_vf_d)) { goto delay; } STACK_PUSH1(acc); break; case u6a_vo_xch: if (UNLIKELY(acc.token.fn == u6a_vf_d)) { STACK_POP(); func = top; STACK_POP(); arg = top; ACC_FN_REF(u6a_vf_d1_s, u6a_vm_pool_alloc2(func, arg)); } else { acc = u6a_vm_stack_xch(acc); } break; case u6a_vo_del: delay: ACC_FN_INIT(U6A_VM_VAR_FN_REF(u6a_vf_d1_d, ins + 1 - text)); ins = text + text_subst_len + ins->operand.offset; continue; case u6a_vo_lc: switch (ins->opcode_ex) { case u6a_vo_ex_print: ACC_FN_INIT(U6A_VM_VAR_FN_REF(u6a_vf_p, ins->operand.offset)); break; default: CHECK_FORCE(u6a_err_invalid_ex_opcode, ins->opcode_ex); } break; default: CHECK_FORCE(u6a_err_invalid_opcode, ins->opcode); } ++ins; } runtime_error: return U6A_VM_VAR_PTR(NULL); } void u6a_runtime_destroy() { free(text); free(rodata); text = NULL; rodata = NULL; }