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:
- "Value" (a C datatype defined by "avm.h") encodes all possible Acorn values. See the Value section below for more information.
- "th" represents the Virtual Machine's current thread. It provides the state information needed by AcornVM's re-entrant library. It must be passed on to every C API function that the "init" function calls.
- "pushType" and "popProperty" are C API functions that respectively create a new Type and add to it a new C-defined method. The C API reference below describes all such functions that can be used.
- incr_method is a C function that implements a callable Acorn method. See the C method section below for more information.
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:
- When Acorn VM calls a C-method, it passes "th", the current thread, as a C-function parameter. As with "init", this must be passed on to every C API function called.
- Acorn VM places all Acorn-called parameters in order on the thread's stack, with the undeclared parameter 'self' starting at index 0. The thread's data stack API's (such as getLocal and pushValue) can be used to retrieve (or place) values.
- The data stack includes additional empty value slots (typically 20) for working storage. More must be explicitly allocated, if they are needed.
- This code example did not validate whether 'self' actually encoded an Integer. Bad example!
- Value holds encoded Acorn values. Conversion functions must be used to transform these values into datatypes digestible by C code, or back into Values that are given back to Acorn. "avm.h" gives the c-datatypes special names (like Aint) to ensure their precision (32 vs. 64-bit) matches the Acorn Value.
- A C-method must always return at least one Value to its Acorn caller. Its return value(s) should be placed in order on top of the data stack. The C return statement should specify how many values the method returns.
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:
- All passed parameters. 'self' is first (at index 0).
The rest follow in order. Initially, the stack top follows the last parameter.
- AuintIdx getTop(Value th) - Return how many values the stack holds. Initially, this is the number of parameters.
- void setTop(Value th, AintIdx idx) - A positive index dictates how many values the stack should hold, which can shrink or grow it (padding with 'null's). A negative index pops that number of values off the top.
- Value getLocal(Value th, AintIdx idx) - Retrieve the stack's value at the index.
- void setLocal(Value th, AintIdx idx, Value val) - Replace the stack's indexed value with val.
Warning: To optimize performance, Acorn does not check the index passed (except when compiled in debug mode, where bad indices will generate an exception). Be sure your method never passes any index value less than 0 or more than the number of known values on your stack, or unexpected results may ensue.
- The method's store of local variables, preserving in-process work.
Acorn VM ensures at least 20 Value slots are available for local variables
(use needMoreLocal() if the method requires more).
Generally, working values are pushed on and popped off from the stack's top.
The index for a just-pushed value is getTop(th)-1.
- Value pushValue(Value th, Value val) - Push a value on the stack's top (which is incremented). Useful for atomic values. Return the value pushed. Use other push functions for new collections, methods, globals and closure variables.
- Value pushLocal(Value th, AintIdx idx) - Push a copy of the stack's value at index onto the stack's top. Return the value pushed.
- Value popValue(Value th) - Pop a value off the top of the stack. Top is decremented.
- void popLocal(Value th, AintIdx idx) - Pop the stack's top value and use it to replace the stack's value at idx.
- AintIdx getFromTop(Value th, AintIdx fromtop) - Retrieve the value using an index counting backwards from the top (0=top-1, 1=top-2, ...).
- void deleteLocal(Value th, AintIdx idx) - Remove the value at index (shifting down all values above it)
- void insertLocal(Value th, AintIdx idx, Value val) - Insert the value into index (shifting up all values there and above)
- void copyLocal(Value th, AintIdx toidx, AintIdx fromidx) - Copy the stack value at fromidx into toidx.
- int needMoreLocal(Value th, AuintIdx needed) - Ensure local data stack has room for 'needed' values above 'top'. Returns 0 if it cannot guarantee that amount of space. This may grow the stack, but never shrinks it.
- The method's return values. There should always be at least one. Acorn VM expects to find them at the top of the stack. The fastest way to safely return 'self' is setTop(th, 1).
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):
- Value pushClosure(Value th, AintIdx size) - Push and return a new Closure with space for get and set methods and the closure variables all pushed onto the stack in that order.
- Value pushCloVar(Value th, AintIdx varidx) - Push and return a specific closure variable's value (0 = get method, 1 = set method, 2 = first closure variable, etc.
- void popCloVar(Value th, AintIdx varidx) - Change a closure variable to have the value popped off the local stack.
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.
- Value pushGloVar(Value th, const char* var) - Push and return the symbolically-named global variable's value.
- void popGloVar(Value th, const char *var) - Alter the symbolically-named global variable to have the value popped off the local stack.
- Value pushGlobal(Value th) - Push the value of the current process thread's global variable table.
All
The following functions apply to all or most Values
- int isSame(Value, Value) - Return 1 if the two values are identical ('==='), otherwise 0. This is quick and highly accurate for null, false, true, integers and symbols. For other Values, it can indicate that they refer to the same object. It should not be used for trying to compare the contents of two collections or seeing if two float values are identical after rounding errors.
- Value getProperty(Value th, Value self, Value propnm) - Get self's raw property value. Return aNull if not found.
- int isCallable(Value val) - Return 1 if callable: a method, closure, or yielder.
- void methodCall(Value th, int nparms, int nexpected) -
Call a method whose property symbol is placed on stack (with nparms above it).
nexpected specifies how many return values to expect to find on stack.
For example:
// C API calls, roughly equivalent to: A = Func("how", Tbl('index'), 14) pushSym(th, “()”); pushGloVar(th, “Func”); pushNewStr(th, “how”); pushGloVar(th, “Tbl”); pushMember(th, thtop-1, “index”); deleteLocal(th, thtop-2); // “Tbl” pushValue(th, anInt(14)); methodCall(th, 4, 1); // 4 parms, returns 1 popGloVar(th, “A”);
pushProperty(Value th, AintIdx validx, const char *propnm) -
Push and return the value held by the uncalled property of the value found at the stack's specified index.
- Value pushGetActProp(Value th, AintIdx selfidx, const char *propnm) - Push and return the value held by the perhaps-called property of the value found at the stack's specified index. Note: This lives in between pushProperty (which never calls) and getCall (which always calls). This calls the property's value only if it is callable, otherwise it just pushes the property's value.
- void popSetActProp(Value th, AintIdx selfidx, const char *mbrnm) - Store the local stack's top value into the perhaps-called property of the value found at the stack's specified index Note: This lives in between popProperty (which never calls) and setCall (which always calls). This calls the property's value only if it is a closure with a set method. Otherwise, it sets the property's value directly if (and only if) self is a type.
- void getCall(Value th, int nparms, int nexpected) - Get a value's property using indexing parameters. Will call a method if found. The stack holds the property symbol followed by nparms parameters (starting with self). nexpected specifies how many return values to expect to find on stack.
- void setCall(Value th, int nparms, int nexpected) - Set a value's property using indexing parameters. Will call a closure's set method if found. The stack holds the property symbol followed by nparms parameters (starting with self). The first value after self is the value to set. nexpected specifies how many return values to expect to find on stack.
- int usesType(Value th, Value val, Value type) - Return 1 if the val value makes use of the provided type's properties.
- Value getType(Value th, Value val) - Return the value's type (works for all values). It may be a Type, an array of Types, or null.
- void setType(Value val, Value type) - Set the type used by a value, if it is a collection based on a string, array or table.
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:
- Value aBool(int) - Return false if 0. Otherwise, return true.
- int isFalse(Value) - Return 1 if value is null or false, otherwise 0.
- int isBool(Value) - Return 1 if the value is true or false, otherwise 0.
Integers
These functions apply to integer values:
- Value anInt(Aint) - Convert a C signed integer into an Integer Value.
- Aint toAint(Value) - Convert an Integer Value into a C signed integer.
- int isInt(Value) - Return 1 if the value is of type Integer, otherwise 0.
Floating Point Numbers
These functions apply to floating point values:
- Value aFloat(Afloat) - Convert a C floating point number into a Float Value.
- Afloat toAfloat(Value) - Convert a Float Value to a C floating point number.
- int isFloat(Value) - Return 1 if the value is of type Float, otherwise 0.
Symbols
These functions apply to symbols. Since symbols are immutable, they should never be altered.
- Value pushSym(Value th, char*) - Push and return the corresponding Symbol value for a 0-terminated c-string.
- Value pushSyml(Value th, char*, AuintIdx len) - Push and return the corresponding Symbol value for byte sequence of specified length.
- int isSym(Value) - Return 1 if the value is a Symbol, otherwise 0.
- const char* toStr(Value) - Return a read-only pointer into a C-string encoded by a symbol (or string) Value (or Null for other Value types). It is guaranteed to have a 0-terminating character just after its full length.
- AuintIdx getSize(Value) - Return the number of bytes in a symbol.
- int isEqStr(Value, char* str) - Return 1 if the symbol or string value's characters match the zero-terminated c-string, otherwise 0. isSame() can be used to compare two symbol values. Other forms of string comparisons can be built using toStr() and native C functions.
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:
- String, for when the buffer contains character- (or byte-) oriented data (e.g. Text).
The space allocated for the string is resizeable, as needed.
The size of a string may be smaller than the allocated block that contains the string.
The c-string ending character '\0' is automatically placed after the string and at the end of the block.
Its functions include:
- Value pushString(Value th, Value type, char* str) - Push and return a new typed string for the specified, 0-terminated c-string (whose contents are copied over). Specifying a type of aNull is changed to Text.newtype.
- Value pushStringl(Value th, Value type, char* str, AuintIdx len) - Push and return a new typed string whose allocated block is len+1 bytes. If a non-NULL str is specified, its contents for len bytes are copied over, making len the string's size. If NULL is given for str, the string's size is set to 0 (and nothing is copied over). Specifying a type of aNull is changed to Text.newtype.
- Value pushSerialized(Value th, Value val) - Push a value's serialized Text, an abridged, human-readable view of the contents of a value, as well as recursively all values it contains.
- int isStr(Value) - Return 1 if the value is a string, otherwise 0.
- const char* toStr(Value) - Return a read-only pointer into the string's byte data. This pointer can be re-cast as needed. If API functions are used, it will have a 0-terminating character just after its full length.
- int isEqStr(Value, char* str) - Return 1 if the symbol or string value's characters match the zero-terminated c-string, otherwise 0. Other forms of string comparisons can be built using toStr() and native C functions.
- AuintIdx getSize(Value) - Return the number of bytes used in the string.
- void strResize(Value th, Value str, AuintIdx len) - Resize string's available space to a specified length, truncating if smaller. Allocated space will not shrink, but may expand if required.
- void strSub(Value th, Value val, AuintIdx pos, AuintIdx sz, char *str, AuintIdx len) - Replace part of a string with the c-string contents starting at pos. If sz==0, it becomes an insert. If str==NULL or len==0, it becomes a deletion. The string will be resized automatically to accommodate excess characters. The operation will not be performed if resizing is not possible.
- void strAppend(Value th, Value val, const char *addstr, AuintIdx addstrlen) - Append characters to the end of a string, growing its allocated block as needed.
- CData, valuable for holding C structured data, including pointers or handles.
In addition to the resizeable block of data, a CData can also ask for a small, fixed-size header area.
The header area has two benefits: it spares the need to allocate the resizeable block of data
when the c-data is small and fixed in size (e.g., a file header), or it can be used to hold
metadata for an array or buffer of data stored in the resizeable block.
- Value pushCData(Value th, Value type, unsigned char cdatatyp, AuintIdx len, int extrahdr) - Push and return a new typed C-data string that allocates len bytes for c-data (if len=0, no block is allocated for c-data). The size begins at zero, but grows when data is added using strSub or strAppend. The c-data may hold pointers and handles, but never Values. The extrahdr indicates the size of the header extension for a small (<= 124 bytes), fixed-size data area. cdatatyp allows the using program to distinguish between up-to-256 different uses of c-data.
- Value strHasFinalizer(Value cdata) - This sets a flag which ensures that the _finalizer method from this cdata's type is called by the garbage collector before an unreferenced cdata value's memory is freed. This ensures that any allocated resource whose handle or pointer is stored in this cdata (such as a file handle) is properly closed and cleaned up.
- void strSwapBuffer(Value th, Value val, char *buffer, AuintIdx len) - Use provided buffer of specified length, replacing whatever AcornVM might have allocated. Provided buffer belongs to Acorn and should not ever be freed by caller.
- unsigned char getCDataType(Value cdata) - Retrieve the cdata's cdatatyp value, allowing one to determine what sort of c-data this is.
- bool isCData(Value) - Return true if the value is a C-data, otherwise false.
- const void* toCData(Value) - Return a pointer into the c-data's resizeable data area. This pointer can be re-cast as needed.
- void *toHeader(Value cdata) - Returns the address of the small, fixed-size header area allocated when the c-data was created. This pointer can be re-cast as needed.
- getSize, strResize, strSub and strAppend may be used on cdata, as described above for strings. Non-character data will have to be re-cast as needed and sizes will have to be adjusted to byte lengths.
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.
- Value pushArray(Value th, Value type, AuintIdx size) - Return an Array of specified type (or List, if aNull), with space allocated for size Values.
- int isArr(Value) - Return 1 if the value is a Array, otherwise 0.
- void arrSetSize(Value th, Value val, AuintIdx len) - Resize array's available space to a specified length, truncating if smaller. Allocated space will not shrink, but may expand if required.
- void arrForceSize(Value th, Value val, AuintIdx len) - Force allocated and used array to a specified size, truncating or expanding as needed. Growth space is initialized to aNull.
- AuintIdx getSize(Value) - Return the size of a symbol, string, array, table or other collection. Any other value type returns 0.
- Value arrGet(Value th, Value arr, AuintIdx pos) - Retrieve the value in array at specified position.
- void arrSet(Value th, Value arr, AuintIdx pos, Value val) - Put val into the array starting at pos. This can expand the size of the array.
- void arrAdd(Value th, Value arr, Value val) - Append val to the end of the array (increasing array's size).
- void arrRpt(Value th, Value arr, AuintIdx pos, AuintIdx n, Value val) - Propagate n copies of val into the array starting at pos. This can expand the size of the array.
- void arrDel(Value th, Value arr, AuintIdx pos, Value n) - Delete n values out of the array starting at pos. All values after these are preserved, essentially shrinking the array.
- void arrIns(Value th, Value arr, AuintIdx pos, AuintIdx n, Value val) - Insert n copies of val into the array starting at pos, expanding the array's size.
- void arrCopy(Value th, Value arr, AuintIdx pos, AuintIdx n, Value arr2, AuintIdx pos2, AuintIdx n2) - Copy n2 values from arr2 starting at pos2 into array, replacing the n values in first array starting at pos. This can increase or decrease the size of the array. arr and arr2 may be the same array.
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.
- Value pushTbl(Value th, Value type, AuintIdx size) - Return a Table of specified type with space allocated for size key/value pairs.
- Value pushType(Value th, Value type, AuintIdx size) - Return a Table of specified type with space allocated for size key/value pairs. Flags are set to indicate it should be treated as a type.
- Value pushMixin(Value th, Value type, AuintIdx size) - Return a Table of specified type with space allocated for size key/value pairs. Flags are set to indicate it should be treated as a mixin.
- int isTbl(Value) - Return 1 if the value is a Table, otherwise 0.
- void tblResize(Value th, Value tbl, AuintIdx size) - Resize table's available space to a specified size (may only grow).
- AuintIdx getSize(tbl) - Return the size of a symbol, string, array, table or other collection. Any other value type returns 0.
- Value pushMember(Value th, AintIdx tblidx, const char *mbrnm) - Push and return the value of the named member of the table found at the stack's specified index.
- void popTblSet(Value th, AintIdx tblidx, const char *mbrnm) - Put the local stack's top value into the named member of the table found at the stack's specified index.
- void popProperty(Value th, AintIdx typeidx, const char *mbrnm) - Store the local stack's top value into the uncalled property of the type found at the stack's specified index Note: Unlike pushProperty, popProperty is restricted to the type being changed.
- int tblHas(Value th, Value tbl, Value key) - Return 1 if table has an entry at key, 0 if not.
- Value tblGet(Value th, Value tbl, Value key) - Return the value paired with 'key', or 'null' if not found.
- void tblSet(Value th, Value tbl, Value key, Value val) - Insert or alter the table's 'key' entry with value.
- void tblRemove(Value th, Value tbl, Value key) - Delete a key from hash table, if found.
- void addMixin(Value th, Value type, Value mixin) - Insert the mixin at the top of a list of inheritable types in inheritype.
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.
- Value pushCMethod(Value th, AcMethodp func) - Push and return the value for a method written in C
- Value pushAcornPgm(Value th, Value pgmtext, Value srcsym) - Push and return the value resulting from compiling and running an Acorn program. pgmtext is the string text for the program. srcsym is the URL for the resource that is the source of the string text.
Thread
The thread manages the execution state for running methods, used by Process, Thread and Yield types. These functions work on threads.
- Value pushThread(Value th) Push and return a new Thread.
- int isThread(Value th) - Return 1 if a Thread, else return 0
Virtual Machine
This refers to the singular virtual machine managing everything. These functions work on the VM.
- Value pushVM(Value th) Push and return the VM's value.
- void vmLog(char *, ...) - Send a printf message to the log (stderr)
- int64_t vmStartTimer() - Start a micro-second based timer, capturing now
- float vmEndTimer(int64_t starttime - Return a floating point number, showing seconds since starttime