Home |  Overview |  Startup |  State |  Registers  |  Representations |  Memory |  I/O Ports |  Extending


A Scheme Interpreter for ARM Microcontrollers:
APS Interpreter Implementation, Version 080

SourceForge.net Logo
 

Overview:


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

 

Global State Vector:


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

 

Register Usage:


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

Internal Representations:


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

Memory Layout:


The memory layout of the running APS interpreter is set-up by the LSD bootloader and described in the LSD Implementation page.

 

Input and Output Ports:


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.

 

Extending the System with New Functions:


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.


Last updated July 20, 2018

bioe-hubert-at-sourceforge.net