Michael Heilmann's Automatic Resource Management System Mark 1
This is the documentation for Michael Heilmann's Automatic Resource Management System Mark 1, henceforth ARMS1. ARMS1 is a precise stop the world garbage collector to be used for programs written in C. ARMS1 is available at michaelheilmann.com/repository/arms1.
Files
You can find the sources of ARMS1 in my GitHub repository https://github.com/michaelheilmann/michaelheilmann.com. The subdirectory of Arms1 in the repository is here https://github.com/michaelheilmann/michaelheilmann.com/tree/main/repository/Arms1.
- The (sources of the) library resides in the directory https://github.com/michaelheilmann/michaelheilmann.com/tree/main/repository/Arms1/Sources
- The (sources of) demos reside in the directory https://github.com/michaelheilmann/michaelheilmann.com/tree/main/repository/Arms1/Demos
- The (sources of this very documentation you are reading) documentation reside in the directory https://github.com/michaelheilmann/michaelheilmann.com/tree/main/repository/Arms/Documentation
You can compile and run these under various platforms (including but not restricted to Windows, Linux, and many more), however, we currently only officially support Windows. To build and run all these, simply checkout the repository to the and outside of the repository directory create a build folder. Invoke from the the build folder
cmake
How a ARMS1 works
ARMS1 allows for the creation of objects and maintains a set of these created objects called the universe \(U\). When an object \(x\) is created, a type \(a\) is associated with that object. That type \(a\) provides means to ARMS1 to determine the objects reachable from that object \(x\) and to properly destroy the object \(x\). To determine which objects are to be retained and are to be destroyed, ARMS1 starts from a subset of the universe called the root set \(R \subseteq U\) and visits all objects transitively reachable from \(R\). The reachable objects are called live objects \(L \subseteq U\). All other objects are called dead objects \(D\). \(L\) and \(D\) are disjoint and partition the universe in such that \(U = L \cup D\) (also note that \(R \subseteq L\) and \(R \cap D = \emptyset\)). ARMS1 removes all dead objects \(D\) from the universe \(U\) such that \(U@new = U@old - D\) and destroys them.
Starting up and shutting down
To use the services of ARMS1, a user must acquire an handle to ARMS1. The user acquires a handle by a succesfull call to Arms_Status Arms_startup()
.
This function returns Arms_Status_Success
to indicate success and returns status code different from Arms_Status_Success
on failure.
The following table lists the possible values returned in case of failure
Value | Description |
Arms_Status_AllocationFailed | an allocation failed |
Arms_Status_OperationInvalid | there are too many handles |
A user may acquire more than one handle.
If the user no longer requires the services of ARMS1, a user must relinquish the acquired handles.
The user relinquishes a handle by a successful call to Arms_Status Arms_shutdown()
.
This function returns Arms_Status_Success
to indicate success and returns status code different from Arms_Status_Success
on failure.
The following table lists the possible values returned in case of failure
Value | Description |
Arms_Status_OperationInvalid | if there are objects in \(U\). This is usually the result of illicit usage of ARMS1. |
Arms_Status_OperationInvalid | if the last handle was already relinquished. This is usually the result of illicit usage of ARMS1 |
Warning: Using the services of ARMS1 without a handle is undefined behavior.
Creating types
The user adds a type to the ARMS1 by invoking Arms_Status Arms_registerType(char const* name, size_t nameLength,
Arms_TypeRemovedCallbackFunction* typeRemoved, Arms_VisitCallbackFunction *visit, Arms_FinalizeCallbackFunction* finalize)
. The first argument name
is
a pointer to an array of nameLength
Bytes denoting the type name. No two types of the same name can be
registered and this function fails with Arms_Status_TypeExists
if an attempt is made to do so. visit
must point to a Arms_VisitCallbackFunction
or must be a null pointer. finalize
must point to a
Arms_FinalizeCallbackFunction
or must be a null pointer. typeRemoved
must point to a Arms_TypeRemovedCallbackFunction
or must be a null pointer. If this function fails it returns a value different from Arms_Status_Success
. The following table lists the possible values returned in case of failure
Value | Description |
Arms_Status_ArgumentInvalid | pObject is a null pointer |
Arms_Status_ArgumentInvalid | name is a null pointer |
Arms_Status_ArgumentInvalid | nameLength exceeds limits |
Arms_Status_ArgumentInvalid | size exceeds limits |
Arms_Status_AllocationFailed | an allocation failed |
Type removed callback function
A type removed function is a function of the signature void Arms_TypeRemovedCallbackFunction(Arms_Natural8 const* name, Arms_Size nameLength)
.
The type removed callback function is supplied to a type when that type is created.
The function is invoked when the type is removed (for example, when Arms_shutdown
is invoked) and is passed a pointer to an array of Bytes (the name of the type) and the length of that array.
struct File {
...;
};
...
static bool g_registered = false;
...
void File_typeRemoved(Arms_Natural8 const* name, Arms_Size nameLength) {
g_registered = false;
}
...
int main(int argc, char**argv) {
Arms_startup();
if (!g_registered) {
Arms_registerType("File", strlen("File"), &typeRemovedCallback, NULL, NULL);
g_registered = true;
}
Arms_shutdown();
return EXIT_SUCCESS;
}
Note that types can be created with a null pointer for the finalize function.
Visit callback function
A visit function is a function of the signature void Arms_VisitCallbackFunction(void *object)
.
The visit callback function is supplied to a type when that type is created and is passed a pointer to
objects of that type (or any other type where it supplied to). The visit function shall invoke
void Arms_visit(void* object)
on any object directly reachable from the specified object.
In the following example, File_visit
is implemented to visit the field fn
of struct File
if it was not null.
struct File {
void* fn;
...
};
...
void File_visit(File* file) {
if (file->fn) {
Arms_visit(file->fn);
file->fn = NULL;
}
}
...
int main(int argc, char**argv) {
Arms_startup();
Arms_registerType("File", strlen("File"), &visitFile, NULL);
struct File* file;
Arms_allocate(&file, "File", strlen("File"), sizeof(struct File));
file->fn = NULL;
Arms_lock(file);
Arms_RunStatistics statistics = { .destroyed = 0 };
Arms_run(&statistics);
Arms_shutdown();
return EXIT_SUCCESS;
}
Note that types can be created with a null pointer for the visit function.
Finalize callback function
A finalize function is a function of the signature void Arms_FinalizeCallbackFunction(void *object)
.
The finalize callback function is supplied to a type when that type is created and is passed a pointer to
objects of that type (or any other type where it supplied to). The finalize function shall perform cleanup
of unmanaged resources like unmanaged memory, file handles, etc. In the following example, File_finalize
is implemented to invoke `fclose` on the field fd
of struct File
if it was not null.
struct File {
FILE* fd;
};
...
void File_finalize(File* file) {
if (file->fd) {
fclose(file->fd);
file->fd = NULL;
}
}
...
int main(int argc, char**argv) {
Arms_startup();
Arms_registerType("File", strlen("File"), NULL, NULL, &finalizeFile);
struct File* file;
Arms_allocate(&file, "File", strlen("File"), sizeof(struct File));
file->fd = fopen(...);
Arms_RunStatistics statistics = { .destroyed = 0 };
Arms_run(&statistics);
Arms_shutdown();
return EXIT_SUCCESS;
}
Note that types can be created with a null pointer for the finalize function.
Creating objects
To create an object, the user of ARMS1 creates an object by invoking Arms_Status Arms_allocate(void** pObject,
char const* name, size_t nameLength, size_t size)
. name
is a pointer to an array of nameLength
Bytes
denoting the type name of the type to be assigned to object. size
denotes the size, in Bytes, of the object to allocated
(0 is a valid size). If this function is invoked successfully, the *pObject
is assigned a pointer to an object of the
specified size. The contents of the Bytes are unspecified. The object is assigned the type of the specified name.
If this function fails it returns a value different from Arms_Status_Success
.
The following table lists the possible values returned in case of failure
Value | Description |
Arms_Status_ArgumentInvalid | pObject is a null pointer |
Arms_Status_ArgumentInvalid | name is a null pointer |
Arms_Status_ArgumentInvalid | nameLength exceeds limits |
Arms_Status_ArgumentInvalid | size exceeds limits |
Arms_Status_TypeNotExists | the type does not exist |
Arms_Status_AllocationFailed | an allocation failed |
Freeing resources
To relinqish resources, the user invokes Arms_Status Arms_run()
which destroys dead objects \(D\) such that the new universe
only contains live objects, in other terms \(U@new = U@old - D\) or equivalently \(U@new = L\) holds.
Arms_run
always succeeds.
Locks
A locked object \(x\) is element of the root set \(R\). Hence this object and all objects reachable from this object are live.
To add a lock to an object, the user invokes Arms_Status Arms_lock(void* object)
.
If this function is invoked successfully, the lock count of the object pointed to by object
was incremented by one. The initial lock count of an object is 0. A lock count of 0 means an object is not locked.
If this function fails it returns a value different from Arms_Status_Success
.
The following table lists the possible values returned in case of failure
Value | Description |
Arms_Status_ArgumentInvalid | object is a null pointer |
Arms_Status_OperationInvalid | the lock would overflow |
Arms_Status_AllocationFailed | an allocation failed |
To remove a lock from an object, the user invokes Arms_Status Arms_unlock(void* object)
.
If this function is invoked successfully, the lock count of the object pointed to by object
was decremented by one. If this function fails it returns a value different from Arms_Status_Success
.
The following table lists the possible values returned in case of failure
Value | Description |
Arms_Status_ArgumentInvalid | object is a null pointer |
Arms_Status_OperationInvalid | the lock count would underflow |
Unmanaged Memory
ARMS1 allows for allocation, reallocation, and deallocation of unmanaged memory.
Arms_allocateUnmanaged
Arms_Status Arms_allocateUnmanaged(void** p, size_t n);
Allocates an unmanaged memory block of size n
.
If this function succeeds:
A pointer to the beginning of the memory block is stored in *p
.
Arms_Status_Success
is returned.
If this function fails: The environment is not observably changed. The function returns one of the status code in the table below.
Status Code | Description |
Arms_Status_AllocationFailed | The allocation failed. |
Arms_Status_ArgumentValueInvalid | p is a null pointer. |
Note that n
can be 0 as 0 is a valid size for a memory block.
Arms_reallocateUnmanaged
Arms_Status Arms_rallocateUnmanaged(void** p, size_t n);
If this function succeeds:
A pointer to the beginning of the new memory block is stored in *p
.
The first
is min(n,
where n
is the size of the
new memory block and Arms_Status_Success
is returned.
If this function fails: The environment is not observably changed. The function returns one of the status code in the table below.
Status Code | Description |
Arms_Status_AllocationFailed | The allocation failed. |
Arms_Status_ArgumentValueInvalid | p is a null pointer. |
Note that n
can be 0 as 0 is a valid size for a memory block.
Arms_deallocateUnmanaged
Arms_Status Arms_deallocateUnmanaged(void* p);
Deallocates an unmanaged memory block.
If this function succeeds:
The memory block pointed to by p
was deallocated.
If this function fails:
The environment is not observably changed.
This function fails only if p
is a null pointer. In that case, it returns Arms_Status_ArgumentValueInvalid
.