LE ordering in data oriented instructions, make more macros

Little Endian ordering is now ensured for stack based and register
based operations e.g.  PUSH now pushes datums in LE ordering onto the
stack, POP pops data on the stack, converting from LE ordering to host
ordering.

More functions in the runtime are now macro defined, so there's less
code while still maintaining the lookup table.

--------------------------------------------------------------------------------

What LE ordering means for actual source code is that I utilise the
convert_*_to_* functions for PUSH and POP.  All other data oriented
instructions are completely internal to the system and any data
storage must be in Little Endian.  So MOV, PUSH-REGISTER and DUP can
all directly memcpy the data around the system without needing to
consider endian at all.

Macros were a necessity after I felt the redefinition of push for 4
different data types was too much.  Most functions are essentially
copies for each datatype.  Lisp macros would make this so easy :(
This commit is contained in:
2024-06-20 02:30:49 +01:00
parent 8984c97d33
commit 4ad3d46996

View File

@@ -361,374 +361,33 @@ err_t vm_push_byte(vm_t *vm, data_t b)
return ERR_OK;
}
err_t vm_push_short(vm_t *vm, data_t f)
{
if (vm->stack.ptr + SHORT_SIZE >= vm->stack.max)
return ERR_STACK_OVERFLOW;
byte_t bytes[SHORT_SIZE] = {0};
convert_short_to_bytes(f.as_short, bytes);
for (size_t i = 0; i < SHORT_SIZE; ++i)
{
byte_t b = bytes[SHORT_SIZE - i - 1];
err_t err = vm_push_byte(vm, DBYTE(b));
if (err)
return err;
/* Pushing value onto the stack
Convert the datum into LE ordering then push onto the stack byte by
byte. This means that the MSB of any datum is at the top of the
stack by the end of the procedure. If we look at the stack from
top down the number is in big endian, but from bottom up the number
is in little endian.
This is the same as just converting some datum into LE ordered
bytes, which convert_<TYPE>_to_bytes does.
Consider halfword 0x89ABCDEF.
1) Bytes are {0xEF, 0xCD, 0xAB, 0x89}
2) Stack top is 0x89 */
#define VM_PUSH_CONSTR(TYPE, TYPE_CAP) \
err_t vm_push_##TYPE(vm_t *vm, data_t f) \
{ \
if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
return ERR_STACK_OVERFLOW; \
convert_##TYPE##_to_bytes(f.as_##TYPE, vm->stack.data + vm->stack.ptr); \
vm->stack.ptr += TYPE_CAP##_SIZE; \
return ERR_OK; \
}
return ERR_OK;
}
err_t vm_push_hword(vm_t *vm, data_t f)
{
if (vm->stack.ptr + HWORD_SIZE >= vm->stack.max)
return ERR_STACK_OVERFLOW;
byte_t bytes[HWORD_SIZE] = {0};
convert_hword_to_bytes(f.as_hword, bytes);
for (size_t i = 0; i < HWORD_SIZE; ++i)
{
byte_t b = bytes[HWORD_SIZE - i - 1];
err_t err = vm_push_byte(vm, DBYTE(b));
if (err)
return err;
}
return ERR_OK;
}
err_t vm_push_word(vm_t *vm, data_t w)
{
if (vm->stack.ptr + WORD_SIZE >= vm->stack.max)
return ERR_STACK_OVERFLOW;
byte_t bytes[WORD_SIZE] = {0};
convert_word_to_bytes(w.as_word, bytes);
for (size_t i = 0; i < WORD_SIZE; ++i)
{
byte_t b = bytes[WORD_SIZE - i - 1];
err_t err = vm_push_byte(vm, DBYTE(b));
if (err)
return err;
}
return ERR_OK;
}
err_t vm_push_byte_register(vm_t *vm, word_t reg)
{
if (reg > vm->registers.used)
return ERR_INVALID_REGISTER_BYTE;
// Interpret each word based register as 8 byte registers
byte_t b = vm->registers.data[reg];
return vm_push_byte(vm, DBYTE(b));
}
err_t vm_push_short_register(vm_t *vm, word_t reg)
{
if (reg > (vm->registers.used / SHORT_SIZE))
return ERR_INVALID_REGISTER_SHORT;
// Interpret the bytes at point reg * SHORT_SIZE as an short
short_t sh = *(short_t *)(vm->registers.data + (reg * SHORT_SIZE));
return vm_push_short(vm, DSHORT(sh));
}
err_t vm_push_hword_register(vm_t *vm, word_t reg)
{
if (reg > (vm->registers.used / HWORD_SIZE))
return ERR_INVALID_REGISTER_HWORD;
// Interpret the bytes at point reg * HWORD_SIZE as an hword
hword_t hw = *(hword_t *)(vm->registers.data + (reg * HWORD_SIZE));
return vm_push_hword(vm, DHWORD(hw));
}
err_t vm_push_word_register(vm_t *vm, word_t reg)
{
if (reg > (vm->registers.used / WORD_SIZE))
return ERR_INVALID_REGISTER_WORD;
return vm_push_word(vm, DWORD(VM_NTH_REGISTER(vm->registers, reg)));
}
err_t vm_mov_byte(vm_t *vm, word_t reg)
{
if (reg >= vm->registers.used)
{
// Expand capacity
darr_ensure_capacity(&vm->registers, reg - vm->registers.used);
vm->registers.used = MAX(vm->registers.used, reg + 1);
}
data_t ret = {0};
err_t err = vm_pop_byte(vm, &ret);
if (err)
return err;
vm->registers.data[reg] = ret.as_byte;
return ERR_OK;
}
err_t vm_mov_short(vm_t *vm, word_t reg)
{
if (reg >= (vm->registers.used / SHORT_SIZE))
{
// Expand capacity till we can ensure that this is a valid
// register to use
// Number of shorts needed ontop of what is allocated:
const size_t shorts = (reg - (vm->registers.used / SHORT_SIZE));
// Number of bytes needed ontop of what is allocated
const size_t diff = (shorts + 1) * SHORT_SIZE;
darr_ensure_capacity(&vm->registers, diff);
vm->registers.used = MAX(vm->registers.used, (reg + 1) * SHORT_SIZE);
}
data_t ret = {0};
err_t err = vm_pop_short(vm, &ret);
if (err)
return err;
// Here we treat vm->registers as a set of shorts
short_t *short_ptr = (short_t *)(vm->registers.data + (reg * SHORT_SIZE));
*short_ptr = ret.as_short;
return ERR_OK;
}
err_t vm_mov_hword(vm_t *vm, word_t reg)
{
if (reg >= (vm->registers.used / HWORD_SIZE))
{
// Expand capacity till we can ensure that this is a valid
// register to use
// Number of hwords needed ontop of what is allocated:
const size_t hwords = (reg - (vm->registers.used / HWORD_SIZE));
// Number of bytes needed ontop of what is allocated
const size_t diff = (hwords + 1) * HWORD_SIZE;
darr_ensure_capacity(&vm->registers, diff);
vm->registers.used = MAX(vm->registers.used, (reg + 1) * HWORD_SIZE);
}
data_t ret = {0};
err_t err = vm_pop_hword(vm, &ret);
if (err)
return err;
// Here we treat vm->registers as a set of hwords
hword_t *hword_ptr = (hword_t *)(vm->registers.data + (reg * HWORD_SIZE));
*hword_ptr = ret.as_hword;
return ERR_OK;
}
err_t vm_mov_word(vm_t *vm, word_t reg)
{
if (reg >= (vm->registers.used / WORD_SIZE))
{
// Number of hwords needed ontop of what is allocated:
const size_t words = (reg - (vm->registers.used / WORD_SIZE));
// Number of bytes needed ontop of what is allocated
const size_t diff = (words + 1) * WORD_SIZE;
darr_ensure_capacity(&vm->registers, diff);
vm->registers.used = MAX(vm->registers.used, (reg + 1) * WORD_SIZE);
}
else if (vm->stack.ptr < WORD_SIZE)
return ERR_STACK_UNDERFLOW;
data_t ret = {0};
err_t err = vm_pop_word(vm, &ret);
if (err)
return err;
((word_t *)(vm->registers.data))[reg] = ret.as_word;
return ERR_OK;
}
err_t vm_dup_byte(vm_t *vm, word_t w)
{
if (vm->stack.ptr < w + 1)
return ERR_STACK_UNDERFLOW;
return vm_push_byte(vm, DBYTE(vm->stack.data[vm->stack.ptr - 1 - w]));
}
err_t vm_dup_short(vm_t *vm, word_t w)
{
if (vm->stack.ptr < SHORT_SIZE * (w + 1))
return ERR_STACK_UNDERFLOW;
byte_t bytes[SHORT_SIZE] = {0};
for (size_t i = 0; i < SHORT_SIZE; ++i)
bytes[SHORT_SIZE - i - 1] =
vm->stack.data[vm->stack.ptr - (SHORT_SIZE * (w + 1)) + i];
return vm_push_short(vm, DSHORT(convert_bytes_to_short(bytes)));
}
err_t vm_dup_hword(vm_t *vm, word_t w)
{
if (vm->stack.ptr < HWORD_SIZE * (w + 1))
return ERR_STACK_UNDERFLOW;
byte_t bytes[HWORD_SIZE] = {0};
for (size_t i = 0; i < HWORD_SIZE; ++i)
bytes[HWORD_SIZE - i - 1] =
vm->stack.data[vm->stack.ptr - (HWORD_SIZE * (w + 1)) + i];
return vm_push_hword(vm, DHWORD(convert_bytes_to_hword(bytes)));
}
err_t vm_dup_word(vm_t *vm, word_t w)
{
if (vm->stack.ptr < WORD_SIZE * (w + 1))
return ERR_STACK_UNDERFLOW;
byte_t bytes[WORD_SIZE] = {0};
for (size_t i = 0; i < WORD_SIZE; ++i)
bytes[WORD_SIZE - i - 1] =
vm->stack.data[vm->stack.ptr - (WORD_SIZE * (w + 1)) + i];
return vm_push_word(vm, DWORD(convert_bytes_to_word(bytes)));
}
err_t vm_malloc_byte(vm_t *vm, word_t n)
{
page_t *page = heap_allocate(&vm->heap, n);
return vm_push_word(vm, DWORD((word_t)page));
}
err_t vm_malloc_short(vm_t *vm, word_t n)
{
page_t *page = heap_allocate(&vm->heap, n * SHORT_SIZE);
return vm_push_word(vm, DWORD((word_t)page));
}
err_t vm_malloc_hword(vm_t *vm, word_t n)
{
page_t *page = heap_allocate(&vm->heap, n * HWORD_SIZE);
return vm_push_word(vm, DWORD((word_t)page));
}
err_t vm_malloc_word(vm_t *vm, word_t n)
{
page_t *page = heap_allocate(&vm->heap, n * WORD_SIZE);
return vm_push_word(vm, DWORD((word_t)page));
}
err_t vm_mset_byte(vm_t *vm, word_t nth)
{
// Stack layout should be [BYTE, PTR]
data_t byte = {0};
err_t err = vm_pop_byte(vm, &byte);
if (err)
return err;
data_t ptr = {0};
err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (nth >= page->available)
return ERR_OUT_OF_BOUNDS;
page->data[nth] = byte.as_byte;
return ERR_OK;
}
err_t vm_mset_short(vm_t *vm, word_t nth)
{
// Stack layout should be [SHORT, PTR]
data_t byte = {0};
err_t err = vm_pop_short(vm, &byte);
if (err)
return err;
data_t ptr = {0};
err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (nth >= (page->available / SHORT_SIZE))
return ERR_OUT_OF_BOUNDS;
((short_t *)page->data)[nth] = byte.as_short;
return ERR_OK;
}
err_t vm_mset_hword(vm_t *vm, word_t nth)
{
// Stack layout should be [HWORD, PTR]
data_t byte = {0};
err_t err = vm_pop_hword(vm, &byte);
if (err)
return err;
data_t ptr = {0};
err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (nth >= (page->available / HWORD_SIZE))
return ERR_OUT_OF_BOUNDS;
((hword_t *)page->data)[nth] = byte.as_hword;
return ERR_OK;
}
err_t vm_mset_word(vm_t *vm, word_t nth)
{
// Stack layout should be [WORD, PTR]
data_t byte = {0};
err_t err = vm_pop_word(vm, &byte);
if (err)
return err;
data_t ptr = {0};
err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (nth >= (page->available / WORD_SIZE))
return ERR_OUT_OF_BOUNDS;
((word_t *)page->data)[nth] = byte.as_word;
return ERR_OK;
}
err_t vm_mget_byte(vm_t *vm, word_t n)
{
// Stack layout should be [PTR]
data_t ptr = {0};
err_t err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (n >= page->available)
return ERR_OUT_OF_BOUNDS;
return vm_push_byte(vm, DBYTE(page->data[n]));
}
err_t vm_mget_short(vm_t *vm, word_t n)
{
// Stack layout should be [PTR]
data_t ptr = {0};
err_t err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (n >= (page->available / SHORT_SIZE))
return ERR_OUT_OF_BOUNDS;
return vm_push_short(vm, DSHORT(((short_t *)page->data)[n]));
}
err_t vm_mget_hword(vm_t *vm, word_t n)
{
// Stack layout should be [PTR]
data_t ptr = {0};
err_t err = vm_pop_word(vm, &ptr);
if (err)
return err;
page_t *page = (page_t *)ptr.as_word;
if (n >= (page->available / HWORD_SIZE))
return ERR_OUT_OF_BOUNDS;
return vm_push_hword(vm, DHWORD(((hword_t *)page->data)[n]));
}
err_t vm_mget_word(vm_t *vm, word_t n)
{
// Stack layout should be [PTR]
data_t ptr = {0};
err_t err = vm_pop_word(vm, &ptr);
if (err)
return err;
printf("%lx\n", ptr.as_word);
page_t *page = (page_t *)ptr.as_word;
if (n >= (page->available / WORD_SIZE))
return ERR_OUT_OF_BOUNDS;
return vm_push_word(vm, DWORD(((word_t *)page->data)[n]));
}
VM_PUSH_CONSTR(short, SHORT)
VM_PUSH_CONSTR(hword, HWORD)
VM_PUSH_CONSTR(word, WORD)
err_t vm_pop_byte(vm_t *vm, data_t *ret)
{
@@ -738,50 +397,183 @@ err_t vm_pop_byte(vm_t *vm, data_t *ret)
return ERR_OK;
}
err_t vm_pop_short(vm_t *vm, data_t *ret)
{
if (vm->stack.ptr < SHORT_SIZE)
return ERR_STACK_UNDERFLOW;
byte_t bytes[SHORT_SIZE] = {0};
for (size_t i = 0; i < SHORT_SIZE; ++i)
{
data_t b = {0};
vm_pop_byte(vm, &b);
bytes[i] = b.as_byte;
/* Popping a value from the stack and storing it
Since data is pushed in little endian format onto the stack, such
that the MSB is the top of the stack, copying N bytes from (top -
N) to N gives us the exact bytes of the datum in little endian.
Convert that into host order and we're done.
Consider halfword 0x89ABCDEF. When pushed onto the stack, looking
at the stack from bottom up there are the values {0xEF, 0xCD, 0xAB,
0x89} where 0x89 is at the top of the stack.
1) Bytes are copied into buffer {0xEF, 0xCD, 0xAB, 0x89}
2) Value is converted into host order datum */
#define VM_POP_CONSTR(TYPE, TYPE_CAP) \
err_t vm_pop_##TYPE(vm_t *vm, data_t *ret) \
{ \
if (vm->stack.ptr < TYPE_CAP##_SIZE) \
return ERR_STACK_UNDERFLOW; \
byte_t bytes[TYPE_CAP##_SIZE] = {0}; \
memcpy(bytes, vm->stack.data + (vm->stack.ptr - TYPE_CAP##_SIZE), \
TYPE_CAP##_SIZE); \
*ret = D##TYPE_CAP(convert_bytes_to_##TYPE(bytes)); \
return ERR_OK; \
}
*ret = DSHORT(convert_bytes_to_short(bytes));
return ERR_OK;
VM_POP_CONSTR(short, SHORT)
VM_POP_CONSTR(hword, HWORD)
VM_POP_CONSTR(word, WORD)
/* Pushing the value stored at a register onto the stack.
MOV stores any set of bytes on the stack (which is a datum in
Little Endian) in the same order in the register i.e. a datum X
will be ordered the exact same way on the stack as in a register.
So instead of getting the value from a register, converting it to
host order, then pushing the datum (which converts it back to
little endian), let's just memcpy the value from the register to
the stack.
Note this means that we check for stack overflow here.
*/
#define VM_PUSH_REGISTER_CONSTR(TYPE, TYPE_CAP) \
err_t vm_push_##TYPE##_register(vm_t *vm, word_t reg) \
{ \
if (reg > (vm->registers.used / TYPE_CAP##_SIZE)) \
return ERR_INVALID_REGISTER_##TYPE_CAP; \
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
return ERR_STACK_OVERFLOW; \
memcpy(vm->stack.data + vm->stack.ptr, \
vm->registers.data + (reg * TYPE_CAP##_SIZE), TYPE_CAP##_SIZE); \
vm->stack.ptr += TYPE_CAP##_SIZE; \
return ERR_OK; \
}
VM_PUSH_REGISTER_CONSTR(byte, BYTE)
VM_PUSH_REGISTER_CONSTR(short, SHORT)
VM_PUSH_REGISTER_CONSTR(hword, HWORD)
VM_PUSH_REGISTER_CONSTR(word, WORD)
/* Move a value from the stack into a specific register.
Values are stored in LE order on the stack. Values in registers
should be in LE order as well for consistency. Which means if, for
a value of N bytes, the array stack[top - N:top] is copied into the
register directly, we're done.
*/
#define VM_MOV_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mov_##TYPE(vm_t *vm, word_t reg) \
{ \
if (reg >= (vm->registers.used / TYPE_CAP##_SIZE)) \
{ \
const size_t diff = \
((reg - (vm->registers.used / TYPE_CAP##_SIZE)) + 1) * \
TYPE_CAP##_SIZE; \
darr_ensure_capacity(&vm->registers, diff); \
vm->registers.used = \
MAX(vm->registers.used, (reg + 1) * TYPE_CAP##_SIZE); \
} \
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
return ERR_STACK_OVERFLOW; \
memcpy(vm->registers.data + (reg * TYPE_CAP##_SIZE), \
vm->stack.data + vm->stack.ptr - (TYPE_CAP##_SIZE), \
TYPE_CAP##_SIZE); \
vm->stack.ptr -= TYPE_CAP##_SIZE; \
return ERR_OK; \
}
VM_MOV_CONSTR(byte, BYTE)
VM_MOV_CONSTR(short, SHORT)
VM_MOV_CONSTR(hword, HWORD)
VM_MOV_CONSTR(word, WORD)
err_t vm_dup_byte(vm_t *vm, word_t w)
{
if (vm->stack.ptr < w + 1)
return ERR_STACK_UNDERFLOW;
return vm_push_byte(vm, DBYTE(vm->stack.data[vm->stack.ptr - 1 - w]));
}
err_t vm_pop_hword(vm_t *vm, data_t *ret)
{
if (vm->stack.ptr < HWORD_SIZE)
return ERR_STACK_UNDERFLOW;
byte_t bytes[HWORD_SIZE] = {0};
for (size_t i = 0; i < HWORD_SIZE; ++i)
{
data_t b = {0};
vm_pop_byte(vm, &b);
bytes[i] = b.as_byte;
#define VM_DUP_CONSTR(TYPE, TYPE_CAP) \
err_t vm_dup_##TYPE(vm_t *vm, word_t w) \
{ \
if (vm->stack.ptr < TYPE_CAP##_SIZE * (w + 1)) \
return ERR_STACK_UNDERFLOW; \
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
return ERR_STACK_OVERFLOW; \
memcpy(vm->stack.data + vm->stack.ptr, \
vm->stack.data + vm->stack.ptr - (TYPE_CAP##_SIZE * (w + 1)), \
TYPE_CAP##_SIZE); \
vm->stack.ptr += TYPE_CAP##_SIZE; \
return ERR_OK; \
}
*ret = DHWORD(convert_bytes_to_hword(bytes));
return ERR_OK;
}
err_t vm_pop_word(vm_t *vm, data_t *ret)
{
if (vm->stack.ptr < WORD_SIZE)
return ERR_STACK_UNDERFLOW;
byte_t bytes[WORD_SIZE] = {0};
for (size_t i = 0; i < WORD_SIZE; ++i)
{
data_t b = {0};
vm_pop_byte(vm, &b);
bytes[i] = b.as_byte;
VM_DUP_CONSTR(short, SHORT)
VM_DUP_CONSTR(hword, HWORD)
VM_DUP_CONSTR(word, WORD)
#define VM_MALLOC_CONSTR(TYPE, TYPE_CAP) \
err_t vm_malloc_##TYPE(vm_t *vm, word_t n) \
{ \
page_t *page = heap_allocate(&vm->heap, n * TYPE_CAP##_SIZE); \
return vm_push_word(vm, DWORD((word_t)page)); \
}
*ret = DWORD(convert_bytes_to_word(bytes));
return ERR_OK;
}
VM_MALLOC_CONSTR(byte, BYTE)
VM_MALLOC_CONSTR(short, SHORT)
VM_MALLOC_CONSTR(hword, HWORD)
VM_MALLOC_CONSTR(word, WORD)
#define VM_MSET_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mset_##TYPE(vm_t *vm, word_t nth) \
{ \
data_t object = {0}; \
err_t err = vm_pop_##TYPE(vm, &object); \
if (err) \
return err; \
data_t ptr = {0}; \
err = vm_pop_word(vm, &ptr); \
if (err) \
return err; \
page_t *page = (page_t *)ptr.as_word; \
if (nth >= (page->available / TYPE_CAP##_SIZE)) \
return ERR_OUT_OF_BOUNDS; \
DARR_AT(TYPE##_t, page->data, nth) = object.as_##TYPE; \
return ERR_OK; \
}
VM_MSET_CONSTR(byte, BYTE)
VM_MSET_CONSTR(short, SHORT)
VM_MSET_CONSTR(hword, HWORD)
VM_MSET_CONSTR(word, WORD)
#define VM_MGET_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mget_##TYPE(vm_t *vm, word_t n) \
{ \
data_t ptr = {0}; \
err_t err = vm_pop_word(vm, &ptr); \
if (err) \
return err; \
page_t *page = (page_t *)ptr.as_word; \
if (n >= (page->available / TYPE_CAP##_SIZE)) \
return ERR_OUT_OF_BOUNDS; \
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
return ERR_STACK_OVERFLOW; \
memcpy(vm->stack.data + vm->stack.ptr, \
page->data + (n * (TYPE_CAP##_SIZE)), TYPE_CAP##_SIZE); \
vm->stack.ptr += TYPE_CAP##_SIZE; \
return ERR_OK; \
}
VM_MGET_CONSTR(byte, BYTE)
VM_MGET_CONSTR(short, SHORT)
VM_MGET_CONSTR(hword, HWORD)
VM_MGET_CONSTR(word, WORD)
// TODO: rename this to something more appropriate
#define VM_MEMORY_STACK_CONSTR(ACTION, TYPE) \