399 lines
15 KiB
C
399 lines
15 KiB
C
/*
|
|
* runtime.c - Unlambda runtime
|
|
*
|
|
* Copyright (C) 2020 CismonX <admin@cismon.net>
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "runtime.h"
|
|
#include "logging.h"
|
|
#include "vm_defs.h"
|
|
#include "vm_stack.h"
|
|
#include "vm_pool.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
|
|
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;
|
|
}
|