This chapter shows how to augment Acorn VM's built-in global types and environment variables using C code. This ability offers significant benefits. Types implemented using C can take advantage of existing C libraries or system software APIs. Additionally, a C-implemented type often runs much faster than its Acorn-based equivalent.

This table categorizes quick access to the API function reference:

Atomic
Null
Bool
Integer
Float
Collection
Symbol
String
Array
Table
Store
Local
Closure
Global
Other
Method
Thread
VM
All

Shared Library

Creating a shared library (.dll or .so) is the most common way to implement a new type (or several related types). Acorn VM enables these libraries to be loaded during its initialization process, once the built-in global types and variables have been loaded.

To ensure a library loads and works successfully, it must be built to match the Acorn VM code in terms of the 32- vs. 64-bit memory architecture, the version of the C API include file, and the operating system.

The library's code that directly interacts with Acorn VM should be written in C. That C code can then interface with code written in other languages, such as C++. Use a C99-compatible compiler that also supports the Acorn VM library's use of the "avm::" namespace. Wrap any C-functions (especially "init") with extern "C" {}, so that the names of these functions are not mangled by a protective C++ compiler.

Insert the following line near the top of every C program that makes use of the Acorn VM C API:

#include "avm.h"

The "init" function

The shared library must have an exported public function named "init". Acorn VM will call the "init" function immediately after loading the shared library. "init" should use the C API to create any new type(s) and corresponding methods, storing them in appropriate global variables. For example:

#include "avm.h"
#define AVM_LIBRARY 1

int incr_method(Value th);	// C-function for a method

AVM_API void init(Value th) {
	pushType(th, aNull, 1);
		pushCMethod(th, incrfunc);
		popProperty(th, 0, “property-name”);
	popGloVar(th, “NewType”);
}

Some notes about this example:

The "init" function can do more than create new types. It can also create global environment variables, alter or augment the definition of existing types, and even re-configure Acorn VM settings.

C method

An Acorn method can be implemented either with Acorn or C code. An Acorn program uses both kinds of methods in exactly the same way. However, they work differently under the covers. The Acorn VM converts an Acorn-implemented method into byte-code which is interpreted. By contrast, a C-implemented method is compiled from C into an shared library, making it directly executable by the computer's CPU. Acorn handles both kinds of call dispatch automatically, as it knows how each method was implemented.

Here is a simple example of an Acorn method implemented in C:

// .incr: A c-method that increments self's number
int incr_method(Value th) {
	Aint nbr = toAint(getLocal(th, 0)); // Get 'self' as a C-integer
	pushValue(th, anInt(nbr+1)); // Increment and push on stack
	return 1;
}

Some notes about this example:

Value

As already mentioned, "Value" is the datatype to use to represent any Acorn value. It is similar to a handle, in that the C code should not manipulate it directly. Instead, you pass it to various C APIs to retrieve the information it possesses. Various C APIs are able to convert a Value to and from the corresponding C data type.

All Values are exactly the same size, either 32-bits or 64-bits depending on the machine architecture it is built for. This consistency makes it easier for code to manage and pass them around. For efficiency, Integers and Floats are actually encoded within the Value. The drawback to doing this is the loss of 2-bits of precision for numbers, which is not a huge loss when you realize at least 30 bits are still available.

C datatypes

In addition to Value, "avm.h" defines these C datatypes used by the API functions:

Aint
A signed integer with the same bit-width as Value. Be mindful that when a signed integer is encoded into a Value, it will lose 2 high-end bits of precision.
Afloat
A floating point number with the same bit-width as Value. It also loses two bits of precision in the mantissa when encoded into a Value.
AintIdx
16-bit signed integer, typically used for indexing into a collection of Values
AuintIdx
16-bit unsigned integer, typically used for sizing a collection of Values
Afunc0
A pointer to a callable c-function with no parameters: Value (*fn) (void)

C API Reference

For ease of reference, the C API functions are organized according to either where they store Values or what type of Value they focus on handling.

Local Data Stack

Each thread holds on a single stack all local data values used by its in-process methods. For each running method, it holds:

Closure variables

Some methods run in the context of a specific Closure. These functions create a new closure and offer access to its bound closure variables (indexed in order of their declaration, beginning with 0):

Global variables

Each process worker has its own global namespace, cloned from the VM's built-in global namespace and then altered as needed within the process worker.

All

The following functions apply to all or most Values

In many cases, use getGlosym("type") to retrieve the value for a specific type.

Null

The C literal that represents Acorn's "null" value is "aNull". To determine if a Value is null, simply compare it to "aNull" (using '==').

Boolean true and false

The C literals for Acorns "true" and "false" values are: "aTrue" and "aFalse". To determine if a Value is true or false, simply compare it with "aTrue" or "aFalse". These functions are focused on boolean values:

Integers

These functions apply to integer values:

Floating Point Numbers

These functions apply to floating point values:

Symbols

These functions apply to symbols. Since symbols are immutable, they should never be altered.

Strings

String operations deal with a single, resizeable, mutable block of data (essentially a collection of bytes). This block can hold any data except an Acorn value (and only because the garbage collector cannot find or make use of it there). API functions support several use cases:

Array

Array is an ordered, re-sizeable, mutable collection of Values. It is used by the List and Closure types, as well as for holding a list of mixins and a prototype for a type's inheritype. These functions work on arrays.

Table

Table is a hashed, resizeable, mutable, unordered collection of key and value pairs. It is used by the Index, Type, and Mixin types. A key is typically a symbol, but can be any value other than null. These functions work on tables.

Methods

A method is a stateless description of a procedure that receives some parameter values, does some work, and returns some values. A method can be written in C, using the C API, or as an Acorn program. These CAPI functions work on methods.

Thread

The thread manages the execution state for running methods, used by Process, Thread and Yield types. These functions work on threads.

Virtual Machine

This refers to the singular virtual machine managing everything. These functions work on the VM.