Compare commits

...

10 Commits

Author SHA1 Message Date
Aryadev Chavali
30bed795fd New TODO on reworking the entire virtual machine
Some checks failed
C/C++ CI / build (push) Has been cancelled
C/C++ CI / test (push) Has been cancelled
I've had a revelation: the virtual machine shouldn't cater to any
specific dynamic or trend.  What I need the VM to do is provide a
basis where very useful features such as arithmetic of arbitrary sized
integers, basic control flow, memory management and file I/O are a bit
nicer than doing it myself in C.  Past that it can be as barebones as
necessary.

Previous development was done in tandem with the assembler, which
influenced how I designed the system.  I will no longer do this.  Here
I describe the creation of more generic opcodes and instructions.
These complicate the virtual machined data model but they are also
extensible, can be generalised to a wider array of use cases and can
be optimised by the virtual machine.

Instead, the assembler can use these generic instructions and make
porcelains over them to provide a nicer environment.  Take the new
PUSH opcode: since it can take an arbitrary payload of bytes and push
them all onto the stack, the assembler can provide porcelains for
shorts, half words and words as well as more interesting macros.
2024-07-10 19:37:02 +01:00
Aryadev Chavali
a408ccacb9 MSET pops n before data to set 2024-07-07 19:39:38 +01:00
Aryadev Chavali
a7f14c8f58 Remove indent in MESSAGE 2024-07-07 19:39:03 +01:00
Aryadev Chavali
7a5eee932a Moved logging macros to base.h and use them everywhere
The macros used in the testing library are actually useful everywhere
so may as well use them.
2024-07-07 03:16:42 +01:00
Aryadev Chavali
74c5746bff Reworked SPEC a ton 2024-07-07 03:04:19 +01:00
Aryadev Chavali
edf90780af Remove JUMP_STACK and CALL_STACK
Any program that generates addresses at runtime can be easily
expressed through fixed address jumps and calls.  No need for them.
2024-07-07 03:01:41 +01:00
Aryadev Chavali
ebdb1a948d Removed m*_stack_* opcodes, make them default behaviour
Memory operations used operands to encode positional/size arguments,
with stack variants in case the user wanted to programmatically do so.
In most large scale cases I don't see the non-stack variants being
used; many cases require using the stack for additions and
subtractions to create values such as indexes or sizes.  Therefore,
it's better to be stack-first.

One counter point is inline optimisation of code at runtime: if an
compile-time-known object is pushed then immediately used in an
operation, we can instead encode the value directly into an operand
based instruction which will speed up execution time because it's
slower to pop the value off the stack than have it available as part
of the instruction.
2024-07-07 03:01:06 +01:00
Aryadev Chavali
2b1e7a49d2 Added a clang-format file to the root
Copy of my personal Dotfiles but this helps in case anyone else wants
to contribute.
2024-06-28 16:21:58 +01:00
Aryadev Chavali
6014620baa Changed fill-column to 80, so more space for comments. 2024-06-28 16:11:01 +01:00
Aryadev Chavali
3a09beb582 Change license agreement terming to ensure version 2 only. 2024-06-28 16:07:15 +01:00
22 changed files with 749 additions and 609 deletions

30
.clang-format Normal file
View File

@@ -0,0 +1,30 @@
# .clang-format -*- mode: yaml; lexical-binding: t; -*-
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveBitFields: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: true
AllowShortFunctionsOnASingleLine: false
AllowShortLambdasOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: Yes
BasedOnStyle: LLVM
BreakBeforeBraces: Custom
BraceWrapping:
AfterEnum: true
AfterStruct: true
AfterFunction: true
AfterNamespace: true
AfterClass: true
AfterUnion: true
AfterControlStatement: true
AfterExternBlock: true
BeforeLambdaBody: true
BeforeCatch: true
BeforeElse: true
SplitEmptyFunction: true
IndentBraces: false
ColumnLimit: 80
IndentWidth: 2
NamespaceIndentation: All

View File

@@ -1,7 +1,7 @@
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")
((nil . ((+license/license-choice . "GPLv2")
((nil . ((+license/license-choice . "GNU General Public License Version 2")
(compile-command . "make all VERBOSE=2 RELEASE=0")))
(c-mode . ((mode . clang-format)
(eval . (eglot-ensure)))))

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-26
* Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
@@ -28,6 +27,16 @@
#define TERM_RED "\033[31m"
#define TERM_RESET "\033[0m"
#define MESSAGE(FILE, COLOUR, NAME, FORMAT, ...) \
fprintf(FILE, "[" COLOUR "%s" TERM_RESET "]: " FORMAT, NAME, __VA_ARGS__)
#define INFO(NAME, FORMAT, ...) \
MESSAGE(stdout, TERM_YELLOW, NAME, FORMAT, __VA_ARGS__)
#define FAIL(NAME, FORMAT, ...) \
MESSAGE(stderr, TERM_RED, NAME, FORMAT, __VA_ARGS__)
#define SUCCESS(NAME, FORMAT, ...) \
MESSAGE(stdout, TERM_GREEN, NAME, FORMAT, __VA_ARGS__)
// Flags for program behaviour (usually related to printing)
#ifndef VERBOSE
#define VERBOSE 0
@@ -92,8 +101,7 @@ typedef union
} data_t;
/**
@brief Enum of type tags for the data_t structure to provide
context.
@brief Enum of type tags for the data_t structure to provide context.
*/
typedef enum
{
@@ -117,11 +125,10 @@ static const hword_t __i = 0xFFFF0000;
#endif
/**
@brief Safely subtract SUB from W, where both are words (64 bit
integers).
@brief Safely subtract SUB from W, where both are words (64 bit integers).
@details In case of underflow (i.e. where W - SUB < 0) returns 0
instead of the underflowed result.
@details In case of underflow (i.e. where W - SUB < 0) returns 0 instead of
the underflowed result.
*/
#define WORD_SAFE_SUB(W, SUB) ((W) > (SUB) ? ((W) - (SUB)) : 0)
@@ -142,17 +149,15 @@ static const hword_t __i = 0xFFFF0000;
/**
@brief Return the Nth half word of WORD.
@details N should range from 0 to 1 as there are 2 half words in a
word
@details N should range from 0 to 1 as there are 2 half words in a word
*/
#define WORD_NTH_HWORD(WORD, N) (((WORD) >> ((N) * 32)) & 0xFFFFFFFF)
/**
@brief Convert a buffer of bytes to a short
@details It is assumed that the buffer of bytes are in virtual
machine byte code format (little endian) and that they are at least
SHORT_SIZE in size.
@details It is assumed that the buffer of bytes are in virtual machine byte
code format (little endian) and that they are at least SHORT_SIZE in size.
*/
short_t convert_bytes_to_short(const byte_t *buffer);
@@ -162,17 +167,16 @@ short_t convert_bytes_to_short(const byte_t *buffer);
@param s: Short to convert
@param buffer: Buffer to store into. It is assumed that the buffer
has at least SHORT_SIZE space.
@param buffer: Buffer to store into. It is assumed that the buffer has at
least SHORT_SIZE space.
*/
void convert_short_to_bytes(const short_t s, byte_t *buffer);
/**
@brief Convert a buffer of bytes to a half word.
@details It is assumed that the buffer of bytes are in virtual
machine byte code format (little endian) and that they are at least
HWORD_SIZE in size.
@details It is assumed that the buffer of bytes are in virtual machine byte
code format (little endian) and that they are at least HWORD_SIZE in size.
*/
hword_t convert_bytes_to_hword(const byte_t *buffer);
@@ -182,36 +186,34 @@ hword_t convert_bytes_to_hword(const byte_t *buffer);
@param h: Half word to convert
@param buffer: Buffer to store into. It is assumed that the buffer
has at least HWORD_SIZE space.
@param buffer: Buffer to store into. It is assumed that the buffer has at
least HWORD_SIZE space.
*/
void convert_hword_to_bytes(const hword_t h, byte_t *buffer);
/**
@brief Convert a buffer of bytes to a word.
@details It is assumed that the buffer of bytes are in virtual
machine byte code format (little endian) and that they are at least
WORD_SIZE in size.
@details It is assumed that the buffer of bytes are in virtual machine byte
code format (little endian) and that they are at least WORD_SIZE in size.
*/
word_t convert_bytes_to_word(const byte_t *);
/**
@brief Convert a word into a VM byte code format bytes (little
endian)
@brief Convert a word into a VM byte code format bytes (little endian)
@param w: Word to convert
@param buffer: Buffer to store into. It is assumed that the buffer
has at least WORD_SIZE space.
@param buffer: Buffer to store into. It is assumed that the buffer has at
least WORD_SIZE space.
*/
void convert_word_to_bytes(const word_t w, byte_t *buffer);
/**
@brief Swap the ordering of bytes within an short
@details The ordering of the bytes in the short are reversed (2
bytes in a short).
@details The ordering of the bytes in the short are reversed (2 bytes in a
short).
@param s: short to swap
*/
@@ -220,8 +222,8 @@ short_t short_byteswap(const short_t s);
/**
@brief Swap the ordering of bytes within an half word
@details The ordering of the bytes in the half word are reversed (4
bytes in a half word).
@details The ordering of the bytes in the half word are reversed (4 bytes in
a half word).
@param h: Half word to swap
*/
@@ -230,8 +232,8 @@ hword_t hword_byteswap(const hword_t h);
/**
@brief Swap the ordering of bytes within an word
@details The ordering of the bytes in the word are reversed (8
bytes in a word).
@details The ordering of the bytes in the word are reversed (8 bytes in a
word).
@param w: Word to swap
*/

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
@@ -43,10 +42,10 @@ typedef struct
/**
@brief Get the `IND`th item of type `TYPE` in `DARR_DATA`
@details Cast `DARR_DATA` to `TYPE`, taking the `IND`th item.
NOTE: This is unsafe as bound checks are not done i.e. if
`DARR_DATA` has at least space for `IND` * sizeof(`TYPE`) items.
It is presumed the caller will check themselves.
@details Cast `DARR_DATA` to `TYPE`, taking the `IND`th item. NOTE: This is
unsafe as bound checks are not done i.e. if `DARR_DATA` has at least space
for `IND` * sizeof(`TYPE`) items. It is presumed the caller will check
themselves.
@param[TYPE] Type to cast internal byte array
@param[DARR_DATA] Byte array of darr
@@ -59,24 +58,23 @@ typedef struct
/**
@brief Initialise a dynamic array `darr` with n bytes of space.
@details All properties of `darr` are initialised. `darr`.used is
set to 0, `darr`.available is set to `n` and `darr`.data is set to
a pointer of `n` bytes. NOTE: If `n` = 0 then it is set to
DARR_DEFAULT_SIZE
@details All properties of `darr` are initialised. `darr`.used is set to 0,
`darr`.available is set to `n` and `darr`.data is set to a pointer of `n`
bytes. NOTE: If `n` = 0 then it is set to DARR_DEFAULT_SIZE
@param[darr] Pointer to darr_t object to initialise
@param[n] Number of bytes to allocate. If equal to 0 then
considered treated as DARR_DEFAULT_SIZE
@param[n] Number of bytes to allocate. If equal to 0 then considered treated
as DARR_DEFAULT_SIZE
*/
void darr_init(darr_t *darr, size_t n);
/**
@brief Ensure a dynamic array has at least n bytes of space free.
@details If `darr` has n or more bytes free, nothing occurs.
Otherwise, the byte array in `darr` is reallocated such that it has
at least `n` bytes of free space. NOTE: `darr` has at least `n`
bytes free if and only if `darr`.used + `n` <= `darr`.available
@details If `darr` has n or more bytes free, nothing occurs. Otherwise, the
byte array in `darr` is reallocated such that it has at least `n` bytes of
free space. NOTE: `darr` has at least `n` bytes free if and only if
`darr`.used + `n` <= `darr`.available
@param[darr] Dynamic array to check
@param[n] Number of bytes
@@ -86,10 +84,9 @@ void darr_ensure_capacity(darr_t *darr, size_t n);
/**
@brief Append a byte to a dynamic array.
@details Append a byte to the end of the byte buffer in a dyamic
array. If the dynamic array doesn't have enough free space to fit
the byte, it will reallocate to ensure it can fit it in via
darr_ensure_capacity().
@details Append a byte to the end of the byte buffer in a dyamic array. If
the dynamic array doesn't have enough free space to fit the byte, it will
reallocate to ensure it can fit it in via darr_ensure_capacity().
@param[darr] Dynamic arrary to append to
@param[b] Byte to append
@@ -99,10 +96,9 @@ void darr_append_byte(darr_t *darr, byte_t b);
/**
@brief Append an array of n bytes to a dynamic array.
@details Append an array of bytes to the end of a byte buffer. If
the dynamic array doesn't have enough free space to fit all n bytes
it will reallocate to ensure it can fit it in via
darr_ensure_capacity().
@details Append an array of bytes to the end of a byte buffer. If the
dynamic array doesn't have enough free space to fit all n bytes it will
reallocate to ensure it can fit it in via darr_ensure_capacity().
@param[darr] Dynamic array to append to
@param[b] Array of bytes to append
@@ -113,9 +109,9 @@ void darr_append_bytes(darr_t *darr, byte_t *b, size_t n);
/**
@brief Get the nth byte of a dynamic array
@details Get the nth byte of the dynamic array. 0 based. NOTE: If
the dynamic array has less than n bytes used, it will return 0 as a
default value, so this is a safe alternative to DARR_AT().
@details Get the nth byte of the dynamic array. 0 based. NOTE: If the
dynamic array has less than n bytes used, it will return 0 as a default
value, so this is a safe alternative to DARR_AT().
@param[darr] Dynamic array to index
@param[n] Index to get byte at
@@ -127,9 +123,9 @@ byte_t *darr_at(darr_t *darr, size_t n);
/**
@brief Write the bytes of a dynamic array to a file pointer
@details Given a dynamic array and a file pointer, write the
internal buffer of bytes to the file pointer. NOTE: The file
pointer is assumed to be open and suitable for writing.
@details Given a dynamic array and a file pointer, write the internal buffer
of bytes to the file pointer. NOTE: The file pointer is assumed to be open
and suitable for writing.
@param[darr] Dynamic array to write
@param[fp] File pointer to write on
@@ -139,14 +135,14 @@ void darr_write_file(darr_t *darr, FILE *fp);
/**
@brief Read a file pointer in its entirety into a dynamic array
@details Read a file pointer as a buffer of bytes then return that
buffer wrapped in a darr_t structure. NOTE: the file pointer is
assumed to be open and suitable for reading.
@details Read a file pointer as a buffer of bytes then return that buffer
wrapped in a darr_t structure. NOTE: the file pointer is assumed to be open
and suitable for reading.
@param[fp]: File pointer to read
@return Dynamic array structure with available set to the size of
the `buffer` read and `data` set to the buffer of bytes.
@return Dynamic array structure with available set to the size of the
`buffer` read and `data` set to the buffer of bytes.
*/
darr_t darr_read_file(FILE *fp);

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-11-01
* Author: Aryadev Chavali
@@ -60,8 +59,7 @@ bool heap_free(heap_t *heap, page_t *page)
if (cur == page)
{
page_delete(cur);
// TODO: When does this fragmentation become a performance
// issue?
// TODO: When does this fragmentation become a performance issue?
DARR_AT(page_t *, heap->page_vec.data, i) = NULL;
return true;
}

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-11-01
* Author: Aryadev Chavali
@@ -27,8 +26,8 @@
/**
@brief Some fixed portion of bytes allocated on the heap.
@details A fixed allocation of bytes. Cannot be resized nor can it
be stack allocated (the usual way) due to flexible array attached.
@details A fixed allocation of bytes. Cannot be resized nor can it be stack
allocated (the usual way) due to flexible array attached.
@prop[next] Next page in the linked list
@prop[available] Available number of bytes in page
@@ -43,9 +42,8 @@ typedef struct Page
/**
@brief Allocate a new page on the heap with the given properties.
@details Allocates a new page using malloc with the given size and
pointer to next page. NOTE: all memory is 0 initialised by
default.
@details Allocates a new page using malloc with the given size and pointer to
next page. NOTE: all memory is 0 initialised by default.
@param[max] Maximum available memory in page
*/
@@ -54,9 +52,8 @@ page_t *page_create(size_t max);
/**
@brief Delete a page, freeing its memory
@details Free's the memory associated with the page via free().
NOTE: any pointers to the page's memory are considered invalid once
this is called.
@details Free's the memory associated with the page via free(). NOTE: any
pointers to the page's memory are considered invalid once this is called.
@param[page] Page to delete
*/
@@ -66,8 +63,8 @@ void page_delete(page_t *page);
@brief A collection of pages through which generic allocations can
occur.
@details Collection of pages maintained through a vector of
pointers to pages.
@details Collection of pages maintained through a vector of pointers to
pages.
@prop[page_vec] Vector of pages
*/
@@ -81,9 +78,8 @@ typedef struct
/**
@brief Instantiate a new heap structure
@details Initialises the heap structure given. No heap allocation
occurs here until a new page is created, so this may be called
safely.
@details Initialises the heap structure given. No heap allocation occurs
here until a new page is created, so this may be called safely.
@param[heap] Pointer to heap to initialise
*/
@@ -92,8 +88,8 @@ void heap_create(heap_t *heap);
/**
@brief Allocate a new page on the heap
@details Creates and joins a new page onto the linked list
maintained by the heap. heap.end is set to this new page.
@details Creates and joins a new page onto the linked list maintained by the
heap. heap.end is set to this new page.
@param[heap] Heap to create a new page on
@param[size] Size of page to allocate
@@ -105,10 +101,9 @@ page_t *heap_allocate(heap_t *heap, size_t size);
/**
@brief Free a page of memory from the heap
@details The page given is removed from the linked list of pages
then freed from the heap via page_delete(). If the page does not
belong to this heap (O(heap.pages) time) then false is returned,
otherwise true.
@details The page given is removed from the linked list of pages then freed
from the heap via page_delete(). If the page does not belong to this heap
(O(heap.pages) time) then false is returned, otherwise true.
@param[heap] Heap to free page from
@param[page] Page to delete

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-28
* Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
@@ -77,14 +76,6 @@ const char *opcode_as_cstr(opcode_t code)
return "MALLOC_HWORD";
case OP_MALLOC_WORD:
return "MALLOC_WORD";
case OP_MALLOC_STACK_BYTE:
return "MALLOC_STACK_BYTE";
case OP_MALLOC_STACK_SHORT:
return "MALLOC_STACK_SHORT";
case OP_MALLOC_STACK_HWORD:
return "MALLOC_STACK_HWORD";
case OP_MALLOC_STACK_WORD:
return "MALLOC_STACK_WORD";
case OP_MSET_BYTE:
return "MSET_BYTE";
case OP_MSET_SHORT:
@@ -93,14 +84,6 @@ const char *opcode_as_cstr(opcode_t code)
return "MSET_HWORD";
case OP_MSET_WORD:
return "MSET_WORD";
case OP_MSET_STACK_BYTE:
return "MSET_STACK_BYTE";
case OP_MSET_STACK_SHORT:
return "MSET_STACK_SHORT";
case OP_MSET_STACK_HWORD:
return "MSET_STACK_HWORD";
case OP_MSET_STACK_WORD:
return "MSET_STACK_WORD";
case OP_MGET_BYTE:
return "MGET_BYTE";
case OP_MGET_SHORT:
@@ -109,14 +92,6 @@ const char *opcode_as_cstr(opcode_t code)
return "MGET_HWORD";
case OP_MGET_WORD:
return "MGET_WORD";
case OP_MGET_STACK_BYTE:
return "MGET_STACK_BYTE";
case OP_MGET_STACK_SHORT:
return "MGET_STACK_SHORT";
case OP_MGET_STACK_HWORD:
return "MGET_STACK_HWORD";
case OP_MGET_STACK_WORD:
return "MGET_STACK_WORD";
case OP_MDELETE:
return "MDELETE";
case OP_MSIZE:
@@ -267,8 +242,6 @@ const char *opcode_as_cstr(opcode_t code)
return "PRINT_SWORD";
case OP_JUMP_ABS:
return "JUMP_ABS";
case OP_JUMP_STACK:
return "JUMP_STACK";
case OP_JUMP_IF_BYTE:
return "JUMP_IF_BYTE";
case OP_JUMP_IF_SHORT:
@@ -279,8 +252,6 @@ const char *opcode_as_cstr(opcode_t code)
return "JUMP_IF_WORD";
case OP_CALL:
return "CALL";
case OP_CALL_STACK:
return "CALL_STACK";
case OP_RET:
return "RET";
case NUMBER_OF_OPCODES:
@@ -312,7 +283,7 @@ void data_print(data_t datum, data_type_t type, FILE *fp)
void inst_print(inst_t instruction, FILE *fp)
{
static_assert(NUMBER_OF_OPCODES == 129, "inst_print: Out of date");
static_assert(NUMBER_OF_OPCODES == 115, "inst_print: Out of date");
fprintf(fp, "%s(", opcode_as_cstr(instruction.opcode));
if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PUSH))
{
@@ -326,10 +297,7 @@ void inst_print(inst_t instruction, FILE *fp)
fprintf(fp, "reg=0x");
data_print(instruction.operand, DATA_TYPE_BYTE, fp);
}
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_DUP) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MALLOC) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MSET) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MGET))
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_DUP))
{
fprintf(fp, "n=0x%lX", instruction.operand.as_word);
}
@@ -345,7 +313,7 @@ void inst_print(inst_t instruction, FILE *fp)
size_t opcode_bytecode_size(opcode_t opcode)
{
static_assert(NUMBER_OF_OPCODES == 129, "inst_bytecode_size: Out of date");
static_assert(NUMBER_OF_OPCODES == 115, "inst_bytecode_size: Out of date");
size_t size = 1; // for opcode
if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH))
{
@@ -370,7 +338,7 @@ size_t opcode_bytecode_size(opcode_t opcode)
size_t inst_write_bytecode(inst_t inst, byte_t *bytes)
{
static_assert(NUMBER_OF_OPCODES == 129, "inst_write_bytecode: Out of date");
static_assert(NUMBER_OF_OPCODES == 115, "inst_write_bytecode: Out of date");
bytes[0] = inst.opcode;
size_t written = 1;
@@ -382,9 +350,6 @@ size_t inst_write_bytecode(inst_t inst, byte_t *bytes)
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_DUP) ||
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)
to_append = DATA_TYPE_WORD;
@@ -457,7 +422,7 @@ bool read_type_from_darr(byte_t *bytes, size_t size, data_type_t type,
int inst_read_bytecode(inst_t *ptr, byte_t *bytes, size_t size_bytes)
{
static_assert(NUMBER_OF_OPCODES == 129, "inst_read_bytecode: Out of date");
static_assert(NUMBER_OF_OPCODES == 115, "inst_read_bytecode: Out of date");
opcode_t opcode = *(bytes++);
if (opcode >= NUMBER_OF_OPCODES || opcode < OP_NOOP)
@@ -472,13 +437,10 @@ int inst_read_bytecode(inst_t *ptr, byte_t *bytes, size_t size_bytes)
if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH))
success = read_type_from_darr(bytes, size_bytes, (data_type_t)opcode,
&inst.operand);
// Read register (as a byte)
// Read operand as a word
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)
success =

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
@@ -65,31 +64,16 @@ typedef enum
OP_MALLOC_HWORD,
OP_MALLOC_WORD,
OP_MALLOC_STACK_BYTE,
OP_MALLOC_STACK_SHORT,
OP_MALLOC_STACK_HWORD,
OP_MALLOC_STACK_WORD,
OP_MSET_BYTE,
OP_MSET_SHORT,
OP_MSET_HWORD,
OP_MSET_WORD,
OP_MSET_STACK_BYTE,
OP_MSET_STACK_SHORT,
OP_MSET_STACK_HWORD,
OP_MSET_STACK_WORD,
OP_MGET_BYTE,
OP_MGET_SHORT,
OP_MGET_HWORD,
OP_MGET_WORD,
OP_MGET_STACK_BYTE,
OP_MGET_STACK_SHORT,
OP_MGET_STACK_HWORD,
OP_MGET_STACK_WORD,
OP_MDELETE,
OP_MSIZE,
@@ -184,7 +168,6 @@ typedef enum
// Program control flow
OP_JUMP_ABS,
OP_JUMP_STACK,
OP_JUMP_IF_BYTE,
OP_JUMP_IF_SHORT,
OP_JUMP_IF_HWORD,
@@ -192,7 +175,6 @@ typedef enum
// Subroutines
OP_CALL,
OP_CALL_STACK,
OP_RET,
// Should not be an opcode
@@ -211,10 +193,10 @@ typedef struct
/**
@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.
@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
@@ -234,21 +216,19 @@ typedef enum
/**
@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.
@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.
@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);

294
spec.org
View File

@@ -3,86 +3,250 @@
#+description: A specification of instructions for the virtual machine
#+date: 2023-11-02
* WIP Data types
There are 3 main data types of the virtual machine. They are all
unsigned. There exist signed versions of these data types, though
there is no difference (internally) between them. For an unsigned
type <T> the signed version is simply S_<T>.
* Data types
There are 4 main data types of the virtual machine. They are all
unsigned.
|-------+------|
| Name | Bits |
|-------+------|
| Byte | 8 |
| Short | 16 |
| HWord | 32 |
| Word | 64 |
|-------+------|
Generally, the abbreviations B, H and W are used for Byte, HWord and
Word respectively. The following table shows a comparison between the
data types where an entry (row and column) $A\times{B}$ refers to "How
many of A can I fit in B".
|-------+------+-------+------|
| | Byte | Hword | Word |
|-------+------+-------+------|
| Byte | 1 | 4 | 8 |
| HWord | 1/4 | 1 | 2 |
| Word | 1/8 | 1/2 | 1 |
|-------+------+-------+------|
Generally, the abbreviations B, S, H and W are used for Byte, Short,
HWord and Word respectively. The following table shows a comparison
between the data types where an entry (row and column) $A\times{B}$
refers to "How many of A can I fit in B".
|-------+------+-------+-------+------|
| | Byte | Short | HWord | Word |
|-------+------+-------+-------+------|
| Byte | 1 | 2 | 4 | 8 |
| Short | 1/2 | 1 | 2 | 4 |
| HWord | 1/4 | 1/2 | 1 | 2 |
| Word | 1/8 | 1/4 | 1/2 | 1 |
|-------+------+-------+-------+------|
These unsigned types can be trivially considered signed via 2s
complement. The signed version of some unsigned type is abbreviated
by prefixing the type with a =S_=. So the signed version of each type
is S_B, S_S, S_H, S_W.
* TODO Storage
There are 4 forms of storage available in the virtual machine: the
*stack*, *registers* and *heap*. The stack, registers and call stack
are considered *fixed storage* in that they have an exact fixed
capacity within the virtual machine. The heap, on the other hand, can
grow dynamically as it supports user requested allocations and is thus
considered *dynamic storage*.
** Stack
+ FILO data structure
+ ~S~ in shorthand
+ ~ptr~ represents the top of the stack at any one point during
execution, ~0~ refers to the address for the bottom of the stack
(aka the minimal value of ~ptr~) and ~n~ refers to the address of
the end of the usable stack space (aka the maximal value for
~ptr~, ~MAX_STACK~)
** Registers
+ constant time read/write data structure
+ ~R~ in shorthand
+ Reserves ~m~ bytes of space (called the ~MAX_REG~), where m must
be a positive multiple of 8
+ May be indexed via a pointer in one of the 4 following forms:
+ ~b<i>~: the ith byte, where i in [0, m)
+ ~s<i>~: the ith short, where i in [0, m/2)
+ ~h<i>~: the ith hword, where i in [0, m/4)
+ ~w<i>~: the ith word, where i in [0, m/8)
+ w<i> refers to the 8 bytes between [8i, 8(i+1)), which implicitly
refers to the:
+ 8 byte registers {b<j> | j in [8i, 8(i + 1))}
+ 4 short registers {s<j> | j in [4i, 4(i + 1))}
+ 2 hword registers {h<j> | j in [2i, 2(i + 1))}
** TODO Heap
+ Random access storage which can be allocated into chunks
+ ~H~ in shorthand
** Call stack
+ FILO data structure containing program addresses (indexes in the
program)
+ ~C~ in shorthand
+ Is reserved for a very small subset of operations for control flow
* WIP Instructions
An instruction for the virtual machine is composed of an *opcode* and,
potentially, an *operand*. The /opcode/ represents the behaviour of
the instruction i.e. what _is_ the instruction. The /operand/ is an
element of one of the /data types/ described previously.
optionally, an *operand*. The /opcode/ represents the specific
behaviour of the instruction i.e. what the instruction does. The
/operand/ is an element of one of the /data types/ described
previously which the opcode uses as part of its function. The operand
is optional based on the opcode: certain opcodes will never require an
operand.
** Operations: abstracting over opcodes
An *operation* is some generic behaviour, potentially involving data
storage. Many operations are generic over data types i.e. they
describe some behaviour that works for some subset of types. Opcodes
are simply specialisations of operations over some data type. For
example the generic behaviour of the operation ~PUSH~, which pushes
the operand onto the stack, is specialised into the opcode
~PUSH_WORD~, which pushes the operand, a word, onto the stack. An
operation may, thus, describe many opcodes and each opcode is a
specialisation of exactly one operation.
Some instructions do have /operands/ while others do not. The former
type of instructions are called *UNIT* instructions while the latter
type are called *MULTI* instructions[fn:1].
The *order* of an operation is the number of specialisations it has
i.e. the number of opcodes that specialise one operation.
All /opcodes/ (with very few exceptions[fn:2]) have two components:
the *root* and the *type specifier*. The /root/ represents the
general behaviour of the instruction: ~PUSH~, ~POP~, ~MOV~, etc. The
/type specifier/ specifies what /data type/ it manipulates. A
complete opcode will be a combination of these two e.g. ~PUSH_BYTE~,
~POP_WORD~, etc. Some /opcodes/ may have more /type specifiers/ than
others.
Some operations may not be generic over data types in which case they
are of order 1 i.e. the opcode describes the exact behaviour of only
one operation.
There are only 3 possible orders for operations: 1, 4 and 8. They are
given the names Nil, Unsigned and Signed for specialising over:
+ No types
+ The 4 unsigned data types described earlier
+ The 4 unsigned data types and their signed variants as well
** Arity
The arity of an operation is the number of input data it takes. An
operation can take input in two ways:
+ From the operand, encoded in the bytecode
+ From the stack by popping from the top
An operation that takes n input data from the stack pops n data from
the stack to use as input.
Since there can only be at most one operand, an operation that takes
input from the operand must have an arity of at least one.
Hence the arity is the sum of inputs taken from both. This can be 0,
in which case the operation is *nullary*. An operation that takes one
input, whether that be from the stack or operand, is *unary*. An
operation that takes two inputs, whichever source either are from, is
*binary*.
** Orientation
An operation can be considered *oriented* around a data storage if it
only takes input from that data storage. So an operation that only
takes input from the stack is *stack-oriented*. Or an operation that
only takes input from the operand is *operand-oriented*.
** Categorisation of operations
With the notation done, we can now describe all operations that the
virtual machine supports. Through describing all of these operations,
including their orders and what operand they accept (if any), we can
describe all opcodes.
*** Trivial nullary operations
These are NIL order operations which are super simple to describe.
+ =NOOP=: Doesn't do anything.
+ =HALT=: Stops execution at point
*** Moving data in fixed storage
There are 5 operations that move data through fixed storage in the
virtual machine. They are of Unsigned order, unary and
operand-oriented.
|-----------------+---------------------------------------------------|
| Name | Behaviour |
|-----------------+---------------------------------------------------|
| =PUSH= | Pushes operand onto stack |
| =POP= | Pops datum off stack |
| =PUSH_REGISTER= | Pushes datum from (operand)th register onto stack |
| =MOV= | Moves datum off stack to the (operand)th register |
| =DUP= | Pushes the (operand)th datum in stack onto stack |
|-----------------+---------------------------------------------------|
*** Using the heap
The heap is utilised through a set of "helper" operations that safely
abstract the underlying implementation. All of these operations are
stack-oriented.
|-----------+----------------------------------------------------------+-------|
| Name | Behaviour | Arity |
|-----------+----------------------------------------------------------+-------|
| =MALLOC= | Allocate n amount of data in the heap, pushing a pointer | 1 |
| =MSET= | Pop a value, set the nth datum of data in the heap | 3 |
| =MGET= | Push the nth datum of data in the heap onto the stack | 3 |
| =MDELETE= | Free data in the heap | 1 |
| =MSIZE= | Get the size of allocation in the heap | 1 |
|-----------+----------------------------------------------------------+-------|
=MALLOC=, =MSET= and =MGET= are of Unsigned order. Due to unsigned
and signed types taking the same size, they can be used for signed
data as well.
*** Boolean operations
There are 5 boolean operations. They are of Unsigned order, binary
and stack-oriented. These are:
+ =NOT=
+ =OR=
+ =AND=
+ =XOR=
+ =EQ=
Though they are all of unsigned order they can be used for signed data
trivially.
*** Comparison operations
There are 4 comparison operations. They are all signed operations,
binary and stack-oriented. They are:
+ LT: Less Than
+ LTE: Less Than or Equal
+ GT: Greater Than
+ GTE: Greater Than or Equal
As =EQ= is an unsigned order operation and doesn't assert anything on
the actual values, it can be used for comparing two signed inputs. It
doesn't perform a cast when comparing and unsigned and signed input
which may mean certain non equivalent values may be considered equal
(e.g. =0xFAA9= is a negative number in 2s complement but a positive
number in unsigned, considered the same under =EQ=).
*** Mathematical operations
There are 3 mathematical operations. They are of unsigned order,
binary and stack-oriented. These are:
+ PLUS
+ SUB
+ MULT
Though they are unsigned, any overflowing operation is wrapped around.
With some thought these operations can treat unsigned data and be used
to generate them.
*** Control flow operations
There are 2 control flow operations. Each perform a "jump", changing
the point of execution to a different point in the program.
|--------------+----------+---------------+-------|
| Name | Order | Orientation | Arity |
|--------------+----------+---------------+-------|
| =JUMP_ABS= | NIL | Operand | 1 |
| =JUMP_IF= | UNSIGNED | Operand+Stack | 2 |
|--------------+----------+---------------+-------|
+ =JUMP_ABS= interprets the operand as an absolute program address and
sets point of execution to that address
+ =JUMP_IF= pops a datum off the stack and compares it to 0. If true,
the point of execution is set to the operand (interpreted as an
absolute program address). If false, execution continues past it.
*** Subroutine operations
There are 2 subroutine operations. They are the only operations that
can mutate the call stack. Through utilising reserved storage in the
virtual machine that can only be altered through these methods, they
abstract control flow to a higher degree than the jump operations.
|------------+-------------+-------|
| Name | Orientation | Arity |
|------------+-------------+-------|
| CALL | Operand | 1 |
| RET | - | 0 |
|------------+-------------+-------|
The CALL* operations take a program address as input (either from the
operand or from the stack). They push the current program address
onto the call stack and perform a jump to the input address.
The RET operation pops a program address off the call stack,
performing a jump to that address.
These operations allow the implementation of /subroutines/: sequences
of code that can be self contained and generic over a variety of call
sites i.e. can return to the address where it was called without hard
coding the address.
*** TODO IO
Currently IO is really bad: the PRINT_* routines are not a nice
abstraction over what's really happening and programs cannot take
input from stdin.
* TODO Bytecode format
Bytecode files are byte sequence which encode instructions for the
virtual machine. Any instruction (even with an operand) has one and
only one byte sequence associated with it.
* TODO Storage
Two types of storage:
+ Data stack which all core VM routines manipulate and work on (FILO)
+ ~DS~ in shorthand, with indexing from 0 (referring to the top of the
stack) up to n (referring to the bottom of the stack). B(DS)
refers to the bytes in the stack (the default).
+ Register space which is generally reserved for user space code
i.e. other than ~mov~ no other core VM routine manipulates the
registers
+ ~R~ in shorthand, with indexing from 0 to $\infty$.
* TODO Standard library
Standard library subroutines reserve the first 16 words (128 bytes) of
register space (W(R)[0] to W(R)[15]). The first 8 words (W(R)[0] to
W(R)[7]) are generally considered "arguments" to the subroutine while
the remaining 8 words (W(R)[8] to W(R)[15]) are considered additional
space that the subroutine may access and mutate for internal purposes.
The stack may have additional bytes pushed, which act as the "return
value" of the subroutine, but no bytes will be popped off (*Stack
Preservation*).
If a subroutine requires more than 8 words for its arguments, then it
will use the stack. This is the only case where the stack is mutated
due to a subroutine call, as those arguments will always be popped off
the stack.
Subroutines must always end in ~RET~. Therefore, they must always be
called via ~CALL~, never by ~JUMP~ (which will always cause error
prone behaviour).
* Footnotes
[fn:2] ~NOOP~, ~HALT~, ~MDELETE~, ~MSIZE~, ~JUMP_*~
[fn:1] /UNIT/ refers to the fact that the internal representation of
these instructions are singular: two instances of the same /UNIT/
instruction will be identical in terms of their binary. On the other
hand, two instances of the same /MULTI/ instruction may not be
equivalent due to the operand they take. Crucially, most if not all
/MULTI/ instructions have different versions for each /data type/.

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-28
* Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-28
* Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-28
* Author: Aryadev Chavali
@@ -71,8 +70,8 @@ void test_lib_darr_ensure_capacity_expands(void)
{10, 10, 10, 20},
{50, 100, 300, 350},
{1 << 20, 2 << 20, 2 << 20, 3 << 20},
// When we reallocate we allocate MORE than needed (for
// amortized constant)
// When we reallocate we allocate MORE than needed (for amortized
// constant)
{1, 5, 5, 10},
{85, 100, 40, 200},
{4 << 20, 5 << 20, 1 << 23, 5 << 21},

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-28
* Author: Aryadev Chavali
@@ -25,16 +24,6 @@
#include <stdlib.h>
#include <string.h>
#define MESSAGE(FILE, COLOUR, NAME, FORMAT, ...) \
fprintf(FILE, "\t[" COLOUR "%s" TERM_RESET "]: " FORMAT, NAME, __VA_ARGS__)
#define INFO(NAME, FORMAT, ...) \
MESSAGE(stdout, TERM_YELLOW, NAME, FORMAT, __VA_ARGS__)
#define FAIL(NAME, FORMAT, ...) \
MESSAGE(stderr, TERM_RED, NAME, FORMAT, __VA_ARGS__)
#define SUCCESS(NAME, FORMAT, ...) \
MESSAGE(stdout, TERM_GREEN, NAME, FORMAT, __VA_ARGS__)
typedef void (*test_fn)(void);
struct Test
{

147
todo.org
View File

@@ -3,6 +3,153 @@
#+date: 2023-11-02
#+startup: noindent
* TODO Completely rework opcodes
Instead of having an opcode per type, we can implement a generic
opcode using two operands just on bytes.
Instead of ~PUSH_(BYTE|SHORT|HWORD|WORD) n~ where n is the data to
push of the precomposed type, we make a generic ~PUSH m, {n}~ where m
is the number of bytes and {n} is the set of bytes to push.
In bytecode that would look like ~<OP_PUSH>|m|n1|n2|n3...|nm~.
Opcodes are already variably sized so we may as well allow this. And
we reduce the number of opcodes by 3.
Each opcode can be encoded this way, but we need to describe the
semantics clearly.
** Register encoding
Firstly, registers are now only encoded by byte pointer. No short,
half word or word pointers.
Since they're so easy to translate between anyway, why should the
virtual machine do the work to handle that?
So a register r is the byte register at index r.
** PUSH
=PUSH m {n}= pushes m bytes of data, encoded by {n}.
** POP
=POP m= pops m bytes of data
** PUSH_REGISTER
=PUSH_REGISTER m r= pushes the m bytes from register space starting at
index r.
** MOV
=MOV m r= moves m bytes of data off the stack into the register space,
starting at index r.
Easy to error check as well in one go.
** DUP
=DUP m= duplicates the last m bytes, pushing them onto the top of the
stack.
** NOT
=NOT m= takes the last m bytes and pushes the ~NOT~ of each byte onto
the stack in order.
Say the top of the stack has the m bytes {n_i} where i is from 1 to m.
Then =NOT m= would pop those bytes then push {!n_i} onto the stack in
the exact same order.
** Binary boolean operators
=<OP> m= pops the last 2m bytes off the stack and does a byte by byte
operation, pushing the result onto the stack.
Say the top of the stack has m bytes {a_i} and then m bytes {b_i}.
These would both be popped off and what would be pushed is {<OP>(a_i,
b_i)} onto the stack in order.
** Mathematical and comparison operations
PLUS, SUB and MULT will now have two versions: U<OP> and <OP> for
unsigned <OP> and signed <OP>. This allows us to deal with edge case
2s complement arithmetic.
=<OP> m= pops the last 2m bytes off the stack then applies the
operation on the two portions of bytes, considering them as signed or
unsigned based on the OP. It then pushes that result back onto the
stack.
NOTE: We can still optimise by checking if m is within some bound of
the known types we have already (i.e. is it about the size of a short,
or a word) then using those known types to do computations faster.
What this provides is a generic algorithm for =m= byte arithmetic
which is what all cool programming languages do.
Comparison operations can be done in basically the same way.
** JUMP_IF
JUMP_IF can check the truthiness of some m bytes of memory, which we
can optimise if the m bytes are in some known bound already.
=JUMP_IF m= pops m bytes off the stack and examines them: if it's all
zero then it doesn't perform the jump, but otherwise it does.
** Shifting
I want to really work on making shifting operators. These move the
stack pointer without manipulating the actual data on the stack, which
can be useful when performing an operation that pops some resource
over and over again (i.e. =MSET='ing data from some heap allocation
requires popping the pointer and data off the stack). Since all
operations use the stack pointer when manipulating it (even ~POP~),
shifting the stack pointer doesn't change their behaviour a whole lot
but may require some extra mental work on the developer.
+ =SHIFT_DOWN m= moves the stack pointer down m bytes. Error may
happen if pointer is shifted further than 0
+ =SHIFT_UP m= moves the stack pointer down m bytes. Error may
occur if pointer shifts past the ~STACK_MAX~.
** Memory model
Something different will have to happen here. I have a few ideas
around making pages and reserving "space" as a generic sense, allowing
the virtual machine to use that space in a variety of ways regardless
of what storage is being used for that space.
Essentially I want a better model which will allow me to use the stack
as generic memory space: pointers to the stack. So a tentative API
would be:
+ A page is a reserved space in some storage, whether that be the heap
or the stack. It is represented by a word which is a pointer to the
start of it. The structure of a page in memory has a word
representing the size of the page and a number of bytes following
it.
+ =RESERVE_STACK m= reserves a page of m bytes on the stack. The
stack pointer is shifted up m+8 bytes and a pointer to the page is
pushed onto the stack.
+ =RESERVE_HEAP m= reserves a page of m bytes in the heap, which is a
VM managed resource that cannot be directly accessed by the user.
The page is pushed onto the stack.
+ =PAGE_WRITE m= writes m bytes of memory, stored on the stack, to a
page. The data to write and the page pointer are popped off the
stack in that order.
+ =PAGE_READ a b= pushes the bytes of a page between indexes [a, b)
onto the stack. The page pointer is popped off the stack.
+ =PAGE_REALLOC m= reallocates the page to the new size of m bytes,
allowing for dynamic memory management. The page pointer is popped
off the stack and a new page pointer is pushed onto the stack.
+ If the page is a stack page, this errors out because that stack
space will be forcibly leaked.
+ =PAGE_FREE= returns ownership of a page back to the runtime. The
page pointer is popped off the stack.
+ In the case of a stack page, this does nothing but zero the space
originally in the stack (including the first 8 bytes for the size
of the page) which means the user must shift down and/or pop data
to use the space effectively and avoid stack leaks.
** I/O
Something better needs to happen here. Perhaps writing a better
wrapper over C file I/O such that users can open file handles and deal
with them. Tentative API:
+ A file handle is a word representing a pointer to it. This can
either be the raw C pointer or an index in some abstraction such as
a dynamic array of file pointers
+ =FILE_OPEN m t= interprets the top m bytes of the stack as the file
name to open. t is a byte encoding the file mode. File handle is
pushed onto the stack.
+ 0 -> Read
+ 1 -> Write
+ 2 -> Append
+ 3 -> Read+
+ 4 -> Write+
+ 5 -> Append+
+ =FILE_READ m= reads the m bytes from a file handle, pushing them
onto the stack. File handle is popped off the stack.
+ =FILE_WRITE m= writes the m bytes on the top of the stack to the
file handle given. Both the bytes to write and the handle are
stored on the stack, first the bytes then the handle.
+ =FILE_STATUS= pushes the current position of the file onto the
stack. File handle is popped off the stack.
+ =FILE_CLOSE= closes and frees the file handle. File handle is
popped off the stack.
* TODO Rework heap to use one allocation
The current approach for the heap is so:
+ Per call to ~malloc~, allocate a new ~page_t~ structure by

View File

@@ -1,19 +1,19 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
* Description: Entrypoint to program
*/
#include "lib/base.h"
#include <stdio.h>
#include <stdlib.h>
@@ -40,7 +40,7 @@ int main(int argc, char *argv[])
const char *filename = argv[1];
#if VERBOSE >= 1
printf("[" TERM_YELLOW "INTERPRETER" TERM_RESET "]: `%s`\n", filename);
INFO("INTERPRETER", "`%s`\n", filename);
#endif
FILE *fp = fopen(filename, "rb");
@@ -53,16 +53,14 @@ int main(int argc, char *argv[])
if (!header_read)
{
fprintf(stderr, "[ERROR]: Could not deserialise program header in `%s`\n",
filename);
FAIL("ERROR", "Could not deserialise program header in `%s`\n", filename);
return 1;
}
// Ensure that we MUST have something to read
else if (program.count == 0)
return 0;
// After reading header, we can allocate the buffer of instrutions
// exactly
// After reading header, we can allocate the buffer of instrutions exactly
program.instructions = calloc(program.count, sizeof(*program.instructions));
size_t bytes_read = 0;
read_err_prog_t read_err =
@@ -71,7 +69,7 @@ int main(int argc, char *argv[])
if (bytes_read == 0)
{
fprintf(stderr, "[ERROR]:%s [%lu]:", filename, read_err.index);
FAIL("ERROR", "%s [%lu]:", filename, read_err.index);
switch (read_err.type)
{
case READ_ERR_INVALID_OPCODE:
@@ -93,8 +91,7 @@ int main(int argc, char *argv[])
}
#if VERBOSE >= 1
printf("\t[" TERM_GREEN "SETUP" TERM_RESET "]: Read %lu instructions\n",
program.count);
SUCCESS("SETUP", "Read %lu instructions\n", program.count);
#endif
size_t stack_size = 256;
@@ -114,11 +111,8 @@ int main(int argc, char *argv[])
vm_load_call_stack(&vm, call_stack, call_stack_size);
#if VERBOSE >= 1
printf("\t[" TERM_GREEN "SETUP" TERM_RESET
"]: Loaded stack and program into VM\n");
#endif
#if VERBOSE >= 1
printf("[" TERM_YELLOW "INTERPRETER" TERM_RESET "]: Beginning execution\n");
SUCCESS("SETUP", "Loaded internals\n%s", "");
INFO("INTERPRETER", "Beginning execution\n%s", "");
#endif
err_t err = vm_execute_all(&vm);
@@ -126,7 +120,7 @@ int main(int argc, char *argv[])
if (err)
{
const char *error_str = err_as_cstr(err);
fprintf(stderr, "[ERROR]: %s\n", error_str);
FAIL("ERROR", "%s\n", error_str);
vm_print_all(&vm, stderr);
ret = 255 - err;
}
@@ -134,7 +128,7 @@ int main(int argc, char *argv[])
vm_stop(&vm);
#if VERBOSE >= 1
printf("[%sINTERPRETER%s]: Finished execution\n", TERM_GREEN, TERM_RESET);
SUCCESS("INTEPRETER", "Finished execution\n%s", "");
#endif
return ret;
}

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
@@ -62,7 +61,7 @@ const char *err_as_cstr(err_t err)
}
}
static_assert(NUMBER_OF_OPCODES == 129, "vm_execute: Out of date");
static_assert(NUMBER_OF_OPCODES == 115, "vm_execute: Out of date");
err_t vm_execute(vm_t *vm)
{
@@ -99,11 +98,10 @@ err_t vm_execute(vm_t *vm)
"Code using OPCODE_DATA_TYPE for quick same type opcode "
"conversion may be out of date.");
// NOTE: We always use the first register to hold the result of
// this pop.
// NOTE: We always use the first register to hold the result of this pop.
// Here we add OP_MOV_BYTE and the data_type_t of the opcode to
// get the right typed OP_MOV opcode.
// Here we add OP_MOV_BYTE and the data_type_t of the opcode to get the
// right typed OP_MOV opcode.
opcode_t mov_opcode =
OPCODE_DATA_TYPE(instruction.opcode, OP_POP) + OP_MOV_BYTE;
@@ -120,13 +118,13 @@ err_t vm_execute(vm_t *vm)
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PLUS) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_SUB) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MULT) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MALLOC_STACK) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MSET_STACK) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MGET_STACK) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_LT) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_LTE) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GT) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GTE) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MALLOC) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MSET) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MGET) ||
instruction.opcode == OP_MDELETE || instruction.opcode == OP_MSIZE)
{
err_t err = STACK_ROUTINES[instruction.opcode](vm);
@@ -137,15 +135,6 @@ err_t vm_execute(vm_t *vm)
// Opcodes defined in loop
else if (instruction.opcode == OP_JUMP_ABS)
return vm_jump(vm, instruction.operand.as_word);
else if (instruction.opcode == OP_JUMP_STACK)
{
data_t ret = {0};
// Set prog->ptr to the word on top of the stack
err_t err = vm_pop_word(vm, &ret);
if (err)
return err;
return vm_jump(vm, ret.as_word);
}
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_JUMP_IF))
{
static_assert(DATA_TYPE_NIL == -1 && DATA_TYPE_WORD == 3,
@@ -153,8 +142,8 @@ err_t vm_execute(vm_t *vm)
"conversion may be out of date.");
data_t datum = {0};
// Here we add OP_POP_BYTE and the data_type_t of the opcode to
// get the right OP_POP opcode.
// Here we add OP_POP_BYTE and the data_type_t of the opcode to get the
// right OP_POP opcode.
opcode_t pop_opcode =
OPCODE_DATA_TYPE(instruction.opcode, OP_JUMP_IF) + OP_POP_BYTE;
@@ -174,17 +163,6 @@ err_t vm_execute(vm_t *vm)
vm->call_stack.address_pointers[vm->call_stack.ptr++] = vm->program.ptr + 1;
return vm_jump(vm, instruction.operand.as_word);
}
else if (instruction.opcode == OP_CALL_STACK)
{
if (vm->call_stack.ptr >= vm->call_stack.max)
return ERR_CALL_STACK_OVERFLOW;
vm->call_stack.address_pointers[vm->call_stack.ptr++] = vm->program.ptr + 1;
data_t ret = {0};
err_t err = vm_pop_word(vm, &ret);
if (err)
return err;
return vm_jump(vm, ret.as_word);
}
else if (instruction.opcode == OP_RET)
{
if (vm->call_stack.ptr == 0)
@@ -229,9 +207,8 @@ err_t vm_execute(vm_t *vm)
// 2) create a format string for each datum type possible
// TODO: Figure out a way to ensure the ordering of OP_PRINT_* is
// exactly BYTE, SBYTE, SHORT, SSHORT, HWORD, SHWORD, WORD, SWORD
// via static_assert
// TODO: Figure out a way to ensure the ordering of OP_PRINT_* is exactly
// BYTE, SBYTE, SHORT, SSHORT, HWORD, SHWORD, WORD, SWORD via static_assert
// lookup table
const char *format_strings[] = {
@@ -286,7 +263,7 @@ err_t vm_execute_all(vm_t *vm)
program->data.instructions[program->ptr].opcode != OP_HALT)
{
#if VERBOSE >= 2
fprintf(stdout, "[vm_execute_all]: Trace(Cycle %lu)\n", cycles);
INFO("vm_execute_all", "Trace(Cycle%lu)\n", cycles);
fputs(
"----------------------------------------------------------------------"
"----------\n",
@@ -342,8 +319,7 @@ err_t vm_execute_all(vm_t *vm)
}
#if VERBOSE >= 1
fprintf(stdout, "[%svm_execute_all%s]: Final VM state(Cycle %lu)\n",
TERM_YELLOW, TERM_RESET, cycles);
INFO("vm_execute_all", "Final VM State(Cycle %lu)\n", cycles);
vm_print_all(vm, stdout);
#endif
return err;
@@ -514,11 +490,15 @@ 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)); \
#define VM_MALLOC_CONSTR(TYPE, TYPE_CAP) \
err_t vm_malloc_##TYPE(vm_t *vm) \
{ \
data_t n = {0}; \
err_t err = vm_pop_word(vm, &n); \
if (err) \
return err; \
page_t *page = heap_allocate(&vm->heap, n.as_word * TYPE_CAP##_SIZE); \
return vm_push_word(vm, DWORD((word_t)page)); \
}
VM_MALLOC_CONSTR(byte, BYTE)
@@ -526,22 +506,26 @@ 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; \
#define VM_MSET_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mset_##TYPE(vm_t *vm) \
{ \
data_t n = {0}; \
err_t err = vm_pop_word(vm, &n); \
if (err) \
return err; \
data_t object = {0}; \
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 (n.as_word >= (page->available / TYPE_CAP##_SIZE)) \
return ERR_OUT_OF_BOUNDS; \
DARR_AT(TYPE##_t, page->data, n.as_word) = object.as_##TYPE; \
return ERR_OK; \
}
VM_MSET_CONSTR(byte, BYTE)
@@ -549,22 +533,26 @@ 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; \
#define VM_MGET_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mget_##TYPE(vm_t *vm) \
{ \
data_t n = {0}; \
err_t err = vm_pop_word(vm, &n); \
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 (n.as_word >= (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.as_word * (TYPE_CAP##_SIZE)), TYPE_CAP##_SIZE); \
vm->stack.ptr += TYPE_CAP##_SIZE; \
return ERR_OK; \
}
VM_MGET_CONSTR(byte, BYTE)
@@ -572,30 +560,6 @@ 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) \
err_t vm_##ACTION##_stack_##TYPE(vm_t *vm) \
{ \
data_t n = {0}; \
err_t err = vm_pop_word(vm, &n); \
if (err) \
return err; \
return vm_##ACTION##_##TYPE(vm, n.as_word); \
}
VM_MEMORY_STACK_CONSTR(malloc, byte)
VM_MEMORY_STACK_CONSTR(malloc, short)
VM_MEMORY_STACK_CONSTR(malloc, hword)
VM_MEMORY_STACK_CONSTR(malloc, word)
VM_MEMORY_STACK_CONSTR(mset, byte)
VM_MEMORY_STACK_CONSTR(mset, short)
VM_MEMORY_STACK_CONSTR(mset, hword)
VM_MEMORY_STACK_CONSTR(mset, word)
VM_MEMORY_STACK_CONSTR(mget, byte)
VM_MEMORY_STACK_CONSTR(mget, short)
VM_MEMORY_STACK_CONSTR(mget, hword)
VM_MEMORY_STACK_CONSTR(mget, word)
err_t vm_mdelete(vm_t *vm)
{
data_t ptr = {0};

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2023-10-15
* Author: Aryadev Chavali
@@ -86,21 +85,6 @@ err_t vm_dup_short(vm_t *, word_t);
err_t vm_dup_hword(vm_t *, word_t);
err_t vm_dup_word(vm_t *, word_t);
err_t vm_malloc_byte(vm_t *, word_t);
err_t vm_malloc_short(vm_t *, word_t);
err_t vm_malloc_hword(vm_t *, word_t);
err_t vm_malloc_word(vm_t *, word_t);
err_t vm_mset_byte(vm_t *, word_t);
err_t vm_mset_short(vm_t *, word_t);
err_t vm_mset_hword(vm_t *, word_t);
err_t vm_mset_word(vm_t *, word_t);
err_t vm_mget_byte(vm_t *, word_t);
err_t vm_mget_short(vm_t *, word_t);
err_t vm_mget_hword(vm_t *, word_t);
err_t vm_mget_word(vm_t *, word_t);
typedef err_t (*word_f)(vm_t *, word_t);
static const word_f WORD_ROUTINES[] = {
[OP_PUSH_REGISTER_BYTE] = vm_push_byte_register,
@@ -117,38 +101,23 @@ static const word_f WORD_ROUTINES[] = {
[OP_DUP_SHORT] = vm_dup_short,
[OP_DUP_HWORD] = vm_dup_hword,
[OP_DUP_WORD] = vm_dup_word,
[OP_MALLOC_BYTE] = vm_malloc_byte,
[OP_MALLOC_SHORT] = vm_malloc_short,
[OP_MALLOC_HWORD] = vm_malloc_hword,
[OP_MALLOC_WORD] = vm_malloc_word,
[OP_MGET_BYTE] = vm_mget_byte,
[OP_MGET_SHORT] = vm_mget_short,
[OP_MGET_HWORD] = vm_mget_hword,
[OP_MGET_WORD] = vm_mget_word,
[OP_MSET_BYTE] = vm_mset_byte,
[OP_MSET_SHORT] = vm_mset_short,
[OP_MSET_HWORD] = vm_mset_hword,
[OP_MSET_WORD] = vm_mset_word,
};
/* Operations that take input from the stack */
err_t vm_malloc_stack_byte(vm_t *);
err_t vm_malloc_stack_short(vm_t *);
err_t vm_malloc_stack_hword(vm_t *);
err_t vm_malloc_stack_word(vm_t *);
err_t vm_malloc_byte(vm_t *);
err_t vm_malloc_short(vm_t *);
err_t vm_malloc_hword(vm_t *);
err_t vm_malloc_word(vm_t *);
err_t vm_mset_stack_byte(vm_t *);
err_t vm_mset_stack_short(vm_t *);
err_t vm_mset_stack_hword(vm_t *);
err_t vm_mset_stack_word(vm_t *);
err_t vm_mset_byte(vm_t *);
err_t vm_mset_short(vm_t *);
err_t vm_mset_hword(vm_t *);
err_t vm_mset_word(vm_t *);
err_t vm_mget_stack_byte(vm_t *);
err_t vm_mget_stack_short(vm_t *);
err_t vm_mget_stack_hword(vm_t *);
err_t vm_mget_stack_word(vm_t *);
err_t vm_mget_byte(vm_t *);
err_t vm_mget_short(vm_t *);
err_t vm_mget_hword(vm_t *);
err_t vm_mget_word(vm_t *);
err_t vm_mdelete(vm_t *);
err_t vm_msize(vm_t *);
@@ -235,97 +204,59 @@ err_t vm_mult_word(vm_t *);
typedef err_t (*stack_f)(vm_t *);
static const stack_f STACK_ROUTINES[] = {
[OP_MALLOC_STACK_BYTE] = vm_malloc_stack_byte,
[OP_MALLOC_STACK_SHORT] = vm_malloc_stack_short,
[OP_MALLOC_STACK_HWORD] = vm_malloc_stack_hword,
[OP_MALLOC_STACK_WORD] = vm_malloc_stack_word,
[OP_MALLOC_BYTE] = vm_malloc_byte, [OP_MALLOC_SHORT] = vm_malloc_short,
[OP_MALLOC_HWORD] = vm_malloc_hword, [OP_MALLOC_WORD] = vm_malloc_word,
[OP_MGET_STACK_BYTE] = vm_mget_stack_byte,
[OP_MGET_STACK_SHORT] = vm_mget_stack_short,
[OP_MGET_STACK_HWORD] = vm_mget_stack_hword,
[OP_MGET_STACK_WORD] = vm_mget_stack_word,
[OP_MSET_STACK_BYTE] = vm_mset_stack_byte,
[OP_MSET_STACK_SHORT] = vm_mset_stack_short,
[OP_MSET_STACK_HWORD] = vm_mset_stack_hword,
[OP_MSET_STACK_WORD] = vm_mset_stack_word,
[OP_MGET_BYTE] = vm_mget_byte, [OP_MGET_SHORT] = vm_mget_short,
[OP_MGET_HWORD] = vm_mget_hword, [OP_MGET_WORD] = vm_mget_word,
[OP_MDELETE] = vm_mdelete,
[OP_MSIZE] = vm_msize,
[OP_MSET_BYTE] = vm_mset_byte, [OP_MSET_SHORT] = vm_mset_short,
[OP_MSET_HWORD] = vm_mset_hword, [OP_MSET_WORD] = vm_mset_word,
[OP_NOT_BYTE] = vm_not_byte,
[OP_NOT_SHORT] = vm_not_short,
[OP_NOT_HWORD] = vm_not_hword,
[OP_NOT_WORD] = vm_not_word,
[OP_MDELETE] = vm_mdelete, [OP_MSIZE] = vm_msize,
[OP_OR_BYTE] = vm_or_byte,
[OP_OR_SHORT] = vm_or_short,
[OP_OR_HWORD] = vm_or_hword,
[OP_OR_WORD] = vm_or_word,
[OP_NOT_BYTE] = vm_not_byte, [OP_NOT_SHORT] = vm_not_short,
[OP_NOT_HWORD] = vm_not_hword, [OP_NOT_WORD] = vm_not_word,
[OP_AND_BYTE] = vm_and_byte,
[OP_AND_SHORT] = vm_and_short,
[OP_AND_HWORD] = vm_and_hword,
[OP_AND_WORD] = vm_and_word,
[OP_OR_BYTE] = vm_or_byte, [OP_OR_SHORT] = vm_or_short,
[OP_OR_HWORD] = vm_or_hword, [OP_OR_WORD] = vm_or_word,
[OP_XOR_BYTE] = vm_xor_byte,
[OP_XOR_SHORT] = vm_xor_short,
[OP_XOR_HWORD] = vm_xor_hword,
[OP_XOR_WORD] = vm_xor_word,
[OP_AND_BYTE] = vm_and_byte, [OP_AND_SHORT] = vm_and_short,
[OP_AND_HWORD] = vm_and_hword, [OP_AND_WORD] = vm_and_word,
[OP_EQ_BYTE] = vm_eq_byte,
[OP_EQ_SHORT] = vm_eq_short,
[OP_EQ_HWORD] = vm_eq_hword,
[OP_EQ_WORD] = vm_eq_word,
[OP_XOR_BYTE] = vm_xor_byte, [OP_XOR_SHORT] = vm_xor_short,
[OP_XOR_HWORD] = vm_xor_hword, [OP_XOR_WORD] = vm_xor_word,
[OP_LT_BYTE] = vm_lt_byte,
[OP_LT_SBYTE] = vm_lt_sbyte,
[OP_LT_SHORT] = vm_lt_short,
[OP_LT_SSHORT] = vm_lt_sshort,
[OP_LT_SHWORD] = vm_lt_shword,
[OP_LT_HWORD] = vm_lt_hword,
[OP_LT_SWORD] = vm_lt_sword,
[OP_LT_WORD] = vm_lt_word,
[OP_EQ_BYTE] = vm_eq_byte, [OP_EQ_SHORT] = vm_eq_short,
[OP_EQ_HWORD] = vm_eq_hword, [OP_EQ_WORD] = vm_eq_word,
[OP_LTE_BYTE] = vm_lte_byte,
[OP_LTE_SBYTE] = vm_lte_sbyte,
[OP_LTE_SHORT] = vm_lte_short,
[OP_LTE_SSHORT] = vm_lte_sshort,
[OP_LTE_SHWORD] = vm_lte_shword,
[OP_LTE_HWORD] = vm_lte_hword,
[OP_LTE_SWORD] = vm_lte_sword,
[OP_LTE_WORD] = vm_lte_word,
[OP_LT_BYTE] = vm_lt_byte, [OP_LT_SBYTE] = vm_lt_sbyte,
[OP_LT_SHORT] = vm_lt_short, [OP_LT_SSHORT] = vm_lt_sshort,
[OP_LT_SHWORD] = vm_lt_shword, [OP_LT_HWORD] = vm_lt_hword,
[OP_LT_SWORD] = vm_lt_sword, [OP_LT_WORD] = vm_lt_word,
[OP_GT_BYTE] = vm_gt_byte,
[OP_GT_SBYTE] = vm_gt_sbyte,
[OP_GT_SHORT] = vm_gt_short,
[OP_GT_SSHORT] = vm_gt_sshort,
[OP_GT_SHWORD] = vm_gt_shword,
[OP_GT_HWORD] = vm_gt_hword,
[OP_GT_SWORD] = vm_gt_sword,
[OP_GT_WORD] = vm_gt_word,
[OP_LTE_BYTE] = vm_lte_byte, [OP_LTE_SBYTE] = vm_lte_sbyte,
[OP_LTE_SHORT] = vm_lte_short, [OP_LTE_SSHORT] = vm_lte_sshort,
[OP_LTE_SHWORD] = vm_lte_shword, [OP_LTE_HWORD] = vm_lte_hword,
[OP_LTE_SWORD] = vm_lte_sword, [OP_LTE_WORD] = vm_lte_word,
[OP_GTE_BYTE] = vm_gte_byte,
[OP_GTE_SBYTE] = vm_gte_sbyte,
[OP_GTE_SHORT] = vm_gte_short,
[OP_GTE_SSHORT] = vm_gte_sshort,
[OP_GTE_SHWORD] = vm_gte_shword,
[OP_GTE_HWORD] = vm_gte_hword,
[OP_GTE_SWORD] = vm_gte_sword,
[OP_GTE_WORD] = vm_gte_word,
[OP_GT_BYTE] = vm_gt_byte, [OP_GT_SBYTE] = vm_gt_sbyte,
[OP_GT_SHORT] = vm_gt_short, [OP_GT_SSHORT] = vm_gt_sshort,
[OP_GT_SHWORD] = vm_gt_shword, [OP_GT_HWORD] = vm_gt_hword,
[OP_GT_SWORD] = vm_gt_sword, [OP_GT_WORD] = vm_gt_word,
[OP_PLUS_BYTE] = vm_plus_byte,
[OP_PLUS_SHORT] = vm_plus_short,
[OP_PLUS_HWORD] = vm_plus_hword,
[OP_PLUS_WORD] = vm_plus_word,
[OP_SUB_BYTE] = vm_sub_byte,
[OP_SUB_SHORT] = vm_sub_short,
[OP_SUB_HWORD] = vm_sub_hword,
[OP_SUB_WORD] = vm_sub_word,
[OP_GTE_BYTE] = vm_gte_byte, [OP_GTE_SBYTE] = vm_gte_sbyte,
[OP_GTE_SHORT] = vm_gte_short, [OP_GTE_SSHORT] = vm_gte_sshort,
[OP_GTE_SHWORD] = vm_gte_shword, [OP_GTE_HWORD] = vm_gte_hword,
[OP_GTE_SWORD] = vm_gte_sword, [OP_GTE_WORD] = vm_gte_word,
[OP_MULT_BYTE] = vm_mult_byte,
[OP_MULT_SHORT] = vm_mult_short,
[OP_MULT_HWORD] = vm_mult_hword,
[OP_MULT_WORD] = vm_mult_word,
[OP_PLUS_BYTE] = vm_plus_byte, [OP_PLUS_SHORT] = vm_plus_short,
[OP_PLUS_HWORD] = vm_plus_hword, [OP_PLUS_WORD] = vm_plus_word,
[OP_SUB_BYTE] = vm_sub_byte, [OP_SUB_SHORT] = vm_sub_short,
[OP_SUB_HWORD] = vm_sub_hword, [OP_SUB_WORD] = vm_sub_word,
[OP_MULT_BYTE] = vm_mult_byte, [OP_MULT_SHORT] = vm_mult_short,
[OP_MULT_HWORD] = vm_mult_hword, [OP_MULT_WORD] = vm_mult_word,
};
#endif

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-25
* Author: Aryadev Chavali
@@ -53,18 +52,16 @@ void vm_stop(vm_t *vm)
{
#if VERBOSE >= 1
bool leaks = false;
printf("[" TERM_YELLOW "DATA" TERM_RESET "]: Checking for leaks...\n");
INFO("vm_stop", "Checking for leaks...\n%s", "");
if (vm->call_stack.ptr > 0)
{
leaks = true;
printf("\t[" TERM_RED "DATA" TERM_RESET "]: Call stack at %lu\n\t[" TERM_RED
"DATA" TERM_RESET "]\n\t[" TERM_RED "DATA" TERM_RESET "]: Call "
"stack trace:",
vm->call_stack.ptr);
FAIL("vm_stop", "Call stack at %lu\n", vm->call_stack.ptr);
FAIL("vm_stop", "Call stack trace:%s", "");
for (size_t i = vm->call_stack.ptr; i > 0; --i)
{
word_t w = vm->call_stack.address_pointers[i - 1];
printf("\t\t%lu: %lX", vm->call_stack.ptr - i, w);
printf("\t[%lu]: %lX", vm->call_stack.ptr - i, w);
if (i != 1)
printf(", ");
printf("\n");
@@ -81,22 +78,20 @@ void vm_stop(vm_t *vm)
capacities[i] = cur->available;
total_capacity += capacities[i];
}
printf("\t[" TERM_RED "DATA" TERM_RESET
"]: Heap: %luB (over %lu %s) not reclaimed\n",
total_capacity, size_pages, size_pages == 1 ? "page" : "pages");
FAIL("vm_stop", "Heap: %luB (over %lu %s) not reclaimed\n", total_capacity,
size_pages, size_pages == 1 ? "page" : "pages");
for (size_t i = 0; i < size_pages; i++)
printf("\t\t[%lu]: %luB lost\n", i, capacities[i]);
printf("\t[%lu]: %luB lost\n", i, capacities[i]);
}
if (vm->stack.ptr > 0)
{
leaks = true;
printf("\t[" TERM_RED "DATA" TERM_RESET "]: Stack: %luB not reclaimed\n",
vm->stack.ptr);
FAIL("vm_stop", "Stack: %luB not reclaimed\n", vm->stack.ptr);
}
if (leaks)
printf("[" TERM_RED "DATA" TERM_RESET "]: Leaks found\n");
FAIL("vm_stop", "Leaks found\n%s", "");
else
printf("[" TERM_GREEN "DATA" TERM_RESET "]: No leaks found\n");
SUCCESS("vm_stop", "No leaks found\n%s", "");
#endif
vm->registers = (struct Registers){0};

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali
* 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.
* 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 Version 2 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/>.
* You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Created: 2024-04-25
* Author: Aryadev Chavali