· Blog · 24 min read
An excellent virtual machine memory architecture - AQ
The memory architecture of a virtual machine directly affects the performance and occupancy of the virtual machine. Designing an excellent architecture can effectively improve performance and efficiency. This article will introduce the memory architecture used by the AQ virtual machine.
![The memory architecture of a virtual machine directly affects the performance and occupancy of the virtual machine. Designing an excellent architecture can effectively improve performance and efficiency. This article will introduce the memory architecture used by the AQ virtual machine.](https://www.axa6.com/aq.png)
Introduction
The memory architecture of the virtual machine directly affects the performance and occupancy of the virtual machine. Designing an excellent architecture can effectively improve performance and efficiency.
This article will introduce the memory architecture used by the AQ virtual machine
and the detailed standards of the AQ virtual machine
memory.
By optimizing the memory architecture of the virtual machine, it helps to improve the operating efficiency and reduce the occupancy of the virtual machine. If possible, the two should be balanced as much as possible to make the virtual machine reach the best state.
In some cases, different development should be done based on the special needs of the virtual machine.
For example: In memory-constrained situations such assingle-chip microcomputers
, it is necessary to reduce the usage as much as possible.
In performance-sensitive situations such asparallel computing
, it is necessary to focus on performance optimization.
Design ideas
Memory Architecture
Basic memory architecture
AQ
adopts the basic memory architecture of register
, but it is different from the standard register
architecture and has made some improvements and optimizations to the register
architecture.
The
registers
here are not theregisters
in theCPU
, but thevirtual registers
simulated in thememory
.
Reasons for choosing registers
Compared with the stack architecture adopted by mainstream language virtual machines such as JAVA
and Python
, AQ
decided to adopt the register
architecture because of performance optimization and easy understanding of bytecode
.
Although the stack
architecture is generally considered easier to port and write, there will be some performance loss in practice. Multiple accesses to memory
will slow down its speed, which is inevitable and difficult to optimize thoroughly. Therefore, in order to solve the performance loss here, AQ
adopts the register
architecture. At the same time, from the perspective of bytecode
, the bytecode of the register
architecture is easier to understand, and its instructions are similar to the parameter
method of function
, rather than directly facing the numerous operations of the stack
.
Differences in register
architecture
Standard register architecture
In the standard register architecture, registers
contain:
Data Type
- the type of data the register will store (int, float, double, etc.)data
- the value of the data that the register will store- (Optional) Tag - The tag of the data that the register will store (e.g. variable, function, class, etc.)
- (Optional) Reference - A reference to the data the register will store (such as the address of an object, etc.)
Although the memory architecture of the virtual machine
of different languages may be different, this information is roughly stored.
This architecture was used during the development of AQ
, but after testing, it was found to have a large memory footprint.
The following is the register.h
code used by AQ
:
// Copyright 2024 AQ authors, All Rights Reserved.
// This program is licensed under the AQ License. You can find the AQ license in
// the root directory.
#ifndef AQ_AQVM_MEMORY_REGISTER_H_
#define AQ_AQVM_MEMORY_REGISTER_H_
#include <stdbool.h>
enum AqvmMemoryRegister_ValueType {
// TODO(Register): Waiting for the improvement of the register.
AqvmMemoryRegisterValueType_INT,
AqvmMemoryRegisterValueType_CONSTINT,
AqvmMemoryRegisterValueType_FLOAT,
AqvmMemoryRegisterValueType_CONSTFLOAT,
AqvmMemoryRegisterValueType_DOUBLE,
AqvmMemoryRegisterValueType_CONSTDOUBLE,
AqvmMemoryRegisterValueType_LONG,
AqvmMemoryRegisterValueType_CONSTLONG,
AqvmMemoryRegisterValueType_CHARACTER,
AqvmMemoryRegisterValueType_CONSTCHARACTER,
AqvmMemoryRegisterValueType_BOOLEAN,
AqvmMemoryRegisterValueType_CONSTBOOLEAN
};
union AqvmMemoryRegister_Value {
// TODO(Register): Waiting for the improvement of the register.
int int_value;
const int const_int_value;
float float_value;
const float const_float_value;
double double_value;
const double const_double_value;
long long_value;
const long const_long_value;
char character_value;
const char const_character_value;
bool boolean_value;
const bool const_boolean_value;
};
struct AqvmMemoryRegister_Register {
enum AqvmMemoryRegister_ValueType type;
union AqvmMemoryRegister_Value value;
};
#endif
As can be seen from the above code, even if only the necessary content is retained, since the AqvmMemoryRegister_ValueType
of the enum
type occupies 4
bytes, the AqvmMemoryRegister_Value
of the union
type occupies 8
bytes, the struct
type itself will occupy 12
bytes of memory.
At the same time, due to the optimization of the C
compiler, the type
of the enum
type in the struct
type AqvmMemoryRegister_Register
is memory aligned
with the value
of the union
type, so 4
bytes of padding memory
are added. Make the struct
type AqvmMemoryRegister_Register
occupy 16
bytes.
If non-8-byte types such as int are used, 4 bytes of padding memory will be wasted, resulting in memory loss. Therefore, 4-8 bytes of memory will be wasted in all registers.
Register architecture of AQ
In order to solve the occupancy problem of the traditional register
architecture, AQ
combines the local variable table
characteristics of the stack frame
of the JVM
and optimizes the memory architecture
, which significantly reduces the occupancy problem.
Here are three options:
// plan 1:
struct AqvmMemoryRegister_Register{
uint8_t type;
void* value_ptr;
};
void* value;
AqvmMemoryRegister_Register array[];
// plan 2:
void* value;
// value to the memory address of index 0 is int, the index 0 to the index 1 is
// float, etc.
size_t type[];
// plan 3:
struct AqvmMemoryRegister_Register {
uint32_t* value;
size_t size;
};
Since pointers take up 4 to 8 bytes, the data itself takes up 1 to 8 bytes, plus the type takes up 1 byte, plan 1 takes up 6 to 17 bytes. There may also be memory alignment issues, so plan 1 will also cause a huge memory loss.
In fact, when it is required to retain memory type information, the highest memory utilization is plan 2
, but plan 2
cannot preserve the coherence
of different types of data in the same data structure (such as: structure), which may make some pointer operations invalid. Therefore, for memory safety
, do not use plan 2
.
In some cases (the virtual machine instruction set includes types), plan 3
can also meet the needs of memory storage, but due to the need for a reduced instruction set, type information is not included in the instructions, so it cannot meet the needs of virtual machine operation.
Therefore, we adopt the following design to ensure the utilization of memory and greatly improve the memory usage problem.
AQ
’s memory
directly uses void*
pointer to store data, size_t
to store the occupied memory size, and uint8_t
array to store the type. Since uint8_t
occupies 8
bits, in order to reduce the occupation, each byte uses 4
bits to store the type. Therefore, a uint8_t
variable can store 2
types. The first 4
bits of each uint8_t
variable are used for the type of the even
bytes, and the last 4
bits are used for the type of the odd
bytes.
// The struct stores information about the memory.
// |type| is a pointer to an array that stores the type of each byte in the
// memory. Each byte uses 4 bits to store the type. So a uint8_t variable can
// store 2 types. Each uint8_t variable's first 4 bits are used for the even
// byte's type and the next 4 bits are used for the odd byte's type. The type
// list is in types.h.
// |data| is a pointer of type void* to the memory that stores the data.
// |size| is the size of the memory.
// NOTICE: The struct AqvmMemory_Memory only stores information of the memory.
// The memory is allocated by the bytecode function when storing the bytecode.
// The memory of |memory| and |type| is part of the bytecode memory.
struct AqvmMemory_Memory {
uint8_t* type;
void* data;
size_t size;
};
Due to memory
reasons, access to type
needs to be used precisely. uint8_t
type requires 8
bits, but this exceeds the storage requirement of the type, so 4
bits can both meet the storage requirement of the type and reduce memory usage. However, special functions are required to maintain access to type
.
// Sets the type of the data at |index| bytes in |memory| to |type|. |type|
// should be less than 4 bits.
// Returns 0 if successful. Returns -1 if the memory pointer is NULL. Returns -2
// if the type pointer is NULL. Returns -3 if the index is out of range. Returns
// -4 if the type is out of range.
int AqvmMemory_SetType(const struct AqvmMemory_Memory* memory, size_t index,
uint8_t type) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_NullMemoryPointer\"",
"\"The memory pointer is NULL.\"", NULL);
return -1;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_NullTypePointer\"",
"\"The type pointer is NULL.\"", NULL);
return -2;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_OutOfMemoryRange\"",
"\"The index is out of memory range.\"", NULL);
return -3;
}
if (type > 0x0F) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_OutOfTypeRange\"",
"\"The type is out of range.\"", NULL);
return -4;
}
// Sets the type of the data at |index| bytes in memory.
// Since Aqvm stores type data occupying 4 bits and uint8_t occupying 8 bits,
// each uint8_t type location stores two type data. The storage locations
// (high 4 bits, low 4 bits) are set according to the parity of |index|. Even
// numbers are stored in the high bits of (|index| / 2) and odd numbers are
// stored in the low bits of (|index| / 2).
if (index % 2 != 0) {
memory->type[index / 2] = (memory->type[index / 2] & 0xF0) | type;
} else {
memory->type[index / 2] = (memory->type[index / 2] & 0x0F) | (type << 4);
}
return 0;
}
// Gets the type of the data at |index| bytes in |memory|.
// Returns the type that is less than 4 bits (0X0F) if successful. Returns 0x11
// if the memory pointer is NULL. Returns 0x12 if the type pointer is NULL.
// Returns 0x13 if the index is out of memory range.
uint8_t AqvmMemory_GetType(const struct AqvmMemory_Memory* memory,
size_t index) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_NullMemoryPointer",
"The memory pointer is NULL.", NULL);
return 0x11;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_NullTypePointer",
"The type pointer is NULL.", NULL);
return 0x12;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_OutOfMemoryRange",
"The index is out of memory range.", NULL);
return 0x13;
}
// Gets the type of the data at |index| bytes in memory.
// Since Aqvm stores type data occupying 4 bits and uint8_t occupying 8 bits,
// each uint8_t type location stores two type data. The storage locations
// (high 4 bits, low 4 bits) are set according to the parity of |index|. Even
// numbers are stored in the high bits of (|index| / 2) and odd numbers are
// stored in the low bits of (|index| / 2).
if (index % 2 != 0) {
return memory->type[index / 2] & 0x0F;
} else {
return (memory->type[index / 2] & 0xF0) >> 4;
}
}
However, this design has high requirements for data storage, because the length of the data is not fixed, so a special function is needed to operate with memory.
// Writes the data that |data_ptr| points to of size |size| to the data of at
// |index| bytes in |memory|.
// Returns 0 if successful. Returns -1 if the memory pointer is NULL. Returns -2
// if the type pointer is NULL. Returns -3 if the index is out of range. Returns
// -4 if the data pointer is NULL.
int AqvmMemory_WriteData(struct AqvmMemory_Memory* memory, size_t index,
void* data_ptr, size_t size) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullMemoryPointer\"",
"\"The memory pointer is NULL.\"", NULL);
return -1;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullTypePointer\"",
"\"The type pointer is NULL.\"", NULL);
return -2;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_OutOfMemoryRange\"",
"\"The index is out of memory range.\"", NULL);
return -3;
}
if (data_ptr == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullDataPointer\"",
"\"The data pointer is NULL.\"", NULL);
return -4;
}
// Since void* does not have a specific size, pointer moves need to be
// converted before moving.
memcpy((void*)((uintptr_t)memory->data + index), data_ptr, size);
return 0;
}
In addition to reducing the use of memory
, it is equally important to avoid the secondary occupation of memory
. Therefore, we reuse the memory
of bytecode
, store the memory data and types in the memory part of bytecode
, and use the memory
pre-allocated in the bytecode
file (the bytecode file contains the memory
data and types) to achieve efficient use of memory
.
Because if the two parts are stored separately, there will be two parts of duplicate memory data and types, one in the memory
part, and the other, the bytecode
part will not be used, so we adopted a reuse method to reduce the memory waste caused by memory data and types.
However, this requires special function implementation, and it should be noted that the allocation and release of memory data and types are managed by the relevant functions of the bytecode.
// Creates and initializes the struct AqvmMemory_Memory with |data|, |type|, and
// |size|. The function will allocate a struct AqvmMemory_Memory and copy
// |data|, |type|, and |size| into the struct. Returns a pointer to the struct
// if successful. Returns NULL if creation fails.
struct AqvmMemory_Memory* AqvmMemory_InitializeMemory(void* data, void* type,
size_t size) {
AqvmBaseLogging_OutputLog("\"INFO\"", "\"AqvmMemory_InitializeMemory_Start\"",
"\"Memory initialization started.\"", NULL);
struct AqvmMemory_Memory* memory_ptr =
(struct AqvmMemory_Memory*)malloc(sizeof(struct AqvmMemory_Memory));
if (memory_ptr == NULL) {
AqvmBaseLogging_OutputLog(
"\"ERROR\"", "\"AqvmMemory_InitializeMemory_MemoryAllocationFailure\"",
"\"Failed to allocate memory.\"", NULL);
return NULL;
}
memory_ptr->data = data;
memory_ptr->type = type;
memory_ptr->size = size;
return memory_ptr;
}
// Free the memory of the |memory_ptr|. No return.
// NOTICE: The function only frees the memory of the struct. The memory pointed
// to by pointers to data and type in struct is not freed. This memory is
// managed by bytecode related functions.
void AqvmMemory_FreeMemory(struct AqvmMemory_Memory* memory_ptr) {
AqvmBaseLogging_OutputLog("\"INFO\"", "\"AqvmMemory_FreeMemory_Start\"",
"\"Memory deallocation started.\"", NULL);
free(memory_ptr);
}
In addition, since some systems have different definitions of types from the AQ standard, related functions are designed to ensure that the virtual machine complies with the standard. If the system is different from the standard, special designs should be made for these systems.
// Checks the memory conditions in the system.
// Returns the number of warnings.
int AqvmMemory_CheckMemoryConditions() {
int warning_count = 0;
if (sizeof(aqbyte) != 1) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_ByteLengthWarning\"",
"\"The length requirement for the byte type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqint) != 4) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_IntLengthWarning\"",
"\"The length requirement for the int type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqlong) != 8) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_LongLengthWarning\"",
"\"The length requirement for the long type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqfloat) != 4) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"",
"\"AqvmMemory_CheckMemoryConditions_FloatLengthWarning\"",
"\"The length requirement for the float type does not conform to the "
"type definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqdouble) != 8) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"",
"\"AqvmMemory_CheckMemoryConditions_DoubleLengthWarning\"",
"\"The length requirement for the double type does not conform to the "
"type definition.\"",
NULL);
++warning_count;
}
if (warning_count == 0) {
AqvmBaseLogging_OutputLog(
"\"INFO\"", "\"AqvmMemory_CheckMemoryConditions_CheckNormal\"",
"\"No memory conditions warning.\"", NULL);
}
return warning_count;
}
Detailed standards:
Directory Structure
The code of the memory
part is located in /aqvm/memory
. It contains multiple code files.
CMakeLists.txt
- CMake build file in this directorymemory.h
- memory data structures and related functionsmemory.c
- implementation of memory related functionstypes.h
- Memory type definitions
memory.h
AqvmMemory_Memory
This structure stores information about memory.
|type| is a pointer to an array that stores the type of each byte in memory. Each byte uses 4 bits to store the type. Therefore, a uint8_t variable can store 2 types. The first 4 bits of each uint8_t variable are used for the type of even bytes, and the last 4 bits are used for the type of odd bytes. The types are listed in types.h.
|data| is a pointer of type void* pointing to the memory where the data is stored.
|size| is the size of the memory.
Note: The structure AqvmMemory_Memory only stores information about memory. Memory is allocated by the bytecode function when storing bytecode. The memory of |memory| and |type| is part of the bytecode memory.
struct AqvmMemory_Memory {
uint8_t* type;
void* data;
size_t size;
};
AqvmMemory_CheckMemoryConditions
Check memory conditions on the system.
Returns the number of warnings.
int AqvmMemory_CheckMemoryConditions() {
int warning_count = 0;
if (sizeof(aqbyte) != 1) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_ByteLengthWarning\"",
"\"The length requirement for the byte type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqint) != 4) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_IntLengthWarning\"",
"\"The length requirement for the int type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqlong) != 8) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_LongLengthWarning\"",
"\"The length requirement for the long type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqfloat) != 4) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"",
"\"AqvmMemory_CheckMemoryConditions_FloatLengthWarning\"",
"\"The length requirement for the float type does not conform to the "
"type definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqdouble) != 8) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"",
"\"AqvmMemory_CheckMemoryConditions_DoubleLengthWarning\"",
"\"The length requirement for the double type does not conform to the "
"type definition.\"",
NULL);
++warning_count;
}
if (warning_count == 0) {
AqvmBaseLogging_OutputLog(
"\"INFO\"", "\"AqvmMemory_CheckMemoryConditions_CheckNormal\"",
"\"No memory conditions warning.\"", NULL);
}
return warning_count;
}
AqvmMemory_InitializeMemory
Create a structure AqvmMemory_Memory containing |data|, |type| and |size|.
This function will allocate an AqvmMemory_Memory structure and copy |data|, |type| and |size| into the structure. Returns a pointer to the structure. Returns NULL if creation fails.
struct AqvmMemory_Memory* AqvmMemory_InitializeMemory(void* data, void* type,
size_t size) {
AqvmBaseLogging_OutputLog("\"INFO\"", "\"AqvmMemory_InitializeMemory_Start\"",
"\"Memory initialization started.\"", NULL);
struct AqvmMemory_Memory* memory_ptr =
(struct AqvmMemory_Memory*)malloc(sizeof(struct AqvmMemory_Memory));
if (memory_ptr == NULL) {
AqvmBaseLogging_OutputLog(
"\"ERROR\"", "\"AqvmMemory_InitializeMemory_MemoryAllocationFailure\"",
"\"Failed to allocate memory.\"", NULL);
return NULL;
}
memory_ptr->data = data;
memory_ptr->type = type;
memory_ptr->size = size;
return memory_ptr;
}
AqvmMemory_FreeMemory
Releases the memory of |memory_ptr|. No return value.
Note: This function only releases the memory of the structure. The memory pointed to by the pointers to the data and types in the structure will not be released. These memories are managed by bytecode related functions.
void AqvmMemory_FreeMemory(struct AqvmMemory_Memory* memory_ptr) {
AqvmBaseLogging_OutputLog("\"INFO\"", "\"AqvmMemory_FreeMemory_Start\"",
"\"Memory deallocation started.\"", NULL);
free(memory_ptr);
}
AqvmMemory_SetType
Sets the data type at byte |index| in |memory| to |type|. |type| should be less than 4 bits.
Returns 0 on success. Returns -1 if the memory pointer is NULL. Returns -2 if the index pointer is NULL. Returns -3 if the index is out of range. Returns -4 if the type is out of range.
int AqvmMemory_SetType(const struct AqvmMemory_Memory* memory, size_t index,
uint8_t type) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_NullMemoryPointer\"",
"\"The memory pointer is NULL.\"", NULL);
return -1;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_NullTypePointer\"",
"\"The type pointer is NULL.\"", NULL);
return -2;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_OutOfMemoryRange\"",
"\"The index is out of memory range.\"", NULL);
return -3;
}
if (type > 0x0F) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_OutOfTypeRange\"",
"\"The type is out of range.\"", NULL);
return -4;
}
// Sets the type of the data at |index| bytes in memory.
// Since Aqvm stores type data occupying 4 bits and uint8_t occupying 8 bits,
// each uint8_t type location stores two type data. The storage locations
// (high 4 bits, low 4 bits) are set according to the parity of |index|. Even
// numbers are stored in the high bits of (|index| / 2) and odd numbers are
// stored in the low bits of (|index| / 2).
if (index % 2 != 0) {
memory->type[index / 2] = (memory->type[index / 2] & 0xF0) | type;
} else {
memory->type[index / 2] = (memory->type[index / 2] & 0x0F) | (type << 4);
}
return 0;
}
AqvmMemory_GetType
Get the data type of the byte at |index| in |memory|.
Returns a type less than 4 bits (0x0F) on success. If the memory pointer is NULL, returns 0x11. If the index pointer is NULL, returns 0x12. If the index is out of memory range, returns 0x13.
uint8_t AqvmMemory_GetType(const struct AqvmMemory_Memory* memory,
size_t index) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_NullMemoryPointer",
"The memory pointer is NULL.", NULL);
return 0x11;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_NullTypePointer",
"The type pointer is NULL.", NULL);
return 0x12;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_OutOfMemoryRange",
"The index is out of memory range.", NULL);
return 0x13;
}
// Gets the type of the data at |index| bytes in memory.
// Since Aqvm stores type data occupying 4 bits and uint8_t occupying 8 bits,
// each uint8_t type location stores two type data. The storage locations
// (high 4 bits, low 4 bits) are set according to the parity of |index|. Even
// numbers are stored in the high bits of (|index| / 2) and odd numbers are
// stored in the low bits of (|index| / 2).
if (index % 2 != 0) {
return memory->type[index / 2] & 0x0F;
} else {
return (memory->type[index / 2] & 0xF0) >> 4;
}
}
AqvmMemory_WriteData
Write the data of size |size| pointed to by |data_ptr| to the data at |index| bytes in |memory|.
Returns 0 on success. If the memory pointer is NULL, returns -1. If the index pointer is NULL, returns -2. If the index is out of memory, returns -3. If the data pointer is NULL, returns -4.
int AqvmMemory_WriteData(struct AqvmMemory_Memory* memory, size_t index,
void* data_ptr, size_t size) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullMemoryPointer\"",
"\"The memory pointer is NULL.\"", NULL);
return -1;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullTypePointer\"",
"\"The type pointer is NULL.\"", NULL);
return -2;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_OutOfMemoryRange\"",
"\"The index is out of memory range.\"", NULL);
return -3;
}
if (data_ptr == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullDataPointer\"",
"\"The data pointer is NULL.\"", NULL);
return -4;
}
// Since void* does not have a specific size, pointer moves need to be
// converted before moving.
memcpy((void*)((uintptr_t)memory->data + index), data_ptr, size);
return 0;
}
memory.h
complete code:
// Copyright 2024 AQ author, All Rights Reserved.
// This program is licensed under the AQ License. You can find the AQ license in
// the root directory.
#ifndef AQ_AQVM_MEMORY_MEMORY_H_
#define AQ_AQVM_MEMORY_MEMORY_H_
#include <stddef.h>
#include <stdint.h>
#include "aqvm/memory/types.h"
// The struct stores information about the memory.
// |type| is a pointer to an array that stores the type of each byte in the
// memory. Each byte uses 4 bits to store the type. So a uint8_t variable can
// store 2 types. Each uint8_t variable's first 4 bits are used for the even
// byte's type and the next 4 bits are used for the odd byte's type. The type
// list is in types.h.
// |data| is a pointer of type void* to the memory that stores the data.
// |size| is the size of the memory.
// NOTICE: The struct AqvmMemory_Memory only stores information of the memory.
// The memory is allocated by the bytecode function when storing the bytecode.
// The memory of |memory| and |type| is part of the bytecode memory.
struct AqvmMemory_Memory {
uint8_t* type;
void* data;
size_t size;
};
// Checks the memory conditions in the system.
// Returns the number of warnings.
int AqvmMemory_CheckMemoryConditions();
// Creates and initializes the struct AqvmMemory_Memory with |data|, |type|, and
// |size|. The function will allocate a struct AqvmMemory_Memory and copy
// |data|, |type|, and |size| into the struct. Returns a pointer to the struct
// if successful. Returns NULL if creation fails.
struct AqvmMemory_Memory* AqvmMemory_InitializeMemory(void* data, void* type,
size_t size);
// Free the memory of the |memory_ptr|. No return.
// NOTICE: The function only frees the memory of the struct. The memory pointed
// to by pointers to data and type in struct is not freed. This memory is
// managed by bytecode related functions.
void AqvmMemory_FreeMemory(struct AqvmMemory_Memory* memory_ptr);
// Sets the type of the data at |index| bytes in |memory| to |type|. |type|
// should be less than 4 bits.
// Returns 0 if successful. Returns -1 if the memory pointer is NULL. Returns -2
// if the type pointer is NULL. Returns -3 if the index is out of range. Returns
// -4 if the type is out of range.
int AqvmMemory_SetType(const struct AqvmMemory_Memory* memory, size_t index,
uint8_t type);
// Gets the type of the data at |index| bytes in |memory|.
// Returns the type that is less than 4 bits (0X0F) if successful. Returns 0x11
// if the memory pointer is NULL. Returns 0x12 if the type pointer is NULL.
// Returns 0x13 if the index is out of memory range.
uint8_t AqvmMemory_GetType(const struct AqvmMemory_Memory* memory,
size_t index);
// Writes the data that |data_ptr| points to of size |size| to the data of at
// |index| bytes in |memory|.
// Returns 0 if successful. Returns -1 if the memory pointer is NULL. Returns -2
// if the type pointer is NULL. Returns -3 if the index is out of range. Returns
// -4 if the data pointer is NULL.
int AqvmMemory_WriteData(struct AqvmMemory_Memory* memory, size_t index,
void* data_ptr, size_t size);
#endif
memory.c
memory.c
complete code:
// Copyright 2024 AQ author, All Rights Reserved.
// This program is licensed under the AQ License. You can find the AQ license in
// the root directory.
#include "aqvm/memory/memory.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "aqvm/memory/types.h"
#include "aqvm/base/logging/logging.h"
int AqvmMemory_CheckMemoryConditions() {
int warning_count = 0;
if (sizeof(aqbyte) != 1) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_ByteLengthWarning\"",
"\"The length requirement for the byte type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqint) != 4) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_IntLengthWarning\"",
"\"The length requirement for the int type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqlong) != 8) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"", "\"AqvmMemory_CheckMemoryConditions_LongLengthWarning\"",
"\"The length requirement for the long type does not conform to the "
"type "
"definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqfloat) != 4) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"",
"\"AqvmMemory_CheckMemoryConditions_FloatLengthWarning\"",
"\"The length requirement for the float type does not conform to the "
"type definition.\"",
NULL);
++warning_count;
}
if (sizeof(aqdouble) != 8) {
AqvmBaseLogging_OutputLog(
"\"WARNING\"",
"\"AqvmMemory_CheckMemoryConditions_DoubleLengthWarning\"",
"\"The length requirement for the double type does not conform to the "
"type definition.\"",
NULL);
++warning_count;
}
if (warning_count == 0) {
AqvmBaseLogging_OutputLog(
"\"INFO\"", "\"AqvmMemory_CheckMemoryConditions_CheckNormal\"",
"\"No memory conditions warning.\"", NULL);
}
return warning_count;
}
struct AqvmMemory_Memory* AqvmMemory_InitializeMemory(void* data, void* type,
size_t size) {
AqvmBaseLogging_OutputLog("\"INFO\"", "\"AqvmMemory_InitializeMemory_Start\"",
"\"Memory initialization started.\"", NULL);
struct AqvmMemory_Memory* memory_ptr =
(struct AqvmMemory_Memory*)malloc(sizeof(struct AqvmMemory_Memory));
if (memory_ptr == NULL) {
AqvmBaseLogging_OutputLog(
"\"ERROR\"", "\"AqvmMemory_InitializeMemory_MemoryAllocationFailure\"",
"\"Failed to allocate memory.\"", NULL);
return NULL;
}
memory_ptr->data = data;
memory_ptr->type = type;
memory_ptr->size = size;
return memory_ptr;
}
void AqvmMemory_FreeMemory(struct AqvmMemory_Memory* memory_ptr) {
AqvmBaseLogging_OutputLog("\"INFO\"", "\"AqvmMemory_FreeMemory_Start\"",
"\"Memory deallocation started.\"", NULL);
free(memory_ptr);
}
int AqvmMemory_SetType(const struct AqvmMemory_Memory* memory, size_t index,
uint8_t type) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_NullMemoryPointer\"",
"\"The memory pointer is NULL.\"", NULL);
return -1;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_NullTypePointer\"",
"\"The type pointer is NULL.\"", NULL);
return -2;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_OutOfMemoryRange\"",
"\"The index is out of memory range.\"", NULL);
return -3;
}
if (type > 0x0F) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_SetType_OutOfTypeRange\"",
"\"The type is out of range.\"", NULL);
return -4;
}
// Sets the type of the data at |index| bytes in memory.
// Since Aqvm stores type data occupying 4 bits and uint8_t occupying 8 bits,
// each uint8_t type location stores two type data. The storage locations
// (high 4 bits, low 4 bits) are set according to the parity of |index|. Even
// numbers are stored in the high bits of (|index| / 2) and odd numbers are
// stored in the low bits of (|index| / 2).
if (index % 2 != 0) {
memory->type[index / 2] = (memory->type[index / 2] & 0xF0) | type;
} else {
memory->type[index / 2] = (memory->type[index / 2] & 0x0F) | (type << 4);
}
return 0;
}
uint8_t AqvmMemory_GetType(const struct AqvmMemory_Memory* memory,
size_t index) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_NullMemoryPointer",
"The memory pointer is NULL.", NULL);
return 0x11;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_NullTypePointer",
"The type pointer is NULL.", NULL);
return 0x12;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("ERROR", "AqvmMemory_GetType_OutOfMemoryRange",
"The index is out of memory range.", NULL);
return 0x13;
}
// Gets the type of the data at |index| bytes in memory.
// Since Aqvm stores type data occupying 4 bits and uint8_t occupying 8 bits,
// each uint8_t type location stores two type data. The storage locations
// (high 4 bits, low 4 bits) are set according to the parity of |index|. Even
// numbers are stored in the high bits of (|index| / 2) and odd numbers are
// stored in the low bits of (|index| / 2).
if (index % 2 != 0) {
return memory->type[index / 2] & 0x0F;
} else {
return (memory->type[index / 2] & 0xF0) >> 4;
}
}
int AqvmMemory_WriteData(struct AqvmMemory_Memory* memory, size_t index,
void* data_ptr, size_t size) {
if (memory == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullMemoryPointer\"",
"\"The memory pointer is NULL.\"", NULL);
return -1;
}
if (memory->type == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullTypePointer\"",
"\"The type pointer is NULL.\"", NULL);
return -2;
}
if (index > memory->size) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_OutOfMemoryRange\"",
"\"The index is out of memory range.\"", NULL);
return -3;
}
if (data_ptr == NULL) {
AqvmBaseLogging_OutputLog("\"ERROR\"",
"\"AqvmMemory_WriteData_NullDataPointer\"",
"\"The data pointer is NULL.\"", NULL);
return -4;
}
// Since void* does not have a specific size, pointer moves need to be
// converted before moving.
memcpy((void*)((uintptr_t)memory->data + index), data_ptr, size);
return 0;
}
The cooperation of these codes together constitutes the complete memory architecture of Aqvm, which effectively alleviates memory pressure and improves the operating efficiency of Aqvm.
We are working harder to develop the
AQ virtual machine
. If you want to learn more or participate in the development, please follow our official website: https://www.axa6.com and Github: https://github.com/aq-org/AQ.
This article is released under AQ License: https://github.com/aq-org/AQ/blob/main/LICENSE. If necessary, please adapt or reprint it according to AQ License.