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.

1. 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.

ARMS1 supports various platforms (including but not restricted to Windows, Linux, and many more), however, we currently only officially support Windows. Find instructions on how to build, test, and use R1 under various systems, please refer to README.md in the root folder of the repository.

2. 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.

3. 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.

3. Managed Memory

3.1 Creating types

The user adds a type to the ARMS1 by invoking Arms_Status Arms_addType(const char* name, size_t nameLength, void* context, 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. context is an opaque pointer which is passed to the type removed callback function, the visit callback function, and the finalize callback function. If Arms_addType 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(void* context, 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). The first argument is the context as supplied to the Arms_addType. The second argument is an array of Bytes (the name of the type) and the third argument the length of that array.

struct File {
 ...;
};
...
static bool g_registered = false; ...
void File_typeRemoved(void* context, Arms_Natural8 const* name, Arms_Size nameLength) {
 g_registered = false;
}
...
int main(int argc, char**argv) {
 Arms_startup();
 if (!g_registered) {
  Arms_addType("File", strlen("File"), NULL, &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* context, void *object). The visit callback function is supplied to a type when that type is created via Arms_addType. The first argument is the context as supplied to the Arms_addType. The second argument is a pointer to the object. The second argument is an array of Bytes (the name of the type) and the third argument the length of that array. 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(void* context, File* file) {
 if (file->fn) {
  Arms_visit(file->fn);
  file->fn = NULL;
 }
}
...
int main(int argc, char**argv) {
 Arms_startup();
 Arms_addType("File", strlen("File"), NULL, NULL, &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* context, void *object). The finalize callback function is supplied to a type when that type is created via Arms_addType. The first argument is the context as supplied to the Arms_addType. The second argument is a pointer to the object. 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(void* context, File* file) {
 if (file->fd) {
  fclose(file->fd);
  file->fd = NULL;
 }
}
...
int main(int argc, char**argv) {
 Arms_startup();
 Arms_addType("File", strlen("File"), NULL, 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.

3.2 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

3.3 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.

3.4 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

4. Unmanaged Memory

ARMS1 allows for allocation, reallocation, and deallocation of unmanaged memory via memory managers.

There are two different memory managers available for different use cases. Currently, the default memory manager and the slab memory manager are available. The former is described in section "Default Memory Manager", the latter is described in section "Slab Memory Manager". All memory manager share a commmon interface which is described in section "Memory Manager Interface".

4.1 Memory Manager Interface

This section describes the interface of the memory managers.

4.1.1 Arms_MemoryManager_allocate

Arms_MemoryManager_Status Arms_MemoryManager_allocate(Arms_MemoryManager* memoryManager, void** p, Arms_Size n);

Allocates an unmanaged memory block of size n using the specified memory manager.

If this function succeeds: A pointer to the beginning of the memory block is stored in *p. Arms_MemoryManager_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_MemoryManager_Status_AllocationFailedThe allocation failed.
Arms_MemoryManager_Status_ArgumentValueInvalidp is a null pointer.

Note that n can be 0 as 0 is a valid size for a memory block.

4.1.2 Arms_MemoryManager_reallocate

Arms_MemoryManager_Status Arms_reallocate(Arms_MemoryManager* memoryManager, 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_MemoryManager_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_MemoryManager_Status_AllocationFailedThe allocation failed.
Arms_MemoryManager_Status_ArgumentValueInvalidp is a null pointer.

Note that n can be 0 as 0 is a valid size for a memory block.

4.1.3 Arms_MemoryManager_deallocate

Arms_MemoryManager_Status Arms_MemoryManager_deallocate(Arms_MemoryManager* memoryManager, 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_MemoryManager_Status_ArgumentValueInvalid.

4.2 Default Memory Manager

Obtain a pointer to the default memory manager by a call to to the function

Arms_MemoryManager* Arms_getDefaultMemoryManager()

This function always succeeds if ARMS1 is initialized. Otherwise its behaviour is undefined. The memory manager returned is valid as long as ARMS1 is initialized.

4.3 Slab Memory Manager

Obtain a pointer to the slab memory manager by a call to to the function

Arms_MemoryManager* Arms_getSlabMemoryManager()

This function always succeeds if ARMS1 is initialized. Otherwise its behaviour is undefined. The memory manager returned is valid as long as ARMS1 is initialized.

Warning: The Slab Memory Manager is work in progress.