Reworked (de)serialising routines for instructions
No longer relying on darr_t or anything other than the C runtime and aliases. This means it should be *even easier* to target this via FFI from other languages without having to initialise my custom made structures! Furthermore I've removed any form of allocation in the library so FFI callers don't need to manage memory in any way. Instead we rely on the caller allocating the correct amount of memory for the functions to work, with basic error handling if that doesn't happen. In the case of inst_read_bytecode, error reporting occurs by making the return of a function an integer. If the integer is positive it is the number of bytes read from the buffer. If negative it flags a possible error, which is a member of read_err_t. prog_read_bytecode has been split into two functions: prog_read_header and prog_read_instructions. prog_read_instructions works under the assumption that the program's header has been filled, e.g. via prog_read_header. prog_read_header returns 0 if there's not enough space in the buffer or if the start_address is greater than the count. prog_read_instructions returns a custom structure which contains an byte position as well as an error enum, allowing for finer error reporting. In the case of inst_write_bytecode via the assumption that the caller allocated the correct memory there is no need for error reporting. For prog_write_bytecode if an error occurs due to In the case of inst_read_bytecode we return the number
This commit is contained in:
266
lib/inst.c
266
lib/inst.c
@@ -276,36 +276,37 @@ void inst_print(inst_t instruction, FILE *fp)
|
||||
fprintf(fp, ")");
|
||||
}
|
||||
|
||||
size_t inst_bytecode_size(inst_t inst)
|
||||
size_t opcode_bytecode_size(opcode_t opcode)
|
||||
{
|
||||
static_assert(NUMBER_OF_OPCODES == 98, "inst_bytecode_size: Out of date");
|
||||
size_t size = 1; // for opcode
|
||||
if (UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_PUSH))
|
||||
if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH))
|
||||
{
|
||||
if (inst.opcode == OP_PUSH_BYTE)
|
||||
if (opcode == OP_PUSH_BYTE)
|
||||
++size;
|
||||
else if (inst.opcode == OP_PUSH_HWORD)
|
||||
else if (opcode == OP_PUSH_HWORD)
|
||||
size += HWORD_SIZE;
|
||||
else if (inst.opcode == OP_PUSH_WORD)
|
||||
else if (opcode == OP_PUSH_WORD)
|
||||
size += WORD_SIZE;
|
||||
}
|
||||
else if (UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_PUSH_REGISTER) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_MOV) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_DUP) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_MALLOC) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_MSET) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_MGET) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_JUMP_IF) ||
|
||||
inst.opcode == OP_JUMP_ABS || inst.opcode == OP_CALL)
|
||||
else if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH_REGISTER) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MOV) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_DUP) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MALLOC) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MSET) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MGET) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_JUMP_IF) ||
|
||||
opcode == OP_JUMP_ABS || opcode == OP_CALL)
|
||||
size += WORD_SIZE;
|
||||
return size;
|
||||
}
|
||||
|
||||
void inst_write_bytecode(inst_t inst, darr_t *darr)
|
||||
size_t inst_write_bytecode(inst_t inst, byte_t *bytes)
|
||||
{
|
||||
static_assert(NUMBER_OF_OPCODES == 98, "inst_write_bytecode: Out of date");
|
||||
// Append opcode
|
||||
darr_append_byte(darr, inst.opcode);
|
||||
|
||||
size_t written = 1;
|
||||
bytes[0] = inst.opcode;
|
||||
// Then append 0 or more operands
|
||||
data_type_t to_append = DATA_TYPE_NIL;
|
||||
if (UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_PUSH))
|
||||
@@ -325,72 +326,70 @@ void inst_write_bytecode(inst_t inst, darr_t *darr)
|
||||
case DATA_TYPE_NIL:
|
||||
break;
|
||||
case DATA_TYPE_BYTE:
|
||||
darr_append_byte(darr, inst.operand.as_byte);
|
||||
bytes[1] = inst.operand.as_byte;
|
||||
written += 1;
|
||||
break;
|
||||
case DATA_TYPE_HWORD:
|
||||
darr_ensure_capacity(darr, HWORD_SIZE);
|
||||
convert_hword_to_bytes(inst.operand.as_hword, darr->data + darr->used);
|
||||
darr->used += HWORD_SIZE;
|
||||
convert_hword_to_bytes(inst.operand.as_hword, bytes + 1);
|
||||
written += HWORD_SIZE;
|
||||
break;
|
||||
case DATA_TYPE_WORD:
|
||||
darr_ensure_capacity(darr, WORD_SIZE);
|
||||
convert_word_to_bytes(inst.operand.as_word, darr->data + darr->used);
|
||||
darr->used += WORD_SIZE;
|
||||
convert_word_to_bytes(inst.operand.as_word, bytes + 1);
|
||||
written += WORD_SIZE;
|
||||
break;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
void insts_write_bytecode(inst_t *insts, size_t size, darr_t *darr)
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
inst_write_bytecode(insts[i], darr);
|
||||
}
|
||||
|
||||
data_t read_type_from_darr(darr_t *darr, data_type_t type)
|
||||
bool read_type_from_darr(byte_t *bytes, size_t size, data_type_t type,
|
||||
data_t *data)
|
||||
{
|
||||
data_t datum = {0};
|
||||
switch (type)
|
||||
{
|
||||
case DATA_TYPE_NIL:
|
||||
break;
|
||||
case DATA_TYPE_BYTE:
|
||||
if (darr->used > darr->available)
|
||||
// TODO: Error (darr has no space left)
|
||||
return DBYTE(0);
|
||||
return DBYTE(darr->data[darr->used++]);
|
||||
if (size == 0)
|
||||
return false;
|
||||
datum = DBYTE(bytes[0]);
|
||||
break;
|
||||
case DATA_TYPE_HWORD:
|
||||
if (darr->used + HWORD_SIZE > darr->available)
|
||||
// TODO: Error (darr has no space left)
|
||||
return DWORD(0);
|
||||
hword_t u = convert_bytes_to_hword(darr->data + darr->used);
|
||||
darr->used += HWORD_SIZE;
|
||||
return DHWORD(u);
|
||||
if (size < HWORD_SIZE)
|
||||
return false;
|
||||
hword_t u = convert_bytes_to_hword(bytes);
|
||||
datum = DHWORD(u);
|
||||
break;
|
||||
case DATA_TYPE_WORD:
|
||||
if (darr->used + WORD_SIZE > darr->available)
|
||||
// TODO: Error (darr has no space left)
|
||||
return DWORD(0);
|
||||
word_t w = convert_bytes_to_word(darr->data + darr->used);
|
||||
darr->used += WORD_SIZE;
|
||||
return DWORD(w);
|
||||
if (size < WORD_SIZE)
|
||||
return false;
|
||||
word_t w = convert_bytes_to_word(bytes);
|
||||
datum = DWORD(w);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// TODO: Error (unrecognised type)
|
||||
return DBYTE(0);
|
||||
*data = datum;
|
||||
return true;
|
||||
}
|
||||
|
||||
inst_t inst_read_bytecode(darr_t *darr)
|
||||
int inst_read_bytecode(inst_t *ptr, byte_t *bytes, size_t size_bytes)
|
||||
{
|
||||
static_assert(NUMBER_OF_OPCODES == 98, "inst_read_bytecode: Out of date");
|
||||
if (darr->used >= darr->available)
|
||||
return (inst_t){0};
|
||||
inst_t inst = {0};
|
||||
opcode_t opcode = darr->data[darr->used++];
|
||||
|
||||
opcode_t opcode = *(bytes++);
|
||||
if (opcode > OP_HALT || opcode == NUMBER_OF_OPCODES || opcode < OP_NOOP)
|
||||
return INST_NOOP;
|
||||
return READ_ERR_INVALID_OPCODE;
|
||||
|
||||
inst_t inst = {opcode, {0}};
|
||||
--size_bytes;
|
||||
|
||||
bool success = true;
|
||||
|
||||
// Read operands
|
||||
if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH))
|
||||
inst.operand = read_type_from_darr(darr, (data_type_t)opcode);
|
||||
success = read_type_from_darr(bytes, size_bytes, (data_type_t)opcode,
|
||||
&inst.operand);
|
||||
// Read register (as a byte)
|
||||
else if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH_REGISTER) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MOV) ||
|
||||
@@ -400,111 +399,90 @@ inst_t inst_read_bytecode(darr_t *darr)
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MGET) ||
|
||||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_JUMP_IF) ||
|
||||
opcode == OP_JUMP_ABS || opcode == OP_CALL)
|
||||
inst.operand = read_type_from_darr(darr, DATA_TYPE_WORD);
|
||||
// Otherwise opcode doesn't take operands
|
||||
|
||||
inst.opcode = opcode;
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
inst_t *insts_read_bytecode(darr_t *bytes, size_t *ret_size)
|
||||
{
|
||||
*ret_size = 0;
|
||||
// NOTE: Here we use the darr as a dynamic array of inst_t.
|
||||
darr_t instructions = {0};
|
||||
darr_init(&instructions, sizeof(inst_t));
|
||||
while (bytes->used < bytes->available)
|
||||
success =
|
||||
read_type_from_darr(bytes, size_bytes, DATA_TYPE_WORD, &inst.operand);
|
||||
else
|
||||
{
|
||||
inst_t instruction = inst_read_bytecode(bytes);
|
||||
darr_append_bytes(&instructions, (byte_t *)&instruction,
|
||||
sizeof(instruction));
|
||||
// Instruction doesn't take operands
|
||||
}
|
||||
*ret_size = instructions.used / sizeof(inst_t);
|
||||
return (inst_t *)instructions.data;
|
||||
|
||||
if (success)
|
||||
{
|
||||
*ptr = inst;
|
||||
return (int)(READ_ERR_END) - (int)(size_bytes);
|
||||
}
|
||||
else
|
||||
return READ_ERR_OPERAND_NO_FIT;
|
||||
}
|
||||
|
||||
inst_t *insts_read_bytecode_file(FILE *fp, size_t *ret)
|
||||
{
|
||||
darr_t darr = darr_read_file(fp);
|
||||
inst_t *instructions = insts_read_bytecode(&darr, ret);
|
||||
free(darr.data);
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void insts_write_bytecode_file(inst_t *instructions, size_t size, FILE *fp)
|
||||
{
|
||||
darr_t darr = {0};
|
||||
darr_init(&darr, 0);
|
||||
insts_write_bytecode(instructions, size, &darr);
|
||||
darr_write_file(&darr, fp);
|
||||
free(darr.data);
|
||||
}
|
||||
|
||||
static_assert(sizeof(prog_t) == WORD_SIZE * 2,
|
||||
static_assert(sizeof(prog_t) == (WORD_SIZE * 2) + sizeof(inst_t *),
|
||||
"prog_{write|read}_* is out of date");
|
||||
void prog_write_bytecode(prog_t *program, darr_t *buffer)
|
||||
|
||||
size_t prog_bytecode_size(prog_t program)
|
||||
{
|
||||
size_t size = WORD_SIZE * 2;
|
||||
for (size_t i = 0; i < program.count; ++i)
|
||||
size += opcode_bytecode_size(program.instructions[i].opcode);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t prog_write_bytecode(prog_t program, byte_t *bytes, size_t size_bytes)
|
||||
{
|
||||
if (size_bytes < PROG_HEADER_SIZE || prog_bytecode_size(program) < size_bytes)
|
||||
return 0;
|
||||
// Write program header i.e. the start and count
|
||||
word_t start = word_htobc(program->start_address);
|
||||
darr_append_bytes(buffer, (byte_t *)&start, sizeof(start));
|
||||
word_t count = word_htobc(program->count);
|
||||
darr_append_bytes(buffer, (byte_t *)&count, sizeof(count));
|
||||
word_t start = word_htobc(program.start_address);
|
||||
*(bytes++) = start;
|
||||
word_t count = word_htobc(program.count);
|
||||
*(bytes++) = count;
|
||||
|
||||
// Write instructions
|
||||
insts_write_bytecode(program->instructions, program->count, buffer);
|
||||
}
|
||||
|
||||
void prog_append_bytecode(prog_t *program, darr_t *buffer)
|
||||
{
|
||||
insts_write_bytecode(program->instructions, program->count, buffer);
|
||||
}
|
||||
|
||||
prog_t *prog_read_bytecode(darr_t *buffer)
|
||||
{
|
||||
// TODO: Error (not enough space for program header)
|
||||
if ((buffer->available - buffer->used) < sizeof(prog_t))
|
||||
return NULL;
|
||||
// Read program header
|
||||
word_t start_address = convert_bytes_to_word(buffer->data + buffer->used);
|
||||
buffer->used += sizeof(start_address);
|
||||
word_t count = convert_bytes_to_word(buffer->data + buffer->used);
|
||||
buffer->used += sizeof(word_t);
|
||||
|
||||
// TODO: Error (not enough space for program instruction count)
|
||||
if ((buffer->available - buffer->used) < WORD_SIZE)
|
||||
return NULL;
|
||||
|
||||
prog_t *program = malloc(sizeof(*program) + (sizeof(inst_t) * count));
|
||||
size_t i;
|
||||
for (i = 0; i < count && (buffer->used < buffer->available); ++i)
|
||||
program->instructions[i] = inst_read_bytecode(buffer);
|
||||
|
||||
// TODO: Error (Expected more instructions)
|
||||
if (i < count - 1)
|
||||
size_t p_iter = 0, b_iter = PROG_HEADER_SIZE;
|
||||
for (; p_iter < program.count && b_iter < size_bytes; ++p_iter)
|
||||
{
|
||||
free(program);
|
||||
return NULL;
|
||||
size_t written =
|
||||
inst_write_bytecode(program.instructions[p_iter], bytes + b_iter);
|
||||
if (written == 0)
|
||||
return 0;
|
||||
b_iter += written;
|
||||
}
|
||||
|
||||
program->start_address = start_address;
|
||||
program->count = count;
|
||||
|
||||
return program;
|
||||
return b_iter;
|
||||
}
|
||||
|
||||
void prog_write_file(prog_t *program, FILE *fp)
|
||||
size_t prog_read_header(prog_t *prog, byte_t *bytes, size_t size_bytes)
|
||||
{
|
||||
darr_t bytecode = {0};
|
||||
prog_write_bytecode(program, &bytecode);
|
||||
fwrite(bytecode.data, bytecode.used, 1, fp);
|
||||
free(bytecode.data);
|
||||
if (size_bytes < PROG_HEADER_SIZE)
|
||||
return 0;
|
||||
prog->start_address = convert_bytes_to_word(bytes);
|
||||
prog->count = convert_bytes_to_word(bytes + WORD_SIZE);
|
||||
|
||||
if (prog->start_address >= prog->count)
|
||||
return 0;
|
||||
return PROG_HEADER_SIZE;
|
||||
}
|
||||
|
||||
prog_t *prog_read_file(FILE *fp)
|
||||
read_err_prog_t prog_read_instructions(prog_t *program, size_t *size_bytes_read,
|
||||
byte_t *bytes, size_t size_bytes)
|
||||
{
|
||||
darr_t buffer = darr_read_file(fp);
|
||||
prog_t *p = prog_read_bytecode(&buffer);
|
||||
free(buffer.data);
|
||||
return p;
|
||||
// If no count then must be empty
|
||||
if (program->count == 0)
|
||||
return (read_err_prog_t){0};
|
||||
|
||||
size_t program_iter = 0, byte_iter = 0;
|
||||
for (; program_iter < program->count && byte_iter < size_bytes;
|
||||
++program_iter)
|
||||
{
|
||||
inst_t inst = {0};
|
||||
int bytes_read =
|
||||
inst_read_bytecode(&inst, bytes + byte_iter, size_bytes - byte_iter);
|
||||
if (bytes_read < 0)
|
||||
return (read_err_prog_t){bytes_read, byte_iter};
|
||||
byte_iter += bytes_read;
|
||||
}
|
||||
|
||||
if (program_iter < program->count)
|
||||
return (read_err_prog_t){READ_ERR_EXPECTED_MORE, 0};
|
||||
*size_bytes_read = byte_iter;
|
||||
return (read_err_prog_t){0};
|
||||
}
|
||||
|
||||
287
lib/inst.h
287
lib/inst.h
@@ -13,14 +13,10 @@
|
||||
#ifndef INST_H
|
||||
#define INST_H
|
||||
|
||||
#include <lib/darr.h>
|
||||
#include <lib/prog.h>
|
||||
|
||||
#include <lib/base.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
const char *opcode_as_cstr(opcode_t);
|
||||
|
||||
#define UNSIGNED_OPCODE_IS_TYPE(OPCODE, OP_TYPE) \
|
||||
(((OPCODE) >= OP_TYPE##_BYTE) && ((OPCODE) <= OP_TYPE##_WORD))
|
||||
|
||||
@@ -29,85 +25,224 @@ const char *opcode_as_cstr(opcode_t);
|
||||
|
||||
#define OPCODE_DATA_TYPE(OPCODE, OP_TYPE) (OPCODE - OP_TYPE##_BYTE)
|
||||
|
||||
// OPCODE_DATA_TYPE: opcode_t -> data_type_t. data_type_t acts as
|
||||
// a map between types and their offsets from the first type of
|
||||
// instruction. That means for opcode_type A and data_type u,
|
||||
// OP_<A>_BYTE + u = OP_<A>_<u>.
|
||||
typedef enum
|
||||
{
|
||||
OP_NOOP = 0,
|
||||
|
||||
// Dealing with data and registers
|
||||
OP_PUSH_BYTE,
|
||||
OP_PUSH_HWORD,
|
||||
OP_PUSH_WORD,
|
||||
|
||||
OP_POP_BYTE,
|
||||
OP_POP_HWORD,
|
||||
OP_POP_WORD,
|
||||
|
||||
OP_PUSH_REGISTER_BYTE,
|
||||
OP_PUSH_REGISTER_HWORD,
|
||||
OP_PUSH_REGISTER_WORD,
|
||||
|
||||
OP_MOV_BYTE,
|
||||
OP_MOV_HWORD,
|
||||
OP_MOV_WORD,
|
||||
|
||||
OP_DUP_BYTE,
|
||||
OP_DUP_HWORD,
|
||||
OP_DUP_WORD,
|
||||
|
||||
// Dealing with the heap
|
||||
OP_MALLOC_BYTE,
|
||||
OP_MALLOC_HWORD,
|
||||
OP_MALLOC_WORD,
|
||||
|
||||
OP_MALLOC_STACK_BYTE,
|
||||
OP_MALLOC_STACK_HWORD,
|
||||
OP_MALLOC_STACK_WORD,
|
||||
|
||||
OP_MSET_BYTE,
|
||||
OP_MSET_HWORD,
|
||||
OP_MSET_WORD,
|
||||
|
||||
OP_MSET_STACK_BYTE,
|
||||
OP_MSET_STACK_HWORD,
|
||||
OP_MSET_STACK_WORD,
|
||||
|
||||
OP_MGET_BYTE,
|
||||
OP_MGET_HWORD,
|
||||
OP_MGET_WORD,
|
||||
|
||||
OP_MGET_STACK_BYTE,
|
||||
OP_MGET_STACK_HWORD,
|
||||
OP_MGET_STACK_WORD,
|
||||
|
||||
OP_MDELETE,
|
||||
OP_MSIZE,
|
||||
|
||||
// Boolean operations
|
||||
OP_NOT_BYTE,
|
||||
OP_NOT_HWORD,
|
||||
OP_NOT_WORD,
|
||||
|
||||
OP_OR_BYTE,
|
||||
OP_OR_HWORD,
|
||||
OP_OR_WORD,
|
||||
|
||||
OP_AND_BYTE,
|
||||
OP_AND_HWORD,
|
||||
OP_AND_WORD,
|
||||
|
||||
OP_XOR_BYTE,
|
||||
OP_XOR_HWORD,
|
||||
OP_XOR_WORD,
|
||||
|
||||
OP_EQ_BYTE,
|
||||
OP_EQ_HWORD,
|
||||
OP_EQ_WORD,
|
||||
|
||||
// Mathematical operations
|
||||
OP_PLUS_BYTE,
|
||||
OP_PLUS_HWORD,
|
||||
OP_PLUS_WORD,
|
||||
|
||||
OP_SUB_BYTE,
|
||||
OP_SUB_HWORD,
|
||||
OP_SUB_WORD,
|
||||
|
||||
OP_MULT_BYTE,
|
||||
OP_MULT_HWORD,
|
||||
OP_MULT_WORD,
|
||||
|
||||
// Comparison operations
|
||||
OP_LT_BYTE,
|
||||
OP_LT_CHAR,
|
||||
OP_LT_HWORD,
|
||||
OP_LT_INT,
|
||||
OP_LT_WORD,
|
||||
OP_LT_LONG,
|
||||
|
||||
OP_LTE_BYTE,
|
||||
OP_LTE_CHAR,
|
||||
OP_LTE_HWORD,
|
||||
OP_LTE_INT,
|
||||
OP_LTE_WORD,
|
||||
OP_LTE_LONG,
|
||||
|
||||
OP_GT_BYTE,
|
||||
OP_GT_CHAR,
|
||||
OP_GT_HWORD,
|
||||
OP_GT_INT,
|
||||
OP_GT_WORD,
|
||||
OP_GT_LONG,
|
||||
|
||||
OP_GTE_BYTE,
|
||||
OP_GTE_CHAR,
|
||||
OP_GTE_HWORD,
|
||||
OP_GTE_INT,
|
||||
OP_GTE_WORD,
|
||||
OP_GTE_LONG,
|
||||
|
||||
// Simple I/O
|
||||
OP_PRINT_BYTE,
|
||||
OP_PRINT_CHAR,
|
||||
OP_PRINT_HWORD,
|
||||
OP_PRINT_INT,
|
||||
OP_PRINT_WORD,
|
||||
OP_PRINT_LONG,
|
||||
|
||||
// Program control flow
|
||||
OP_JUMP_ABS,
|
||||
OP_JUMP_STACK,
|
||||
OP_JUMP_IF_BYTE,
|
||||
OP_JUMP_IF_HWORD,
|
||||
OP_JUMP_IF_WORD,
|
||||
|
||||
// Subroutines
|
||||
OP_CALL,
|
||||
OP_CALL_STACK,
|
||||
OP_RET,
|
||||
|
||||
// Should not be an opcode
|
||||
NUMBER_OF_OPCODES,
|
||||
OP_HALT = 0b11111111, // top of the byte is a HALT
|
||||
} opcode_t;
|
||||
|
||||
size_t opcode_bytecode_size(opcode_t);
|
||||
const char *opcode_as_cstr(opcode_t);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
opcode_t opcode;
|
||||
data_t operand;
|
||||
} inst_t;
|
||||
|
||||
/**
|
||||
@brief Serialise an instruction into a byte buffer
|
||||
|
||||
@details Given an instruction and a suitably sized byte buffer,
|
||||
write the bytecode for the instruction into the buffer. NOTE: This
|
||||
function does NOT check the bounds of `bytes` i.e. we assume the
|
||||
caller has created a suitably sized buffer.
|
||||
|
||||
@param[inst] Instruction to serialise
|
||||
@param[bytes] Buffer to write on
|
||||
|
||||
@return[size_t] Number of bytes written to `bytes`.
|
||||
*/
|
||||
size_t inst_write_bytecode(inst_t inst, byte_t *bytes);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
READ_ERR_INVALID_OPCODE = -1,
|
||||
READ_ERR_OPERAND_NO_FIT = -2,
|
||||
READ_ERR_EXPECTED_MORE = -3,
|
||||
READ_ERR_END = -4
|
||||
} read_err_t;
|
||||
|
||||
/**
|
||||
@brief Deserialise an instruction from a bytecode buffer
|
||||
|
||||
@details Given a buffer of bytes, deserialise an instruction,
|
||||
storing the result in the pointer given. The number of bytes read
|
||||
in the buffer is returned, which should be opcode_bytecode_size().
|
||||
NOTE: If bytes is not suitably sized for the instruction expected
|
||||
or it is not well formed i.e. not the right schema then a negative
|
||||
number is returned.
|
||||
|
||||
@param[inst] Pointer to instruction which will store result
|
||||
@param[bytes] Bytecode buffer to deserialise
|
||||
@param[size_bytes] Number of bytes in buffer
|
||||
|
||||
@return[int] Number of bytes read. If negative then an error
|
||||
occurred in deserialisation (either buffer was not suitably sized
|
||||
or instruction was not well formed) so any result must be
|
||||
considered invalid.
|
||||
*/
|
||||
int inst_read_bytecode(inst_t *inst, byte_t *bytes, size_t size_bytes);
|
||||
|
||||
void inst_print(inst_t, FILE *);
|
||||
|
||||
size_t inst_bytecode_size(inst_t);
|
||||
void inst_write_bytecode(inst_t, darr_t *);
|
||||
void insts_write_bytecode(inst_t *, size_t, darr_t *);
|
||||
// Here the dynamic array is a preloaded buffer of bytes, where
|
||||
// darr.available is the number of overall bytes and used is the
|
||||
// cursor (where we are in the buffer).
|
||||
inst_t inst_read_bytecode(darr_t *);
|
||||
inst_t *insts_read_bytecode(darr_t *, size_t *);
|
||||
typedef struct
|
||||
{
|
||||
word_t start_address;
|
||||
word_t count;
|
||||
inst_t *instructions;
|
||||
} prog_t;
|
||||
|
||||
void insts_write_bytecode_file(inst_t *, size_t, FILE *);
|
||||
inst_t *insts_read_bytecode_file(FILE *, size_t *);
|
||||
#define PROG_HEADER_SIZE (WORD_SIZE * 2)
|
||||
|
||||
// Write the entire program as bytecode
|
||||
void prog_write_bytecode(prog_t *, darr_t *);
|
||||
// Only append the instructions as bytecode
|
||||
void prog_append_bytecode(prog_t *, darr_t *);
|
||||
// Read an entire program as bytecode
|
||||
prog_t *prog_read_bytecode(darr_t *);
|
||||
size_t prog_bytecode_size(prog_t);
|
||||
|
||||
void prog_write_file(prog_t *, FILE *);
|
||||
prog_t *prog_read_file(FILE *);
|
||||
size_t prog_write_bytecode(prog_t program, byte_t *bytes, size_t size_bytes);
|
||||
|
||||
#define INST_NOOP ((inst_t){0})
|
||||
#define INST_HALT ((inst_t){.opcode = OP_HALT})
|
||||
size_t prog_read_header(prog_t *program, byte_t *bytes, size_t size_bytes);
|
||||
|
||||
#define INST_PUSH(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_PUSH_##TYPE, .operand = D##TYPE(OP)})
|
||||
typedef struct
|
||||
{
|
||||
read_err_t type;
|
||||
size_t index;
|
||||
} read_err_prog_t;
|
||||
|
||||
#define INST_MOV(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_MOV_##TYPE, .operand = D##TYPE(OP)})
|
||||
read_err_prog_t prog_read_instructions(prog_t *program, size_t *size_bytes_read,
|
||||
byte_t *bytes, size_t size_bytes);
|
||||
|
||||
#define INST_POP(TYPE) ((inst_t){.opcode = OP_POP_##TYPE})
|
||||
|
||||
#define INST_PUSH_REG(TYPE, REG) \
|
||||
((inst_t){.opcode = OP_PUSH_REGISTER_##TYPE, .operand = D##TYPE(REG)})
|
||||
|
||||
#define INST_DUP(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_DUP_##TYPE, .operand = DWORD(OP)})
|
||||
|
||||
#define INST_MALLOC(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_MALLOC_##TYPE, .operand = DWORD(OP)})
|
||||
#define INST_MALLOC_STACK(TYPE) ((inst_t){.opcode = OP_MALLOC_STACK_##TYPE})
|
||||
#define INST_MSET(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_MSET_##TYPE, .operand = DWORD(OP)})
|
||||
#define INST_MSET_STACK(TYPE) ((inst_t){.opcode = OP_MSET_STACK_##TYPE})
|
||||
#define INST_MGET(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_MGET_##TYPE, .operand = DWORD(OP)})
|
||||
#define INST_MGET_STACK(TYPE) ((inst_t){.opcode = OP_MGET_STACK_##TYPE})
|
||||
#define INST_MDELETE ((inst_t){.opcode = OP_MDELETE})
|
||||
#define INST_MSIZE ((inst_t){.opcode = OP_MSIZE})
|
||||
|
||||
#define INST_NOT(TYPE) ((inst_t){.opcode = OP_NOT_##TYPE})
|
||||
#define INST_OR(TYPE) ((inst_t){.opcode = OP_OR_##TYPE})
|
||||
#define INST_AND(TYPE) ((inst_t){.opcode = OP_AND_##TYPE})
|
||||
#define INST_XOR(TYPE) ((inst_t){.opcode = OP_XOR_##TYPE})
|
||||
#define INST_EQ(TYPE) ((inst_t){.opcode = OP_EQ_##TYPE})
|
||||
#define INST_LT(TYPE) ((inst_t){.opcode = OP_LT_##TYPE})
|
||||
#define INST_LTE(TYPE) ((inst_t){.opcode = OP_LTE_##TYPE})
|
||||
#define INST_GT(TYPE) ((inst_t){.opcode = OP_GT_##TYPE})
|
||||
#define INST_GTE(TYPE) ((inst_t){.opcode = OP_GTE_##TYPE})
|
||||
#define INST_PLUS(TYPE) ((inst_t){.opcode = OP_PLUS_##TYPE})
|
||||
#define INST_SUB(TYPE) ((inst_t){.opcode = OP_SUB_##TYPE})
|
||||
#define INST_MULT(TYPE) ((inst_t){.opcode = OP_MULT_##TYPE})
|
||||
|
||||
#define INST_JUMP_ABS(OP) \
|
||||
((inst_t){.opcode = OP_JUMP_ABS, .operand = DWORD(OP)})
|
||||
#define INST_JUMP_STACK ((inst_t){.opcode = OP_JUMP_STACK})
|
||||
#define INST_JUMP_IF(TYPE, OP) \
|
||||
((inst_t){.opcode = OP_JUMP_IF_##TYPE, .operand = DWORD(OP)})
|
||||
#define INST_CALL(OP) ((inst_t){.opcode = OP_CALL, .operand = DWORD(OP)})
|
||||
#define INST_CALL_STACK ((inst_t){.opcode = OP_CALL_STACK})
|
||||
#define INST_RET ((inst_t){.opcode = OP_RET})
|
||||
|
||||
#define INST_PRINT(TYPE) ((inst_t){.opcode = OP_PRINT_##TYPE})
|
||||
#endif
|
||||
|
||||
173
lib/prog.h
173
lib/prog.h
@@ -1,173 +0,0 @@
|
||||
/* Copyright (C) 2024 Aryadev Chavali
|
||||
|
||||
* You may distribute and modify this code under the terms of the
|
||||
* GPLv2 license. You should have received a copy of the GPLv2
|
||||
* license with this file. If not, please write to:
|
||||
* aryadev@aryadevchavali.com.
|
||||
|
||||
* Created: 2024-04-14
|
||||
* Author: Aryadev Chavali
|
||||
* Description: Structures for both instructions and programs for the
|
||||
* virtual machine
|
||||
*/
|
||||
|
||||
#ifndef PROG_H
|
||||
#define PROG_H
|
||||
|
||||
#include <lib/base.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OP_NOOP = 0,
|
||||
|
||||
// Dealing with data and registers
|
||||
OP_PUSH_BYTE,
|
||||
OP_PUSH_HWORD,
|
||||
OP_PUSH_WORD,
|
||||
|
||||
OP_POP_BYTE,
|
||||
OP_POP_HWORD,
|
||||
OP_POP_WORD,
|
||||
|
||||
OP_PUSH_REGISTER_BYTE,
|
||||
OP_PUSH_REGISTER_HWORD,
|
||||
OP_PUSH_REGISTER_WORD,
|
||||
|
||||
OP_MOV_BYTE,
|
||||
OP_MOV_HWORD,
|
||||
OP_MOV_WORD,
|
||||
|
||||
OP_DUP_BYTE,
|
||||
OP_DUP_HWORD,
|
||||
OP_DUP_WORD,
|
||||
|
||||
// Dealing with the heap
|
||||
OP_MALLOC_BYTE,
|
||||
OP_MALLOC_HWORD,
|
||||
OP_MALLOC_WORD,
|
||||
|
||||
OP_MALLOC_STACK_BYTE,
|
||||
OP_MALLOC_STACK_HWORD,
|
||||
OP_MALLOC_STACK_WORD,
|
||||
|
||||
OP_MSET_BYTE,
|
||||
OP_MSET_HWORD,
|
||||
OP_MSET_WORD,
|
||||
|
||||
OP_MSET_STACK_BYTE,
|
||||
OP_MSET_STACK_HWORD,
|
||||
OP_MSET_STACK_WORD,
|
||||
|
||||
OP_MGET_BYTE,
|
||||
OP_MGET_HWORD,
|
||||
OP_MGET_WORD,
|
||||
|
||||
OP_MGET_STACK_BYTE,
|
||||
OP_MGET_STACK_HWORD,
|
||||
OP_MGET_STACK_WORD,
|
||||
|
||||
OP_MDELETE,
|
||||
OP_MSIZE,
|
||||
|
||||
// Boolean operations
|
||||
OP_NOT_BYTE,
|
||||
OP_NOT_HWORD,
|
||||
OP_NOT_WORD,
|
||||
|
||||
OP_OR_BYTE,
|
||||
OP_OR_HWORD,
|
||||
OP_OR_WORD,
|
||||
|
||||
OP_AND_BYTE,
|
||||
OP_AND_HWORD,
|
||||
OP_AND_WORD,
|
||||
|
||||
OP_XOR_BYTE,
|
||||
OP_XOR_HWORD,
|
||||
OP_XOR_WORD,
|
||||
|
||||
OP_EQ_BYTE,
|
||||
OP_EQ_HWORD,
|
||||
OP_EQ_WORD,
|
||||
|
||||
// Mathematical operations
|
||||
OP_PLUS_BYTE,
|
||||
OP_PLUS_HWORD,
|
||||
OP_PLUS_WORD,
|
||||
|
||||
OP_SUB_BYTE,
|
||||
OP_SUB_HWORD,
|
||||
OP_SUB_WORD,
|
||||
|
||||
OP_MULT_BYTE,
|
||||
OP_MULT_HWORD,
|
||||
OP_MULT_WORD,
|
||||
|
||||
// Comparison operations
|
||||
OP_LT_BYTE,
|
||||
OP_LT_CHAR,
|
||||
OP_LT_HWORD,
|
||||
OP_LT_INT,
|
||||
OP_LT_WORD,
|
||||
OP_LT_LONG,
|
||||
|
||||
OP_LTE_BYTE,
|
||||
OP_LTE_CHAR,
|
||||
OP_LTE_HWORD,
|
||||
OP_LTE_INT,
|
||||
OP_LTE_WORD,
|
||||
OP_LTE_LONG,
|
||||
|
||||
OP_GT_BYTE,
|
||||
OP_GT_CHAR,
|
||||
OP_GT_HWORD,
|
||||
OP_GT_INT,
|
||||
OP_GT_WORD,
|
||||
OP_GT_LONG,
|
||||
|
||||
OP_GTE_BYTE,
|
||||
OP_GTE_CHAR,
|
||||
OP_GTE_HWORD,
|
||||
OP_GTE_INT,
|
||||
OP_GTE_WORD,
|
||||
OP_GTE_LONG,
|
||||
|
||||
// Simple I/O
|
||||
OP_PRINT_BYTE,
|
||||
OP_PRINT_CHAR,
|
||||
OP_PRINT_HWORD,
|
||||
OP_PRINT_INT,
|
||||
OP_PRINT_WORD,
|
||||
OP_PRINT_LONG,
|
||||
|
||||
// Program control flow
|
||||
OP_JUMP_ABS,
|
||||
OP_JUMP_STACK,
|
||||
OP_JUMP_IF_BYTE,
|
||||
OP_JUMP_IF_HWORD,
|
||||
OP_JUMP_IF_WORD,
|
||||
|
||||
// Subroutines
|
||||
OP_CALL,
|
||||
OP_CALL_STACK,
|
||||
OP_RET,
|
||||
|
||||
// Should not be an opcode
|
||||
NUMBER_OF_OPCODES,
|
||||
OP_HALT = 0b11111111, // top of the byte is a HALT
|
||||
} opcode_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
opcode_t opcode;
|
||||
data_t operand;
|
||||
} inst_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
word_t start_address;
|
||||
word_t count;
|
||||
inst_t instructions[];
|
||||
} prog_t;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user