Low-level operations

This section contains descriptions of several low-level facilities, most of which deal with memory management. Note that using these operations is generally more complex and error prone than using higher-level operations described earlier, so be careful and read all instructions carefully.

Temporary locations

The operations described below can be used to allocate additional locations for holding temporary values so that they are seen by the garbage collector. These are generally used in utility functions that can be called from various Alore functions or methods implemented in C. Temporary locations that are used directly in Alore functions or methods should be generally allocated in the frame.

Since these operations allocate the locations from a stack, the allocation and freeing calls must be matched in a Last In, First Out fashion. However, if a direct exception is raised before freeing, the runtime automatically frees any unfreed temporary locations allocated after the point at which the exception is caught. In general, there is no need to account for direct exceptions when using temporary locations.

The number of available temporary locations per thread might be fixed to a relatively low value. Therefore avoid allocating a large number of temporary locations and use Alore container objects such as Arrays for holding large numbers of values.

Note: These functions never cause the garbage collector to run if they return successfully. This means, most importantly, that they do not invalidate any AValues held in C local variables (unless an exception was raised).

AValue *AAllocTemp(AThread *t, AValue v)
Allocate a temporary location in the temporary value stack of the current thread and store v in it. Return a pointer to the temporary location. You must call AFreeTemp before returning from the current executing Alore function or method to release the temporary location. The temporary location behaves like a location in the frame of a function. Raise a direct exception on all error conditions.
void AFreeTemp(AThread *t)
Release a temporary location allocated using AAllocTemp. This function does not check for error conditions and never raises an exception or causes the garbage collector to run.
AValue *AAllocTemps(AThread *t, int n)
Allocate n consecutive temporary locations in the temporary value stack of the current thread and initialize them to some undefined value, for example AZero. You must call AFreeTemps to free these values before returning from the current executing Alore function or method. Raise a direct exception on all error conditions.
void AFreeTemps(AThread *t, int n)
Release n temporary locations allocated using AAllocTemps. This function does not check for error conditions and never raises an exception or causes the garbage collector to run.

Instance binary data manipulation

The following three operations (ADataPtr, ASetData_M and AGetData_M) can only be used with objects of types that have allocated binary data using A_BINARY_DATA or A_BINARY_DATA_P macros. These functions perform no error checking.

void *ADataPtr(AValue v, unsigned offset)
Return a pointer to instance-specific binary data at the specified offset. The returned pointer will be invalidated by any operation that may trigger garbage collection.
ASetData_M(AValue v, unsigned offset, <type>, <newValue>)
Modify the instance-specific binary data at the specified offset, storing a value newValue of the specified type. Example:
MyData *ptr = ...;
ASetData_M(frame[0], 0, MyData *, ptr);
  /* Self must have at least sizeof(MyData *) bytes allocated as
     instance-specific data. */

The offset must have proper alignment for the data type. See Alignment of binary data for more information.

<type> AGetData_M(AValue v, unsigned offset, <type>)
Read instance-specific binary data at the specified offset, returning a C value of the specified type. Example:
MyData *ptr = AGetData_M(frame[0], 0, MyData *);

The offset must have proper alignment for the data type. See Alignment of binary data for more information.

ABool ASetExternalDataSize(AThread *t, AValue v, Asize_t size)
Use this function to inform the garbage collector of any external data included in object v that is not allocated from the garbage collected heap, since this data is not otherwise visible to the garbage collector. The garbage collector may take advantage of this information when scheduling garbage collection cycles.

If the exact size of referenced data cannot be calculated, it is sufficient to provide an estimation of the size. If the actual size is larger than the estimate, the program may consume more memory than it otherwise would. Conversely, if the actual size is smaller than the estimate, the program may spend more time in the garbage collector than it otherwise would.

Return TRUE if successful, FALSE if failed.

Note: Size must fit in a short Int. This operation may raise a non-direct exception.

Note: The type of v must have the A_EXTERNAL_DATA declaration.

Object construction

AValue AMakeUnitializedObject(AThread *t, AValue type)
Construct an instance of a type, but do not call the create method or setup the member variables. All member variables will be initialized to nil.

Note: You must initialize the object yourself after calling this function and before making the object visible to external code. You must not use this function to create objects whose internal structure might change. This includes all objects of all types defined in the Alore standard library.

Memory management

These functions can often be used as replacements for C malloc, free and realloc calls. They allocate memory from the garbage collected heap, so unlike memory allocated using malloc, these memory blocks are automatically freed by the Alore garbage collector. The blocks allocated using these functions must not be made visible to Alore code. All of these functions raise direct exceptions on all error conditions.

AValue AAllocMem(AThread *t, Asize_t size)
Allocate a block of size bytes from the garbage collected heap. Use AMemPtr to access the contents of the block, but note that this block may be moved by the garbage collector, and thus the return value of AMemPtr may change. As always, the ordinary rules for using AValue values apply to the return value.
AValue AAllocMemFixed(AThread *t, Asize_t size)
Allocate a fixed, non-movable block of size bytes from the garbage collected heap. Use AMemPtr to access the contents of the block. This block may not be moved by the garbage collector, and thus the return value of AMemPtr is guaranteed to not to be changed by the garbage collector. But note that the returned AValue must still be always accessible to the garbage collector while it is alive.
void *AMemPtr(AValue mem)
Return a pointer to the start of the memory area related to a block allocated using AAllocMem or AAllocMemFixed. This block can be used to store any binary data, as long the boundaries of the block, specified by the size of block, are honored. Note that the block cannot be used to store garbage collected references (AValue values), and unless the block was allocated using AAllocMemFixed, it may be moved by the garbage collector at any memory allocation operation and thus the value returned by this function becomes invalid. The returned pointer is aligned to a 32-bit or 64-bit boundary depending on the current pointer width (32 or 64 bits, respectively).

Note: The alignment might be inappropriate for storing some data types (doubles, for example). Manual alignment enforcement may sometimes be needed.

AValue AReallocMem(AThread *t, AValue mem, Asize_t newSize)
Change the size of the block allocated using AAllocMem or AAllocMemFixed to a new size. The original contents of the block are kept unless the new size is smaller than the old one, in which case the initial contents, up to the new size, are maintained. Return a reference to the resized block, which might be different from the original value. The return value behaves identically to a block just allocated using AAllocMem or AAllocMemFixed. The movability status of the block is remains the same as originally, i.e. if the block was allocated originally using AAllocMemFixed, the returned block will be non-movable. The mem argument must refer to a valid block.
void AFreeMem(AThread *t, AValue mem)
Either do nothing or release the contents of the block referenced to by the mem argument. There must be no active references to the block when AFreeMem is called. Calling this function is always optional, since the garbage collector will eventually free any unreferenced blocks, and the implementation may also ignore this call.
AValue AAllocBuf(AThread *t, Asize_t initReserve)
Allocate a growable buffer with initReserve bytes initially reserved for the buffer data. The buffer will be empty initially, independent of the value of the initReserve argument.
AValue AAllocBufFixed(AThread *t, Asize_t initReserve)
Allocate a fixed, non-movable growable buffer with initReserve bytes reserved for the buffer data. The buffer will be empty initially, independent of the value of the initReserve argument.
void AAppendBuf(AThread *t, AValue buf, const char *data, Asize_t len)
Append len bytes of data, pointed to by the argument data, to the end of a buffer allocated using AAllocBuf or AAllocBufFixed. The buffer is grown if not enough additional space has been reserved for the data.
void *ABufPtr(AValue buf)
Return a pointer to the beginning of the memory area related to a buffer allocated using AAllocBuf or AAllocBufFixed. If the buffer is not fixed, any operation that may trigger garbage collection will invalidate the pointer. The available size of the area is specified by the accumulated number of bytes that has been appended to the buffer using either AAppendBuf or AReserveBuf. The alignment of the returned data is the same as for AMemPtr.
void *AReserveBuf(AThread *t, AValue buf, Asize_t len)
This behaves like like AAppendBuf, but additional space is only allocated at the end of the buffer and not initialized. A pointer to the uninitialized data area is returned. This pointer behaves like the pointer returned by ABufPtr, i.e. if the buffer is not fixed, any operation that may trigger garbage collection will invalidate the pointer.
void AFreeBuf(AThread *t, AValue buf)
Either do nothing or release the contents of the buffer referenced to by the buf argument. There must be no active references to the buffer when AFreeBuf is called. Calling this function is always optional, since the garbage collector will eventually free any unreferenced buffers, and the implementation may also ignore this call.

Value containers

These functions can be used to create and access container objects. Each container object encapsulates a single value. The only special property of the container objects is the possibility of creating a C-level pointer that can be used to read and modify the value stored in the container. This pointer can live across function calls and returns and memory allocation operations. The effect is somewhat similar to that achieved using AAllocTemp, but the locations allocated with AAllocTemp can only live within a single function invocation, and they must be freed in a strict LIFO order. Neither of these is true for value containers.

If you need to store multiple values, you can either allocate multiple container objects or store a composite object such as an array in a single container object.

AValue AAllocContainer(AThread *t, AValue initValue)
Allocate a container object that is initialized to the specified value.

Note: You must never expose the returned object to Alore code.

void *AContainerPtr(AValue container)
Return an opaque pointer that can be used to access and modify the object reference stored in a container object, but only using the functions below. The pointer remains valid until the container object becomes unreferenced and thus may be garbage collected. This function performs no error checking.

Note: The returned pointer does not count as a reference to the container, since C level pointers are not visible to the garbage collector. You need to maintain a reference to the container object in a location that is accessible to the garbage collector, such as in another Alore object, in a function frame, etc.

AValue AContainerValue(void *ptr)
Return the value stored in a container object. The pointer argument can be obtained using AContainerPtr. This function performs no error checking. In particular, the function does not check if the container object has been freed by the garbage collector.
void ASetContainerValue(AThread *t, void *ptr, AValue item)
Modify the value contained by a container object. The pointer argument can be obtained using AContainerPtr. The argument pointer must be valid. Raise a direct exception on all error conditions.

Thread support

Potentially long-running or blocking C operations must be surrounded with AAllowBlocking and AEndBlocking calls, since otherwise the thread performing those C operations may block other threads. In particular, garbage collection cannot be performed during a call to an ordinary C function unless the call is surrounded by these function calls.

void AAllowBlocking()
Allow potentially blocking C operations such as file I/O to be performed in the current thread. No Alore API functions (other than AEndBlocking) can be called after calling AAllowBlocking until calling AEndBlocking. This function enables other threads to run the garbage collector at any time without having to synchronize with the current thread. Normally the garbage collector can be run only at well-defined instants in each thread.
void AEndBlocking()
Disallow blocking C operations in the current thread. This must be called only after calling AAllowBlocking to enable the normal mode of operation in the current thread.

Here is a short example of using these functions:

#include <alore/alore.h>
#include <stdio.h>

static AValue WriteFile(AThread *t, AValue *frame)
{
    /* Note: Most error checking is left out for brevity. */
    
    char path[1024];
    FILE *f;

    AGetStr(t, frame[0], path, 1024);

    AAllowBlocking();
    f = fopen(path, "w");
    fprintf(f, "Hello\n");
    fclose(f);
    AEndBlocking();

    return ANil;
}

A_MODULE(example, "example")
    A_SUB("WriteFile", 1, 0, WriteFile)
A_END_MODULE()

Miscellaneous functions

ABool ACheckInterrupt(AThread *t)
Return TRUE and raise InterruptException if the user has recently pressed Ctrl+C (or Ctrl+Break, depending on the operating system); return FALSE otherwise. You should call this function periodically in a long-running function or method invocation to enable the user to interrupt the program. You should generally propagate the exception to the caller when interrupted.