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 -*- ;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables") ;;; 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"))) (compile-command . "make all VERBOSE=2 RELEASE=0")))
(c-mode . ((mode . clang-format) (c-mode . ((mode . clang-format)
(eval . (eglot-ensure))))) (eval . (eglot-ensure)))))

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-26 * Created: 2023-10-26
* Author: Aryadev Chavali * Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-15 * Created: 2023-10-15
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -28,6 +27,16 @@
#define TERM_RED "\033[31m" #define TERM_RED "\033[31m"
#define TERM_RESET "\033[0m" #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) // Flags for program behaviour (usually related to printing)
#ifndef VERBOSE #ifndef VERBOSE
#define VERBOSE 0 #define VERBOSE 0
@@ -92,8 +101,7 @@ typedef union
} data_t; } data_t;
/** /**
@brief Enum of type tags for the data_t structure to provide @brief Enum of type tags for the data_t structure to provide context.
context.
*/ */
typedef enum typedef enum
{ {
@@ -117,11 +125,10 @@ static const hword_t __i = 0xFFFF0000;
#endif #endif
/** /**
@brief Safely subtract SUB from W, where both are words (64 bit @brief Safely subtract SUB from W, where both are words (64 bit integers).
integers).
@details In case of underflow (i.e. where W - SUB < 0) returns 0 @details In case of underflow (i.e. where W - SUB < 0) returns 0 instead of
instead of the underflowed result. the underflowed result.
*/ */
#define WORD_SAFE_SUB(W, SUB) ((W) > (SUB) ? ((W) - (SUB)) : 0) #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. @brief Return the Nth half word of WORD.
@details N should range from 0 to 1 as there are 2 half words in a @details N should range from 0 to 1 as there are 2 half words in a word
word
*/ */
#define WORD_NTH_HWORD(WORD, N) (((WORD) >> ((N) * 32)) & 0xFFFFFFFF) #define WORD_NTH_HWORD(WORD, N) (((WORD) >> ((N) * 32)) & 0xFFFFFFFF)
/** /**
@brief Convert a buffer of bytes to a short @brief Convert a buffer of bytes to a short
@details It is assumed that the buffer of bytes are in virtual @details It is assumed that the buffer of bytes are in virtual machine byte
machine byte code format (little endian) and that they are at least code format (little endian) and that they are at least SHORT_SIZE in size.
SHORT_SIZE in size.
*/ */
short_t convert_bytes_to_short(const byte_t *buffer); 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 s: Short to convert
@param buffer: Buffer to store into. It is assumed that the buffer @param buffer: Buffer to store into. It is assumed that the buffer has at
has at least SHORT_SIZE space. least SHORT_SIZE space.
*/ */
void convert_short_to_bytes(const short_t s, byte_t *buffer); void convert_short_to_bytes(const short_t s, byte_t *buffer);
/** /**
@brief Convert a buffer of bytes to a half word. @brief Convert a buffer of bytes to a half word.
@details It is assumed that the buffer of bytes are in virtual @details It is assumed that the buffer of bytes are in virtual machine byte
machine byte code format (little endian) and that they are at least code format (little endian) and that they are at least HWORD_SIZE in size.
HWORD_SIZE in size.
*/ */
hword_t convert_bytes_to_hword(const byte_t *buffer); 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 h: Half word to convert
@param buffer: Buffer to store into. It is assumed that the buffer @param buffer: Buffer to store into. It is assumed that the buffer has at
has at least HWORD_SIZE space. least HWORD_SIZE space.
*/ */
void convert_hword_to_bytes(const hword_t h, byte_t *buffer); void convert_hword_to_bytes(const hword_t h, byte_t *buffer);
/** /**
@brief Convert a buffer of bytes to a word. @brief Convert a buffer of bytes to a word.
@details It is assumed that the buffer of bytes are in virtual @details It is assumed that the buffer of bytes are in virtual machine byte
machine byte code format (little endian) and that they are at least code format (little endian) and that they are at least WORD_SIZE in size.
WORD_SIZE in size.
*/ */
word_t convert_bytes_to_word(const byte_t *); word_t convert_bytes_to_word(const byte_t *);
/** /**
@brief Convert a word into a VM byte code format bytes (little @brief Convert a word into a VM byte code format bytes (little endian)
endian)
@param w: Word to convert @param w: Word to convert
@param buffer: Buffer to store into. It is assumed that the buffer @param buffer: Buffer to store into. It is assumed that the buffer has at
has at least WORD_SIZE space. least WORD_SIZE space.
*/ */
void convert_word_to_bytes(const word_t w, byte_t *buffer); void convert_word_to_bytes(const word_t w, byte_t *buffer);
/** /**
@brief Swap the ordering of bytes within an short @brief Swap the ordering of bytes within an short
@details The ordering of the bytes in the short are reversed (2 @details The ordering of the bytes in the short are reversed (2 bytes in a
bytes in a short). short).
@param s: short to swap @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 @brief Swap the ordering of bytes within an half word
@details The ordering of the bytes in the half word are reversed (4 @details The ordering of the bytes in the half word are reversed (4 bytes in
bytes in a half word). a half word).
@param h: Half word to swap @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 @brief Swap the ordering of bytes within an word
@details The ordering of the bytes in the word are reversed (8 @details The ordering of the bytes in the word are reversed (8 bytes in a
bytes in a word). word).
@param w: Word to swap @param w: Word to swap
*/ */

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-15 * Created: 2023-10-15
* Author: Aryadev Chavali * Author: Aryadev Chavali

View File

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

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-11-01 * Created: 2023-11-01
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -60,8 +59,7 @@ bool heap_free(heap_t *heap, page_t *page)
if (cur == page) if (cur == page)
{ {
page_delete(cur); page_delete(cur);
// TODO: When does this fragmentation become a performance // TODO: When does this fragmentation become a performance issue?
// issue?
DARR_AT(page_t *, heap->page_vec.data, i) = NULL; DARR_AT(page_t *, heap->page_vec.data, i) = NULL;
return true; return true;
} }

View File

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

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-28 * Created: 2024-04-28
* Author: Aryadev Chavali * Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-15 * Created: 2023-10-15
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -77,14 +76,6 @@ const char *opcode_as_cstr(opcode_t code)
return "MALLOC_HWORD"; return "MALLOC_HWORD";
case OP_MALLOC_WORD: case OP_MALLOC_WORD:
return "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: case OP_MSET_BYTE:
return "MSET_BYTE"; return "MSET_BYTE";
case OP_MSET_SHORT: case OP_MSET_SHORT:
@@ -93,14 +84,6 @@ const char *opcode_as_cstr(opcode_t code)
return "MSET_HWORD"; return "MSET_HWORD";
case OP_MSET_WORD: case OP_MSET_WORD:
return "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: case OP_MGET_BYTE:
return "MGET_BYTE"; return "MGET_BYTE";
case OP_MGET_SHORT: case OP_MGET_SHORT:
@@ -109,14 +92,6 @@ const char *opcode_as_cstr(opcode_t code)
return "MGET_HWORD"; return "MGET_HWORD";
case OP_MGET_WORD: case OP_MGET_WORD:
return "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: case OP_MDELETE:
return "MDELETE"; return "MDELETE";
case OP_MSIZE: case OP_MSIZE:
@@ -267,8 +242,6 @@ const char *opcode_as_cstr(opcode_t code)
return "PRINT_SWORD"; return "PRINT_SWORD";
case OP_JUMP_ABS: case OP_JUMP_ABS:
return "JUMP_ABS"; return "JUMP_ABS";
case OP_JUMP_STACK:
return "JUMP_STACK";
case OP_JUMP_IF_BYTE: case OP_JUMP_IF_BYTE:
return "JUMP_IF_BYTE"; return "JUMP_IF_BYTE";
case OP_JUMP_IF_SHORT: case OP_JUMP_IF_SHORT:
@@ -279,8 +252,6 @@ const char *opcode_as_cstr(opcode_t code)
return "JUMP_IF_WORD"; return "JUMP_IF_WORD";
case OP_CALL: case OP_CALL:
return "CALL"; return "CALL";
case OP_CALL_STACK:
return "CALL_STACK";
case OP_RET: case OP_RET:
return "RET"; return "RET";
case NUMBER_OF_OPCODES: 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) 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)); fprintf(fp, "%s(", opcode_as_cstr(instruction.opcode));
if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_PUSH)) 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"); fprintf(fp, "reg=0x");
data_print(instruction.operand, DATA_TYPE_BYTE, fp); data_print(instruction.operand, DATA_TYPE_BYTE, fp);
} }
else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_DUP) || 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))
{ {
fprintf(fp, "n=0x%lX", instruction.operand.as_word); 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) 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 size_t size = 1; // for opcode
if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH)) 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) 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; bytes[0] = inst.opcode;
size_t written = 1; 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_DUP) ||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_MOV) || UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_MOV) ||
UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_DUP) || 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) || UNSIGNED_OPCODE_IS_TYPE(inst.opcode, OP_JUMP_IF) ||
inst.opcode == OP_JUMP_ABS || inst.opcode == OP_CALL) inst.opcode == OP_JUMP_ABS || inst.opcode == OP_CALL)
to_append = DATA_TYPE_WORD; 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) 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++); opcode_t opcode = *(bytes++);
if (opcode >= NUMBER_OF_OPCODES || opcode < OP_NOOP) 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)) if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH))
success = read_type_from_darr(bytes, size_bytes, (data_type_t)opcode, success = read_type_from_darr(bytes, size_bytes, (data_type_t)opcode,
&inst.operand); &inst.operand);
// Read register (as a byte) // Read operand as a word
else if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH_REGISTER) || else if (UNSIGNED_OPCODE_IS_TYPE(opcode, OP_PUSH_REGISTER) ||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MOV) || UNSIGNED_OPCODE_IS_TYPE(opcode, OP_MOV) ||
UNSIGNED_OPCODE_IS_TYPE(opcode, OP_DUP) || 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) || UNSIGNED_OPCODE_IS_TYPE(opcode, OP_JUMP_IF) ||
opcode == OP_JUMP_ABS || opcode == OP_CALL) opcode == OP_JUMP_ABS || opcode == OP_CALL)
success = success =

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-15 * Created: 2023-10-15
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -65,31 +64,16 @@ typedef enum
OP_MALLOC_HWORD, OP_MALLOC_HWORD,
OP_MALLOC_WORD, OP_MALLOC_WORD,
OP_MALLOC_STACK_BYTE,
OP_MALLOC_STACK_SHORT,
OP_MALLOC_STACK_HWORD,
OP_MALLOC_STACK_WORD,
OP_MSET_BYTE, OP_MSET_BYTE,
OP_MSET_SHORT, OP_MSET_SHORT,
OP_MSET_HWORD, OP_MSET_HWORD,
OP_MSET_WORD, OP_MSET_WORD,
OP_MSET_STACK_BYTE,
OP_MSET_STACK_SHORT,
OP_MSET_STACK_HWORD,
OP_MSET_STACK_WORD,
OP_MGET_BYTE, OP_MGET_BYTE,
OP_MGET_SHORT, OP_MGET_SHORT,
OP_MGET_HWORD, OP_MGET_HWORD,
OP_MGET_WORD, OP_MGET_WORD,
OP_MGET_STACK_BYTE,
OP_MGET_STACK_SHORT,
OP_MGET_STACK_HWORD,
OP_MGET_STACK_WORD,
OP_MDELETE, OP_MDELETE,
OP_MSIZE, OP_MSIZE,
@@ -184,7 +168,6 @@ typedef enum
// Program control flow // Program control flow
OP_JUMP_ABS, OP_JUMP_ABS,
OP_JUMP_STACK,
OP_JUMP_IF_BYTE, OP_JUMP_IF_BYTE,
OP_JUMP_IF_SHORT, OP_JUMP_IF_SHORT,
OP_JUMP_IF_HWORD, OP_JUMP_IF_HWORD,
@@ -192,7 +175,6 @@ typedef enum
// Subroutines // Subroutines
OP_CALL, OP_CALL,
OP_CALL_STACK,
OP_RET, OP_RET,
// Should not be an opcode // Should not be an opcode
@@ -211,10 +193,10 @@ typedef struct
/** /**
@brief Serialise an instruction into a byte buffer @brief Serialise an instruction into a byte buffer
@details Given an instruction and a suitably sized byte buffer, @details Given an instruction and a suitably sized byte buffer, write the
write the bytecode for the instruction into the buffer. NOTE: This bytecode for the instruction into the buffer. NOTE: This function does NOT
function does NOT check the bounds of `bytes` i.e. we assume the check the bounds of `bytes` i.e. we assume the caller has created a suitably
caller has created a suitably sized buffer. sized buffer.
@param[inst] Instruction to serialise @param[inst] Instruction to serialise
@param[bytes] Buffer to write on @param[bytes] Buffer to write on
@@ -234,21 +216,19 @@ typedef enum
/** /**
@brief Deserialise an instruction from a bytecode buffer @brief Deserialise an instruction from a bytecode buffer
@details Given a buffer of bytes, deserialise an instruction, @details Given a buffer of bytes, deserialise an instruction, storing the
storing the result in the pointer given. The number of bytes read result in the pointer given. The number of bytes read in the buffer is
in the buffer is returned, which should be opcode_bytecode_size(). returned, which should be opcode_bytecode_size(). NOTE: If bytes is not
NOTE: If bytes is not suitably sized for the instruction expected suitably sized for the instruction expected or it is not well formed i.e. not
or it is not well formed i.e. not the right schema then a negative the right schema then a negative number is returned.
number is returned.
@param[inst] Pointer to instruction which will store result @param[inst] Pointer to instruction which will store result
@param[bytes] Bytecode buffer to deserialise @param[bytes] Bytecode buffer to deserialise
@param[size_bytes] Number of bytes in buffer @param[size_bytes] Number of bytes in buffer
@return[int] Number of bytes read. If negative then an error @return[int] Number of bytes read. If negative then an error occurred in
occurred in deserialisation (either buffer was not suitably sized deserialisation (either buffer was not suitably sized or instruction was not
or instruction was not well formed) so any result must be well formed) so any result must be considered invalid.
considered invalid.
*/ */
int inst_read_bytecode(inst_t *inst, byte_t *bytes, size_t size_bytes); 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 #+description: A specification of instructions for the virtual machine
#+date: 2023-11-02 #+date: 2023-11-02
* WIP Data types * Data types
There are 3 main data types of the virtual machine. They are all There are 4 main data types of the virtual machine. They are all
unsigned. There exist signed versions of these data types, though unsigned.
there is no difference (internally) between them. For an unsigned
type <T> the signed version is simply S_<T>.
|-------+------| |-------+------|
| Name | Bits | | Name | Bits |
|-------+------| |-------+------|
| Byte | 8 | | Byte | 8 |
| Short | 16 |
| HWord | 32 | | HWord | 32 |
| Word | 64 | | Word | 64 |
|-------+------| |-------+------|
Generally, the abbreviations B, H and W are used for Byte, HWord and Generally, the abbreviations B, S, H and W are used for Byte, Short,
Word respectively. The following table shows a comparison between the HWord and Word respectively. The following table shows a comparison
data types where an entry (row and column) $A\times{B}$ refers to "How between the data types where an entry (row and column) $A\times{B}$
many of A can I fit in B". refers to "How many of A can I fit in B".
|-------+------+-------+------| |-------+------+-------+-------+------|
| | Byte | Hword | Word | | | Byte | Short | HWord | Word |
|-------+------+-------+------| |-------+------+-------+-------+------|
| Byte | 1 | 4 | 8 | | Byte | 1 | 2 | 4 | 8 |
| HWord | 1/4 | 1 | 2 | | Short | 1/2 | 1 | 2 | 4 |
| Word | 1/8 | 1/2 | 1 | | 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 * WIP Instructions
An instruction for the virtual machine is composed of an *opcode* and, An instruction for the virtual machine is composed of an *opcode* and,
potentially, an *operand*. The /opcode/ represents the behaviour of optionally, an *operand*. The /opcode/ represents the specific
the instruction i.e. what _is_ the instruction. The /operand/ is an behaviour of the instruction i.e. what the instruction does. The
element of one of the /data types/ described previously. /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 The *order* of an operation is the number of specialisations it has
type of instructions are called *UNIT* instructions while the latter i.e. the number of opcodes that specialise one operation.
type are called *MULTI* instructions[fn:1].
All /opcodes/ (with very few exceptions[fn:2]) have two components: Some operations may not be generic over data types in which case they
the *root* and the *type specifier*. The /root/ represents the are of order 1 i.e. the opcode describes the exact behaviour of only
general behaviour of the instruction: ~PUSH~, ~POP~, ~MOV~, etc. The one operation.
/type specifier/ specifies what /data type/ it manipulates. A
complete opcode will be a combination of these two e.g. ~PUSH_BYTE~, There are only 3 possible orders for operations: 1, 4 and 8. They are
~POP_WORD~, etc. Some /opcodes/ may have more /type specifiers/ than given the names Nil, Unsigned and Signed for specialising over:
others. + 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 * TODO Bytecode format
Bytecode files are byte sequence which encode instructions for the Bytecode files are byte sequence which encode instructions for the
virtual machine. Any instruction (even with an operand) has one and virtual machine. Any instruction (even with an operand) has one and
only one byte sequence associated with it. 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 * 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 /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-28 * Created: 2024-04-28
* Author: Aryadev Chavali * Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-28 * Created: 2024-04-28
* Author: Aryadev Chavali * Author: Aryadev Chavali

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-28 * Created: 2024-04-28
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -71,8 +70,8 @@ void test_lib_darr_ensure_capacity_expands(void)
{10, 10, 10, 20}, {10, 10, 10, 20},
{50, 100, 300, 350}, {50, 100, 300, 350},
{1 << 20, 2 << 20, 2 << 20, 3 << 20}, {1 << 20, 2 << 20, 2 << 20, 3 << 20},
// When we reallocate we allocate MORE than needed (for // When we reallocate we allocate MORE than needed (for amortized
// amortized constant) // constant)
{1, 5, 5, 10}, {1, 5, 5, 10},
{85, 100, 40, 200}, {85, 100, 40, 200},
{4 << 20, 5 << 20, 1 << 23, 5 << 21}, {4 << 20, 5 << 20, 1 << 23, 5 << 21},

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-28 * Created: 2024-04-28
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -25,16 +24,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.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); typedef void (*test_fn)(void);
struct Test struct Test
{ {

147
todo.org
View File

@@ -3,6 +3,153 @@
#+date: 2023-11-02 #+date: 2023-11-02
#+startup: noindent #+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 * TODO Rework heap to use one allocation
The current approach for the heap is so: The current approach for the heap is so:
+ Per call to ~malloc~, allocate a new ~page_t~ structure by + Per call to ~malloc~, allocate a new ~page_t~ structure by

View File

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

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-15 * Created: 2023-10-15
* Author: Aryadev Chavali * 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) 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 " "Code using OPCODE_DATA_TYPE for quick same type opcode "
"conversion may be out of date."); "conversion may be out of date.");
// NOTE: We always use the first register to hold the result of // NOTE: We always use the first register to hold the result of this pop.
// this pop.
// Here we add OP_MOV_BYTE and the data_type_t of the opcode to // Here we add OP_MOV_BYTE and the data_type_t of the opcode to get the
// get the right typed OP_MOV opcode. // right typed OP_MOV opcode.
opcode_t mov_opcode = opcode_t mov_opcode =
OPCODE_DATA_TYPE(instruction.opcode, OP_POP) + OP_MOV_BYTE; 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_PLUS) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_SUB) || UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_SUB) ||
UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_MULT) || 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_LT) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_LTE) || SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_LTE) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GT) || SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GT) ||
SIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_GTE) || 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) instruction.opcode == OP_MDELETE || instruction.opcode == OP_MSIZE)
{ {
err_t err = STACK_ROUTINES[instruction.opcode](vm); err_t err = STACK_ROUTINES[instruction.opcode](vm);
@@ -137,15 +135,6 @@ err_t vm_execute(vm_t *vm)
// Opcodes defined in loop // Opcodes defined in loop
else if (instruction.opcode == OP_JUMP_ABS) else if (instruction.opcode == OP_JUMP_ABS)
return vm_jump(vm, instruction.operand.as_word); 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)) else if (UNSIGNED_OPCODE_IS_TYPE(instruction.opcode, OP_JUMP_IF))
{ {
static_assert(DATA_TYPE_NIL == -1 && DATA_TYPE_WORD == 3, 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."); "conversion may be out of date.");
data_t datum = {0}; data_t datum = {0};
// Here we add OP_POP_BYTE and the data_type_t of the opcode to // Here we add OP_POP_BYTE and the data_type_t of the opcode to get the
// get the right OP_POP opcode. // right OP_POP opcode.
opcode_t pop_opcode = opcode_t pop_opcode =
OPCODE_DATA_TYPE(instruction.opcode, OP_JUMP_IF) + OP_POP_BYTE; 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; vm->call_stack.address_pointers[vm->call_stack.ptr++] = vm->program.ptr + 1;
return vm_jump(vm, instruction.operand.as_word); 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) else if (instruction.opcode == OP_RET)
{ {
if (vm->call_stack.ptr == 0) 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 // 2) create a format string for each datum type possible
// TODO: Figure out a way to ensure the ordering of OP_PRINT_* is // TODO: Figure out a way to ensure the ordering of OP_PRINT_* is exactly
// exactly BYTE, SBYTE, SHORT, SSHORT, HWORD, SHWORD, WORD, SWORD // BYTE, SBYTE, SHORT, SSHORT, HWORD, SHWORD, WORD, SWORD via static_assert
// via static_assert
// lookup table // lookup table
const char *format_strings[] = { const char *format_strings[] = {
@@ -286,7 +263,7 @@ err_t vm_execute_all(vm_t *vm)
program->data.instructions[program->ptr].opcode != OP_HALT) program->data.instructions[program->ptr].opcode != OP_HALT)
{ {
#if VERBOSE >= 2 #if VERBOSE >= 2
fprintf(stdout, "[vm_execute_all]: Trace(Cycle %lu)\n", cycles); INFO("vm_execute_all", "Trace(Cycle%lu)\n", cycles);
fputs( fputs(
"----------------------------------------------------------------------" "----------------------------------------------------------------------"
"----------\n", "----------\n",
@@ -342,8 +319,7 @@ err_t vm_execute_all(vm_t *vm)
} }
#if VERBOSE >= 1 #if VERBOSE >= 1
fprintf(stdout, "[%svm_execute_all%s]: Final VM state(Cycle %lu)\n", INFO("vm_execute_all", "Final VM State(Cycle %lu)\n", cycles);
TERM_YELLOW, TERM_RESET, cycles);
vm_print_all(vm, stdout); vm_print_all(vm, stdout);
#endif #endif
return err; return err;
@@ -514,11 +490,15 @@ VM_DUP_CONSTR(short, SHORT)
VM_DUP_CONSTR(hword, HWORD) VM_DUP_CONSTR(hword, HWORD)
VM_DUP_CONSTR(word, WORD) VM_DUP_CONSTR(word, WORD)
#define VM_MALLOC_CONSTR(TYPE, TYPE_CAP) \ #define VM_MALLOC_CONSTR(TYPE, TYPE_CAP) \
err_t vm_malloc_##TYPE(vm_t *vm, word_t n) \ err_t vm_malloc_##TYPE(vm_t *vm) \
{ \ { \
page_t *page = heap_allocate(&vm->heap, n * TYPE_CAP##_SIZE); \ data_t n = {0}; \
return vm_push_word(vm, DWORD((word_t)page)); \ 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) VM_MALLOC_CONSTR(byte, BYTE)
@@ -526,22 +506,26 @@ VM_MALLOC_CONSTR(short, SHORT)
VM_MALLOC_CONSTR(hword, HWORD) VM_MALLOC_CONSTR(hword, HWORD)
VM_MALLOC_CONSTR(word, WORD) VM_MALLOC_CONSTR(word, WORD)
#define VM_MSET_CONSTR(TYPE, TYPE_CAP) \ #define VM_MSET_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mset_##TYPE(vm_t *vm, word_t nth) \ err_t vm_mset_##TYPE(vm_t *vm) \
{ \ { \
data_t object = {0}; \ data_t n = {0}; \
err_t err = vm_pop_##TYPE(vm, &object); \ err_t err = vm_pop_word(vm, &n); \
if (err) \ if (err) \
return err; \ return err; \
data_t ptr = {0}; \ data_t object = {0}; \
err = vm_pop_word(vm, &ptr); \ err = vm_pop_##TYPE(vm, &object); \
if (err) \ if (err) \
return err; \ return err; \
page_t *page = (page_t *)ptr.as_word; \ data_t ptr = {0}; \
if (nth >= (page->available / TYPE_CAP##_SIZE)) \ err = vm_pop_word(vm, &ptr); \
return ERR_OUT_OF_BOUNDS; \ if (err) \
DARR_AT(TYPE##_t, page->data, nth) = object.as_##TYPE; \ return err; \
return ERR_OK; \ 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) VM_MSET_CONSTR(byte, BYTE)
@@ -549,22 +533,26 @@ VM_MSET_CONSTR(short, SHORT)
VM_MSET_CONSTR(hword, HWORD) VM_MSET_CONSTR(hword, HWORD)
VM_MSET_CONSTR(word, WORD) VM_MSET_CONSTR(word, WORD)
#define VM_MGET_CONSTR(TYPE, TYPE_CAP) \ #define VM_MGET_CONSTR(TYPE, TYPE_CAP) \
err_t vm_mget_##TYPE(vm_t *vm, word_t n) \ err_t vm_mget_##TYPE(vm_t *vm) \
{ \ { \
data_t ptr = {0}; \ data_t n = {0}; \
err_t err = vm_pop_word(vm, &ptr); \ err_t err = vm_pop_word(vm, &n); \
if (err) \ if (err) \
return err; \ return (err); \
page_t *page = (page_t *)ptr.as_word; \ data_t ptr = {0}; \
if (n >= (page->available / TYPE_CAP##_SIZE)) \ err = vm_pop_word(vm, &ptr); \
return ERR_OUT_OF_BOUNDS; \ if (err) \
else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \ return err; \
return ERR_STACK_OVERFLOW; \ page_t *page = (page_t *)ptr.as_word; \
memcpy(vm->stack.data + vm->stack.ptr, \ if (n.as_word >= (page->available / TYPE_CAP##_SIZE)) \
page->data + (n * (TYPE_CAP##_SIZE)), TYPE_CAP##_SIZE); \ return ERR_OUT_OF_BOUNDS; \
vm->stack.ptr += TYPE_CAP##_SIZE; \ else if (vm->stack.ptr + TYPE_CAP##_SIZE >= vm->stack.max) \
return ERR_OK; \ 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) VM_MGET_CONSTR(byte, BYTE)
@@ -572,30 +560,6 @@ VM_MGET_CONSTR(short, SHORT)
VM_MGET_CONSTR(hword, HWORD) VM_MGET_CONSTR(hword, HWORD)
VM_MGET_CONSTR(word, WORD) 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) err_t vm_mdelete(vm_t *vm)
{ {
data_t ptr = {0}; data_t ptr = {0};

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2023, 2024 Aryadev Chavali /* Copyright (C) 2023, 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2023-10-15 * Created: 2023-10-15
* Author: Aryadev Chavali * 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_hword(vm_t *, word_t);
err_t vm_dup_word(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); typedef err_t (*word_f)(vm_t *, word_t);
static const word_f WORD_ROUTINES[] = { static const word_f WORD_ROUTINES[] = {
[OP_PUSH_REGISTER_BYTE] = vm_push_byte_register, [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_SHORT] = vm_dup_short,
[OP_DUP_HWORD] = vm_dup_hword, [OP_DUP_HWORD] = vm_dup_hword,
[OP_DUP_WORD] = vm_dup_word, [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 */ /* Operations that take input from the stack */
err_t vm_malloc_stack_byte(vm_t *); err_t vm_malloc_byte(vm_t *);
err_t vm_malloc_stack_short(vm_t *); err_t vm_malloc_short(vm_t *);
err_t vm_malloc_stack_hword(vm_t *); err_t vm_malloc_hword(vm_t *);
err_t vm_malloc_stack_word(vm_t *); err_t vm_malloc_word(vm_t *);
err_t vm_mset_stack_byte(vm_t *); err_t vm_mset_byte(vm_t *);
err_t vm_mset_stack_short(vm_t *); err_t vm_mset_short(vm_t *);
err_t vm_mset_stack_hword(vm_t *); err_t vm_mset_hword(vm_t *);
err_t vm_mset_stack_word(vm_t *); err_t vm_mset_word(vm_t *);
err_t vm_mget_stack_byte(vm_t *); err_t vm_mget_byte(vm_t *);
err_t vm_mget_stack_short(vm_t *); err_t vm_mget_short(vm_t *);
err_t vm_mget_stack_hword(vm_t *); err_t vm_mget_hword(vm_t *);
err_t vm_mget_stack_word(vm_t *); err_t vm_mget_word(vm_t *);
err_t vm_mdelete(vm_t *); err_t vm_mdelete(vm_t *);
err_t vm_msize(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 *); typedef err_t (*stack_f)(vm_t *);
static const stack_f STACK_ROUTINES[] = { static const stack_f STACK_ROUTINES[] = {
[OP_MALLOC_STACK_BYTE] = vm_malloc_stack_byte, [OP_MALLOC_BYTE] = vm_malloc_byte, [OP_MALLOC_SHORT] = vm_malloc_short,
[OP_MALLOC_STACK_SHORT] = vm_malloc_stack_short, [OP_MALLOC_HWORD] = vm_malloc_hword, [OP_MALLOC_WORD] = vm_malloc_word,
[OP_MALLOC_STACK_HWORD] = vm_malloc_stack_hword,
[OP_MALLOC_STACK_WORD] = vm_malloc_stack_word,
[OP_MGET_STACK_BYTE] = vm_mget_stack_byte, [OP_MGET_BYTE] = vm_mget_byte, [OP_MGET_SHORT] = vm_mget_short,
[OP_MGET_STACK_SHORT] = vm_mget_stack_short, [OP_MGET_HWORD] = vm_mget_hword, [OP_MGET_WORD] = vm_mget_word,
[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_MDELETE] = vm_mdelete, [OP_MSET_BYTE] = vm_mset_byte, [OP_MSET_SHORT] = vm_mset_short,
[OP_MSIZE] = vm_msize, [OP_MSET_HWORD] = vm_mset_hword, [OP_MSET_WORD] = vm_mset_word,
[OP_NOT_BYTE] = vm_not_byte, [OP_MDELETE] = vm_mdelete, [OP_MSIZE] = vm_msize,
[OP_NOT_SHORT] = vm_not_short,
[OP_NOT_HWORD] = vm_not_hword,
[OP_NOT_WORD] = vm_not_word,
[OP_OR_BYTE] = vm_or_byte, [OP_NOT_BYTE] = vm_not_byte, [OP_NOT_SHORT] = vm_not_short,
[OP_OR_SHORT] = vm_or_short, [OP_NOT_HWORD] = vm_not_hword, [OP_NOT_WORD] = vm_not_word,
[OP_OR_HWORD] = vm_or_hword,
[OP_OR_WORD] = vm_or_word,
[OP_AND_BYTE] = vm_and_byte, [OP_OR_BYTE] = vm_or_byte, [OP_OR_SHORT] = vm_or_short,
[OP_AND_SHORT] = vm_and_short, [OP_OR_HWORD] = vm_or_hword, [OP_OR_WORD] = vm_or_word,
[OP_AND_HWORD] = vm_and_hword,
[OP_AND_WORD] = vm_and_word,
[OP_XOR_BYTE] = vm_xor_byte, [OP_AND_BYTE] = vm_and_byte, [OP_AND_SHORT] = vm_and_short,
[OP_XOR_SHORT] = vm_xor_short, [OP_AND_HWORD] = vm_and_hword, [OP_AND_WORD] = vm_and_word,
[OP_XOR_HWORD] = vm_xor_hword,
[OP_XOR_WORD] = vm_xor_word,
[OP_EQ_BYTE] = vm_eq_byte, [OP_XOR_BYTE] = vm_xor_byte, [OP_XOR_SHORT] = vm_xor_short,
[OP_EQ_SHORT] = vm_eq_short, [OP_XOR_HWORD] = vm_xor_hword, [OP_XOR_WORD] = vm_xor_word,
[OP_EQ_HWORD] = vm_eq_hword,
[OP_EQ_WORD] = vm_eq_word,
[OP_LT_BYTE] = vm_lt_byte, [OP_EQ_BYTE] = vm_eq_byte, [OP_EQ_SHORT] = vm_eq_short,
[OP_LT_SBYTE] = vm_lt_sbyte, [OP_EQ_HWORD] = vm_eq_hword, [OP_EQ_WORD] = vm_eq_word,
[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_LTE_BYTE] = vm_lte_byte, [OP_LT_BYTE] = vm_lt_byte, [OP_LT_SBYTE] = vm_lt_sbyte,
[OP_LTE_SBYTE] = vm_lte_sbyte, [OP_LT_SHORT] = vm_lt_short, [OP_LT_SSHORT] = vm_lt_sshort,
[OP_LTE_SHORT] = vm_lte_short, [OP_LT_SHWORD] = vm_lt_shword, [OP_LT_HWORD] = vm_lt_hword,
[OP_LTE_SSHORT] = vm_lte_sshort, [OP_LT_SWORD] = vm_lt_sword, [OP_LT_WORD] = vm_lt_word,
[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_GT_BYTE] = vm_gt_byte, [OP_LTE_BYTE] = vm_lte_byte, [OP_LTE_SBYTE] = vm_lte_sbyte,
[OP_GT_SBYTE] = vm_gt_sbyte, [OP_LTE_SHORT] = vm_lte_short, [OP_LTE_SSHORT] = vm_lte_sshort,
[OP_GT_SHORT] = vm_gt_short, [OP_LTE_SHWORD] = vm_lte_shword, [OP_LTE_HWORD] = vm_lte_hword,
[OP_GT_SSHORT] = vm_gt_sshort, [OP_LTE_SWORD] = vm_lte_sword, [OP_LTE_WORD] = vm_lte_word,
[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_GTE_BYTE] = vm_gte_byte, [OP_GT_BYTE] = vm_gt_byte, [OP_GT_SBYTE] = vm_gt_sbyte,
[OP_GTE_SBYTE] = vm_gte_sbyte, [OP_GT_SHORT] = vm_gt_short, [OP_GT_SSHORT] = vm_gt_sshort,
[OP_GTE_SHORT] = vm_gte_short, [OP_GT_SHWORD] = vm_gt_shword, [OP_GT_HWORD] = vm_gt_hword,
[OP_GTE_SSHORT] = vm_gte_sshort, [OP_GT_SWORD] = vm_gt_sword, [OP_GT_WORD] = vm_gt_word,
[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_PLUS_BYTE] = vm_plus_byte, [OP_GTE_BYTE] = vm_gte_byte, [OP_GTE_SBYTE] = vm_gte_sbyte,
[OP_PLUS_SHORT] = vm_plus_short, [OP_GTE_SHORT] = vm_gte_short, [OP_GTE_SSHORT] = vm_gte_sshort,
[OP_PLUS_HWORD] = vm_plus_hword, [OP_GTE_SHWORD] = vm_gte_shword, [OP_GTE_HWORD] = vm_gte_hword,
[OP_PLUS_WORD] = vm_plus_word, [OP_GTE_SWORD] = vm_gte_sword, [OP_GTE_WORD] = vm_gte_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_PLUS_BYTE] = vm_plus_byte, [OP_PLUS_SHORT] = vm_plus_short,
[OP_MULT_SHORT] = vm_mult_short, [OP_PLUS_HWORD] = vm_plus_hword, [OP_PLUS_WORD] = vm_plus_word,
[OP_MULT_HWORD] = vm_mult_hword, [OP_SUB_BYTE] = vm_sub_byte, [OP_SUB_SHORT] = vm_sub_short,
[OP_MULT_WORD] = vm_mult_word, [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 #endif

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-25 * Created: 2024-04-25
* Author: Aryadev Chavali * Author: Aryadev Chavali
@@ -53,18 +52,16 @@ void vm_stop(vm_t *vm)
{ {
#if VERBOSE >= 1 #if VERBOSE >= 1
bool leaks = false; 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) if (vm->call_stack.ptr > 0)
{ {
leaks = true; leaks = true;
printf("\t[" TERM_RED "DATA" TERM_RESET "]: Call stack at %lu\n\t[" TERM_RED FAIL("vm_stop", "Call stack at %lu\n", vm->call_stack.ptr);
"DATA" TERM_RESET "]\n\t[" TERM_RED "DATA" TERM_RESET "]: Call " FAIL("vm_stop", "Call stack trace:%s", "");
"stack trace:",
vm->call_stack.ptr);
for (size_t i = vm->call_stack.ptr; i > 0; --i) for (size_t i = vm->call_stack.ptr; i > 0; --i)
{ {
word_t w = vm->call_stack.address_pointers[i - 1]; 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) if (i != 1)
printf(", "); printf(", ");
printf("\n"); printf("\n");
@@ -81,22 +78,20 @@ void vm_stop(vm_t *vm)
capacities[i] = cur->available; capacities[i] = cur->available;
total_capacity += capacities[i]; total_capacity += capacities[i];
} }
printf("\t[" TERM_RED "DATA" TERM_RESET FAIL("vm_stop", "Heap: %luB (over %lu %s) not reclaimed\n", total_capacity,
"]: Heap: %luB (over %lu %s) not reclaimed\n", size_pages, size_pages == 1 ? "page" : "pages");
total_capacity, size_pages, size_pages == 1 ? "page" : "pages");
for (size_t i = 0; i < size_pages; i++) 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) if (vm->stack.ptr > 0)
{ {
leaks = true; leaks = true;
printf("\t[" TERM_RED "DATA" TERM_RESET "]: Stack: %luB not reclaimed\n", FAIL("vm_stop", "Stack: %luB not reclaimed\n", vm->stack.ptr);
vm->stack.ptr);
} }
if (leaks) if (leaks)
printf("[" TERM_RED "DATA" TERM_RESET "]: Leaks found\n"); FAIL("vm_stop", "Leaks found\n%s", "");
else else
printf("[" TERM_GREEN "DATA" TERM_RESET "]: No leaks found\n"); SUCCESS("vm_stop", "No leaks found\n%s", "");
#endif #endif
vm->registers = (struct Registers){0}; vm->registers = (struct Registers){0};

View File

@@ -1,13 +1,12 @@
/* Copyright (C) 2024 Aryadev Chavali /* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but * This program is distributed in the hope that it will be useful, but WITHOUT
* WITHOUT ANY WARRANTY; without even the implied warranty of * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* General Public License for more details. * more details.
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License Version 2
* along with this program. If not, see * along with this program. If not, see <https://www.gnu.org/licenses/>.
* <https://www.gnu.org/licenses/>.
* Created: 2024-04-25 * Created: 2024-04-25
* Author: Aryadev Chavali * Author: Aryadev Chavali