The ArmPit Scheme (APS) interpreter is written in ARM assembly using a unified syntax that bridges Thumb2, 32-bit ARM and 64-bit Aarch64 instruction sets (ARMv7M, ARMv7A and ARMv8A architectures), via macros where necessary. The interpreter is designed to be loaded into volatile memory by the Live-SD (LSD) bootloader, and to run from memory address 0x00 in all cases, except for some Cortex-M MCUs where that address is not available and 0x20000000 is used instead. The APS is the hardware-independent part of the ArmPit Scheme system, with 4 machine code targets: 64-bit Aarch64, 32-bit ARM, Thumb-2 running from 0x00 and Thumb-2 running from 0x20000000. The corresponding binaries are named aps64.bin (Cortex-A53), aps32.bin (Cortex-A5, A8 and A9), aspT2.bin (Cortex-M4F and M7) and apsT2co2.bin (Cortex-M4F and M7), respectively.
The APS interpreter source code is organized into 1 main configuration file, 3 files of constants and macro definitions (2 used per ABI), 4 interpreter startup files (2 used per ABI), and 21 language-functionality implementation files. The main configuration file is in the top folder of the source code and the other 28 files are in the aps/ folder:
Main Configuration File: aps_080.s Constants and Macros: aps_constants.s aps_constants_64.s aps_macros.s Intepreter Startup: aps_reset_T2.s aps_reset_A32.s aps_reset_A64.s aps_init.s Scheme Implementation: aps_core.s r7rs_4.1_primitive_expression_types.s r7rs_4.2_derived_expression_types.s ... r7rs_6.14_system_interface.s r6rs_library.s
The main configuration file, aps_080.s, uses ".include" statements to load the various source code components needed for assembly. Twenty-five of the above files are loaded in all cases. The aps_constants.s file is loaded for 32-bit systems while aps_constants_64.s is used for 64-bit systems. The aps_reset_T2.s file is loaded for Cortex-M (Thumb2) MCUs, while aps_reset_A32.s is loaded for 32-bit Cortex-A5, A8 and A9 chips and aps_reset_A64.s is loaded for 64-bit Cortex-A53 CPUs. The script aps_build (at top-level in the source code) assembles the result into a machine code APS for the corresponding binary interface. It is typically called by build_all (also at top-level), which assembles all LSDs and APSs. The aps_build script actually creates a temporary file (bin/temp.s), by pre-pending a binary interface indicator to aps_080.s, and assembles that temporary file, rather than the original. This is done to specify whether one_cons_cell corresponds to 8 bytes (32-bit system) or 16 bytes (64-bit system) and, in the case of Cortex-M, whether code runs from 0x00 (cortex=1) or not (cortex=2). The pre-pended directive (one line) directs aps_080.s to ".include" the correct files and is also used in various parts of the source code (including aps_macros.s) to adjust the code to execute or the target address for jumps.
The configuration file code uses macros defined in aps_macros.s to construct a hardware-independent environment and obarray of built-in scheme primitives. During startup, this environment is merged with the hardware-dependent environment provided by the LSD bootloader (i/o ports and system-0) to form the running environment for the APS. For 32-bit systems, the configuration file also builds a function pre-entry jump table that points to common function entry code for several of the built-in primitives. In 64-bit systems, the 16-bit address of pre-entry code is stored directly in the primitive's tag so that the jump table is not needed. The configuration file further defines a few useful scheme objects, such as the stack bottom (label stkbtm:), the empty vector (named empty_vector), and informational strings (encoded as scheme symbols) displayed through the uart during startup.
APS execution begins in either the aps_reset_T2.s, aps_reset_A32.s, or aps_reset_A64.s file (depending on the MCU's binary interface). The first APS instruction is executed as the LSD bootloader ends by jumping to the address specified by the variable scheme_reset_address, which is defined in board.h files for Cortex-M MCUs and in lsd_constants.s for others. For the 32-bit Cortex-A MCUs, this first instruction is at address 0x00 and is that contained in the first exception table entry in aps_reset_A32.s. It is a jump to label "reset:". For other MCUs, the LSD jumps directly to the label "reset:". In all cases, the label "reset:" directly follows the exception vector table in the aps_reset_XX.s file.
The APS is entered from the LSD in privileged mode (eg. EL1 for Aarch64), with the cpu-id in register sv1, LSD's global vector in s17 (32-bit) or x26 (64-bit) and LSD's Main Buffer in s18 (32-bit) or x27 (64-bit). It checks to see if it is running on the main core (specified in board.h with default value in lsd_constants.s), and if so, copies the Main Buffer and all Core Buffers to SDRAM (or an appropriate SRAM target), stores addresses of some interrupt, memory allocation and error routines in it (which LSD i/o ports code may need when the system runs) merges LSD's ports and system-0 sub-environments with the APS sub-environment (including obarrays) and copies LSD's ISR_vector to SDRAM (or appropriate SRAM). Then, for any cpu (not just the main core), the core's exception table address is updated to be that at the beginning of the aps_reset_XX.s file, and stack addresses are setup for privileged and user mode. At this stage, sv1 contains the cpu-id, sv2 contains the SDRAM address of the Main Buffer and sv3 contains the address of the Core Buffer for the executing cpu, and the code jumps to label "scinit:" in aps_init.s.
The code in aps_init.s starts by storing some useful constants in MCU registers. In particular, the values 10.0, 1.0, 0.5, infinity, NaN and e (the base of the natural logarithm) are stored in FPU registers. In 64-bit systems, the scheme value: null (empty list), is also stored in a register (nul). The code then intializes the free memory heap pointer (fre), builds the initial top-level user environment (env), prepares empty read and write buffers and sets-up the global state vector (_GLV, stored in s17 or x26) which contains, among others, heap limits for the executing core, the default i/o port for this core, the current parse mode (i.e. library or normal), the user environment, the built-in environment, user callbacks, user libraries, and pointers to read and write buffers. At this stage, and throughout the execution of the interpreter, APS's _GLV is stored in s17 or x26, while s18 or x27 contains LSD's Main Buffer (the root Main Buffer: MBFr) which contains, at index 16, a pointer to APS's Main Buffer (MBF) in SDRAM (APS's MBF is also available at index 5 in APS's _GLV, except during garbage collection when it can still be accessed through the MBFr in s18/x27). The code then sets the initial memory barriers at the top of the lower and upper heaps, to trigger garbage collection when allocation reaches them (APS 080 uses a stop-and-copy algorithm). It sets the current data stack (dts) to the stack bottom label (stkbtm:). On Cortex-M, it stores the initial address of the memory allocation nursery in register frd. It then de-reserves memory, drops to user mode with interrupts enabled (eg. EL0 in Aarch64), unlocks the file system and loads the REP.
The REP (Read-Eval-Print loop) is a scheme expression stored as a pseudo-symbol (i.e. in utf-8 format, using ".ascii" directives) and named prgstr in aps_init.s. The utf-8 REP is converted to a string using the scheme function "utf8->string" in r7rs_6.7_strings.s which is done by calling "utf2st" in aps_core.s that goes through the pre-entry function "stnd1" (also in in aps_core.s) which, when "utf8->string" is used at the APS prompt would be done automatically (however, not so from within assembly; calling the pre-entry function, if any, has to be done explicitly here). The "stnd1" pre-entry function applies to scheme functions with an argument list of the form: (obj {start} {end}) and sets default values for {start} and {end} if they are not specified. The REP string is parsed by the scheme function "parse" (which does not have a pre-entry function) in r7rs_6.13_input_and_output.s. The REP is then started by evaluating the result via a branch to the scheme evaluator, located at label "eval:" in aps_core.s (one could also use the scheme eval primitive in r7rs_6.12_environment_and_evaluation.s by branching to adr_eval, with sv2 set to either env or scheme_null; here, we save one instruction by branching directly to eval).
Each cpu core running the APS interpreter maintains a Global State Vector that governs some aspects of its operation. The Global State Vector is stored in register s18 (32bit) or x27 (64-bit) of the cpu core and accessible from the Scheme REP through the _GLV function. The vector is built when APS starts (in aps_init.s) and its contents are:
index object 0 scheme interrupt callback function 1 address of above-heap insertion point (pseudo scheme integer) 2 scheme data object received from i2c0 port (unused, null) 3 scheme data object received from i2c1 port (unused, null) 4 scheme default input/output port 5 scheme main system buffer address (MBF, in SDRAM) 6 scheme open file list 7 scheme user environment (top-level) 8 scheme user obarray 9 address of top of lower heap (pseudo scheme integer) 10 address of top of upper heap (pseudo scheme integer) 11 scheme cpu core's ID for user variables 12 library start page address above heap 13 scheme built-in environment 14 library parse mode indicator 1 (for reader) 15 library parse mode indicator 2 (for reader) 16 address of pre-entry function table (0 for 64-bit) 17 scheme built-in obarray 18 address of bottom of lower heap (pseudo scheme integer) 19 unused 20 scheme count of garbage collections 21 scheme read buffer 22 scheme write buffer
Every new symbol parsed by the scheme reader, and the new symbols generated when macros are expanded (for macro hygiene), are placed in the user obarray which is stored above the heap. This leads to changes in the above-heap insertion point at index 1 in the GLV as can be ascertained using: (vector-ref (_GLV) 1). User libraries are also stored above the heap, leading to changes in both the above heap insertion point and the library start page at index 12. When extending the obarray or defining a new library would exceed the available above-heap space, the lower and upper heaps are shrunk and the memory barriers that trigger gc are moved correspondingly. GLV values at indices 1, 9, 10 and 12 all change as a result. The gc counter at index 20 is incremented each time a garbage collection is triggered and can be reset to zero using: (vector-set! (_GLV) 20 0). The read and write buffers at indices 21 and 22 are each 64 KB in size and therefore viewing the contents of the GLV at the REP, using (_GLV), while feasible, may not be the most comfortable approach. The r7rs vector-copy function may be used to truncate the output, for example: (vector-copy (_GLV) 0 21).
ARM registers are renamed within the APS source to better reflect their purpose during normal execution of the interpreter (outside of interrupts and garbage collection). The renaming was designed to be relatively similar between 32-bit and 64-bit chips, and therefore, whereas all 16 integer (general purpose) registers are used in 32-bit systems, not all of the 32 available integer registers are used in 64-bit systems. In both 32-bit and 64-bit cpus, some FPU registers are also used by the APS interpreter. In particular, a few useful floating point constants are permanently stored in selected FPU registers. The main differences between register use in 32-bits and 64-bits are that the _GLV and MBFr are stored in FPU registers in the 32-bit case but are in integer registers in 64-bits, and, constant values of zero, one and the null list are stored in integer registers in 64-bits but not in 32-bits (since no register remained available for this purpose). Also, (evidently) registers are 32-bits wide in 32-bit systems (including single precision FPU registers) and 64-bits wide in 64-bit systems (including double precision FPU registers). The registers for 32-bit systems are used essentially in the same way as in previous versions of the software, except that the APS _GLV is no longer in a general purpose register (formerly: glv) but is now in a FPU register, and the frd register (which stores the address of the next available memory cell in the allocation nursery on Cortex-M devices) replaces that former glv in the integer register file (ARM register r11). For compatibility with 64-bit systems, the scheme continuation register, formerly named cnt, is now named con (there is a cnt instruction in Aarch64 that conflicted with the previous name).
For 32-bit systems, the 16 ARM User Mode registers and the FPU registers, their names within the source code and their uses outside of interrupts, are as follows (see file: aps_constants.s):
ARM APS Name Name Usage Notes ---- ----- ----------------------------------- --------------------------- r0 fre pointer to next free heap cell also mem reservation status r1 con scheme continuation register also named cnt r2 rva raw value work register a not garbage collected r3 rvb raw value work register b not garbage collected r4 sv1 scheme value work register 1 r5 sv2 scheme value work register 2 r6 sv3 scheme value work register 3 r7 sv4 scheme value work register 4 r8 sv5 scheme value work register 5 r9 env pointer to current environment r10 dts pointer to data/return stack r11 frd pointer to next free nursery cell Cortex-M (else tmp val reg) r12 rvc raw value work register c not garbage collected r13 sp system stack pointer ARM sp r14 lnk system link return ARM lr r15 pc system program counter ARM pc ---FPU----(single precision)------------------------------------------------ s0 fpt float value temporary register used for context-switching s1 fp0 float value work register 0 s2 fp1 float value work register 1 s3 fp2 float value work register 2 ---- ----- ----------------------------------- --------------------------- s10 fp10p raw value 10.0 float constant s11 fp1p0 raw value 1.0 float constant s12 fpnan raw value NaN float constant s13 fp0p5 raw value 0.5 float constant s14 fpinf raw value +Inf float constant s15 fpep0 raw value e float constant ---- ----- ----------------------------------- --------------------------- s17 s17 global state vector (_GLV) s18 s18 main buffer root (MBFr) ---- ----- ----------------------------------- ---------------------------
For 64-bit systems, the 32 integer registers and the FPU registers, their names in the source code, and their uses outside of interrupts, are as follows (see: aps_constants_64.s):
ARM APS Name Name Usage Notes ---- ----- ----------------------------------- --------------------------- x0 fre pointer to next free heap cell also mem reservation status x1 con scheme continuation register x2 rva raw value work register a not garbage collected x3 rvb raw value work register b not garbage collected x4 sv1 scheme value work register 1 x5 sv2 scheme value work register 2 x6 sv3 scheme value work register 3 x7 sv4 scheme value work register 4 x8 sv5 scheme value work register 5 x9 env pointer to current environment x10 dts pointer to data/return stack x11 frd temporary value register x12 rvc raw value work register c not garbage collected ---- ----- ----------------------------------- --------------------------- x26 x26 global state vector (_GLV) x27 x27 main buffer root (MBFr) x28 one raw value 1 also sv0 = scheme int 0, i0 x29 nul scheme value () constant x30 lnk system link return ARM lr xzr zro raw value 0 ARM constant ---FPU----(double precision)------------------------------------------------ d0 fp0 float value work register 0 d1 fp1 float value work register 1 d2 fp2 float value work register 2 d3 fpt float value temporary register used for context-switching ---- ----- ----------------------------------- --------------------------- d10 fp10p raw value 10.0 float constant d11 fp1p0 raw value 1.0 float constant d12 fpnan raw value NaN float constant d13 fp0p5 raw value 0.5 float constant d14 fpinf raw value +Inf float constant d15 fpep0 raw value e float constant ---- ----- ----------------------------------- ---------------------------
The internal representation of Scheme objects in the APS interpreter is similar to that used in previous versions. The least-significant bits are typically used as type tags and the representation extends also to 64-bit systems. In 080, the tags used for complex numbers and rationals were changed to be the same as those for floating point numbers and integers, respectively. This makes it easier to extract the components of these compound entities. In 64-bit systems, ARMv8 tagged pointers are used to indicate the type of object referenced by a pointer, which speeds up type analysis and dispatch. Also, in 64-bits, the address of pre-entry functions is stored directly in the tag of primitves that use them, which makes the pre-entry function table not needed. The prepost macro, in aps_macros.s illustrates how the address of a pre-entry function is obtained from the tag of a primitive function in 32-bit and 64-bit systems.
Addresses are aligned to word boundaries and hence natively have their lowest bits as #b00 in 32-bit systems or #b000 in 64-bit. Integers are stored as a raw value in the 30 or 62 bits above their type tag, using two's complement. A raw integer is shifted left by 2 bits and orred with the #b01 type tag to make a scheme integer. The converse operation is performed by an arithmetic shift, two bits to the right, to preserve the sign and value of the raw integer. Floats are represented using a shortened form of the IEEE-754 32-bit standard (single precision) in 32-bit CPUs and IEEE-754 64-bit standard (double precision) in 64-bit chips. In both cases, the lower two bits of the mantissa are replaced by the type tag #b10. The following illustrates these internal representations, with uppercase letters representing nibbles (4-bit items) possibly in hexadecimal form (X if arbitrary, T for pointer tag), lower case letters representing individual bits, and numerical digits representing actual bit values (s, e, E, m and M stand for sign, exponent and mantissa):
32-bit 64-bit object ----------- ---------------------- --------------------------------- XXXXXXXbb00 TTXXXXXXXXXXXXXb000 address, aligned to word boundary XXXXXXXbb01 XXXXXXXXXXXXXXXbb01 scheme integer sEEMMMMMm10 seeeEEMMMMMMMMMMMMmm10 scheme float
Immediates, other than integers and floats, are encoded either as an 8-bit value (eg. an 8-bit tag and no additional value info), or as a tag orr-ed with the object's (shifted) value. Scheme null, #t, #f and broken-hearts are identified as 8-bit objects (eg. valueless tag) #x0F, #x1F, #x2F and #x9F, respectively. Characters are encoded using a 8-bit tag in 32-bit or a 4-bit tag in 64-bit, with their 16-bit unicode value shifted left by 8-bits. Variables are encoded using an 8-bit MCU ID, a 16-bit variable ID and either an 8-bit tag (in 32-bitters) or a 4-bit tag (in 64-bitters). The 8-bit tags for characters and variables are those that were used also in prior versions of the interpreter and may be converged to the 4-bit tags used in 64-bit systems in future versions. Also, the encoding of variables is likely to be modified in future releases so that it extends seamlessly from 32-bit to 64-bits (for example: VRID-MC-tag rather than MC-VRID-tag). In version 080, these 6 immediate objects are encoded as follows:
32-bit 64-bit object ----------- ---------------------- --------------------------------- #x0000000F #x000000000000000F '() scheme null #x0000001F #x000000000000001F #t #x0000002F #x000000000000002F #f #x0000009F #x000000000000009F broken-heart (used during garbage collection) ----------- ---------------------- --------------------------------- #x00CCCC3F #x0000000000CCCC03 character, CCCC = character's 16-bit unicode code MC-VRID-AF 00000000-MC-VRID-07 variable, MC = MCU ID or 0, VRID = 16-bit var ID, #xAF/#x07=tag ----------- ---------------------- ---------------------------------
As introduced in version 060, the addresses used to reference compound objects are aligned to the first cell of a double-word aligned pair of addresses for pairs and lists (ends in #b000 in 32-bits and #b0000 in 64-bits) and to the second word for other compound objects such as rationals and strings (ends in #b100 or #b1000, in 32 and 64-bits, respectively). A pair (or cons cell) is represented as (Z is the nibble of zeros: #b0000):
32-bit pair 64-bit pair heap item location -------------- ---------------------- --------- ------------------- XXXXXXX1000 -> ZZXXXXXXXXXXXXX0000 -> car pair's heap address cdr address + 1 word
In 64-bit systems, ARMv8 pointer tag bits, defined in aps_constants_64.s, are used to help identify compound objects (other than pairs). The 64-bit systems automatically exclude these upper bits when computing addresses:
tag bit object ------- --------------------- 56 bytevector 57 string 58 symbol 59 vector 60 rational 61 complex 62 procedure 63 non-executable procedure (eg. macro)
Rationals and complex numbers are represented by two consecutive words (each word is 32-bits or 64-bits in size), each tagged as a scheme integer (for rationals) or scheme float (for complex numbers), stored on the heap, and pointed to by the address of the second of the two words. The macro "ratcpx" in aps_macros.s is used to check if an object is a rational or complex number by testing that it is an appropriately aligned address and has the correct pointer-tag (64-bits; faster) or in-heap tag (32-bits; slower as it requires reading from memory). The internal representations are:
32-bit object 64-bit object heap content location -------------- ------------------- ---------------------- ----------------------- rational rational numerator (sch. int) address minus 1 word XXXXXXXb100 -> 0001ZXXXXXXXXXXXXX1000 -> denominator (sch. int) rational's heap address -------------- ------------------- ---------------------- ----------------------- complex complex real (scheme float) address minus 1 word XXXXXXXb100 -> 0010ZXXXXXXXXXXXXX1000 -> imag (scheme float) complex's heap address -------------- ------------------- ---------------------- -----------------------
Sized objects (bytevectors, strings, symbols and vectors) are represented by a pointer to the second word of the heap area reserved for the object, which is where the object's data starts. The fisrt word (below the data) contains the object's size as a raw integer followed (in the least significant bits) by a type tag whose upper two bits are #b01. This is such that the object's size can be extracted as a scheme integer by an appropriate shift of the full tag (size + type). Different type tags are used in 32-bit and 64-bit systems, but most of them are 8-bit long. The exception is the 64-bit vector size tag which is encoded directly as a scheme integer, with a 2-bit type tag. In 32-bit, the 8-bit type tag identifies the object type and, if either bit 4, or bit 5, or both, are set then the object's contents do not need to be scanned by gc (i.e. the object is not a vector). In 64-bit, the type of object is to be obtained from the ARMv8 pointer tag. For these systems, bit 1 of the type tag indicates whether the object contents are subjected to gc (it is zero only for a vector) and, for non-vectors, bit 4 indicates the size of items contained by the object (0 for 8-bit items or 1 for 16-bit items) such that the number of words reserved for the object (that need to be copied during gc) can be calculated from the combination of object size and item size (attempts to converge type tags, possibly to #x77, #x5B, #x4B and #x6B, may occur in a future release):
32-bit object heap content 64-bit object heap content location -------------- ----------------- ---------------------- ----------------- --------------------- vector tag: SSSSSS4F vector SSSSSSSSSSSSSSSss01 address - 1 word XXXXXXXb100 -> vector item 0 Z1000XXXXXXXXXXXXX1000 -> vector item 0 vector's heap address vector item 1 vector item 1 address + 1 word vector item 2 vector item 2 address + 2 words ... ... ... -------------- ----------------- ---------------------- ----------------- --------------------- string tag: SSSSSS5F string SSSSSSSSSSSSSS5B address - 1 word XXXXXXXb100 -> unicode chars 0-1 Z0010XXXXXXXXXXXXX1000 -> unicode chars 0-3 string's heap address unicode chars 2-3 unicode chars 4-7 address + 1 word unicode chars 4-5 unicode chars 8-11 address + 2 words ... ... ... -------------- ----------------- ---------------------- ----------------- --------------------- bytevector tag: SSSSSS6F bytevector SSSSSSSSSSSSSS4B address - 1 word XXXXXXXb100 -> octets 0-3 Z0001XXXXXXXXXXXXX1000 -> octets 0-7 bytevector's address octets 4-7 octets 8-15 address + 1 word octets 8-11 octets 16-23 address + 2 words ... ... ... -------------- ----------------- ---------------------- ----------------- --------------------- symbol tag: SSSSSS7F symbol SSSSSSSSSSSSSS4B address - 1 word XXXXXXXb100 -> utf-8 bytes 0-3 Z0100XXXXXXXXXXXXX1000 -> utf-8 bytes 0-7 symbol's heap address utf-8 bytes 4-7 utf-8 bytes 8-15 address + 1 word utf-8 bytes 8-11 utf-8 bytes 16-23 address + 2 words ... ... ... -------------- ----------------- ---------------------- ----------------- ---------------------
In 32-bit and 64-bit, built-in primitive functions and syntax procedures are stored as immediates when they do not use common pre-entry functions. In 32-bits, the 8-bit tag #xdf is used along with 3 bits (nnn) to indicate the number of input arguments (0 if none or listed args), bit 11 (s) indicates whether the object is a syntax procedure (1) or function (0) and the upper 16-bits are the address of the primitive's machine code within APS (which has a maximum size of 64KB). The address is shown as AAAA... in the table below. In 64-bit, the 4-bit tag #x0c is used, followed by 4 bits for the number of input arguments (N), the address of the primitive's machine code is in bits 20 to 61 and bits 62 and 63 are pointer tags (pseudo) indicating that this is a procedure and may be a syntax rather than a function (bit s):
32-bit 64-bit Object -------------- ------------------- --------------------------- AAAAbb01snnnDF s1aaAAAAAAAAAAZZZNC primitive function or macro (direct entry)
When a pre-entry function is used, the 32-bit representation becomes an indirect one, with pointer to machine code and tag, while the 64-bit representation remains an immediate (note that primitives with pre-entry functions are available in the APS interpreter but not in the LSD bootloader because these functions are defined in aps_080.s and downstream code files, not in lsd_080.s). In the 32-bit tag, bit 13 is set and the startup value (for sv4) is stored in bits 16 to 23 while the pre-entry function's index in the pre-entry table is stored in bits 24 to 31. In 64-bits, bit 9 is set, the sv4 startup value is stored in bits 12 to 19, the pre-entry function's address (divided by 4) is stored in bits 20 to 40 (as BBBB... below) and the function's address is stored in bits 41 to 61:
32-bit code space location 64-bit Object ----------- ------------- ------------------ --------------------- ------------------ EESS3snnnDF address - 1 word XXXXXXXb100 -> machine code proc address s1aaAAAAAbBBBBBSS2NC primitive with pre-entry machine code address + 1 word machine code address + 2 words ... ...
Compound procedures (user lambdas), continuations and macros are stored as non-immediates. The 8-bit tag #xdf and 4-bit tag #x0c are used for 32-bit and 64-bit systems, respectively. In 32-bits, bit 11 indicates a macro, bit 14 indicates a lambda and bit 15 indicates a continuation. In 64-bits, bit 7 indicates a macro, bit 10 indicates a lambda and bit 11 indicates a continuation. The base memory footprint of each of these objects is 4 words, and their contents start with a tag and continue with pointers to the object's components, or null. Garbage collection treats them essentially as vectors of size 3, following pointer-links in them to establish the live-set. For future use, setting both the lambda and continuation bits is planned to represent a compiled procedure (a compiler for version 080 may be developed in the future).
32-bit object heap content 64-bit object heap content location -------------- ----------------- ---------------------- ------------------- --------------------- lambda ZZZZ400nnDF lambda ZZZZZZZZZZZZZ400nnC address - 1 word XXXXXXXb100 -> vars list (ptr) 0100ZXXXXXXXXXXXXX1000 -> vars list (ptr) lambda's heap address body (ptr) body (ptr) address + 1 word env (ptr) env (ptr) address + 2 words -------------- ----------------- ---------------------- ------------------- --------------------- continuation ZZZZ800nnDF continuation ZZZZZZZZZZZZZ800nnC address - 1 word XXXXXXXb100 -> winders (ptr) 0100ZXXXXXXXXXXXXX1000 -> winders (ptr) continuation's heap address ret-stack (ptr) ret-stack (ptr) address + 1 word env (ptr) env (ptr) address + 2 words -------------- ----------------- ---------------------- ------------------- --------------------- macro ZZZZZ10nnDF macro ZZZZZZZZZZZZZZ10nnC address - 1 word XXXXXXXb100 -> literals (ptr) 1100ZXXXXXXXXXXXXX1000 -> literals (ptr) macro's heap address body (ptr) body (ptr) address + 1 word () () address + 2 words -------------- ----------------- ---------------------- ------------------- ---------------------
The memory layout of the running APS interpreter is set-up by the LSD bootloader and described in the LSD Implementation page.
Please refer to LSD Implementation (near the bottom of the section on "LSD Scheme Objects") for a description of the implementation of input and output ports.
Please refer to the LSD Implementation section on "Extending the System with New LSD Functions" for an example of adding a new function, available at top-level, in Armpit Scheme (within the LSD sub-system). The same method can be used to add a function within the APS component of the system.