Home

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.

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 path-to-repository-directory

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_AllocationFailedan allocation failed
Arms_Status_OperationInvalidthere 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_OperationInvalidif there are objects in \(U\). This is usually the result of illicit usage of ARMS1.
Arms_Status_OperationInvalidif 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

ValueDescription
Arms_Status_ArgumentInvalidpObject is a null pointer
Arms_Status_ArgumentInvalidname is a null pointer
Arms_Status_ArgumentInvalidnameLength exceeds limits
Arms_Status_ArgumentInvalidsize exceeds limits
Arms_Status_AllocationFailedan 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

ValueDescription
Arms_Status_ArgumentInvalidpObject is a null pointer
Arms_Status_ArgumentInvalidname is a null pointer
Arms_Status_ArgumentInvalidnameLength exceeds limits
Arms_Status_ArgumentInvalidsize exceeds limits
Arms_Status_TypeNotExiststhe type does not exist
Arms_Status_AllocationFailedan 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

ValueDescription
Arms_Status_ArgumentInvalidobject is a null pointer
Arms_Status_OperationInvalidthe lock would overflow
Arms_Status_AllocationFailedan 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

ValueDescription
Arms_Status_ArgumentInvalidobject is a null pointer
Arms_Status_OperationInvalidthe 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 CodeDescription
Arms_Status_AllocationFailedThe allocation failed.
Arms_Status_ArgumentValueInvalidp 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 k values of the new memory memory block and the old memory block are equal where k is min(n, m) where n is the size of the new memory block and m is the size of the new memory block. 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 CodeDescription
Arms_Status_AllocationFailedThe allocation failed.
Arms_Status_ArgumentValueInvalidp 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.