LambLisp 01 Red Fox Alpha
From AI to Actuator
Loading...
Searching...
No Matches
Cell Class Reference

Public Types

Cell type enumeration
Note the following properties of this enumeration:
NIL is a singleton, in the middle of the enumeration.
Types <= NIL are atoms.
Types >= NIL are lists.
Types > NIL are pairs.
Types <= T_PORT_HEAP are heap-allocated objects, requiring specialized finalizing but not specialized marking.
Types <= T_ANY_HEAP_SVEC are heap allocated vectors requiring specialized marking in addition to finalizing.
For Scheme purposes, there is one pair type, but there are several other specialized pair types that facilitate execution.
These additional pair types are atoms, but list operations such as car, cdr, and append can operate on them.
enum  {
  T_SVEC_HEAP =0 , T_SVEC2N_HEAP , T_ANY_HEAP_SVEC = T_SVEC2N_HEAP , T_SYM_HEAP ,
  T_BVEC_HEAP , T_BIGNUM , T_STR_HEAP , T_CPP_HEAP ,
  T_PORT_HEAP , T_NEEDS_FINALIZING = T_PORT_HEAP , T_BVEC_EXT , T_STR_EXT ,
  T_BVEC_IMM , T_STR_IMM , T_GENSYM , T_BOOL ,
  T_CHAR , T_INT32 , T_INT64 , T_FLOAT32 ,
  T_FLOAT64 , T_MOP3_PROC , T_MOP3_NPROC , T_VOID ,
  T_UNDEF , T_NIL , T_COMPLEX , T_RATIONAL ,
  T_PAIR , T_SVEC_IMM , T_PROC , T_NPROC ,
  T_MACRO , T_DICT , T_THUNK_SEXPR , T_THUNK_BODY ,
  T_ERROR , T_VALUES , T_BYTECODE , T_BYTECODE_N ,
  Ntypes
}

Public Member Functions

LL_int32 car_int32 () const
LL_float32 car_float32 () const
LL_int32 cdr_int32 () const
LL_float32 cdr_float32 () const
Sexpr_t mk_error (const char *fmt,...) CHECKPRINTF_pos2
Sexpr_t mk_error (Sexpr_t irritants, const char *fmt,...) CHECKPRINTF_pos3
CharVec_t prechecked_str_ext_get_chars ()
CharVec_t prechecked_str_imm_get_chars ()
void prechecked_bvec_imm_set_length (LL_int32 l)
Charst_t prechecked_gensym_get_chars ()
void prechecked_gensym_get_info (LL_int32 &hsh, Charst_t &chars)
Sexpr_t prechecked_error_get_irritants ()
Sexpr_t prechecked_error_get_str ()
Charst_t prechecked_error_get_chars ()
Static Cell constructors, used only for "well-known" atomic cells (nil, true, false etc).

In general, it is best not to do anything complex at static construction time. There is no guarantee that dependencies will be ready. In particular, use of the terminal may cause the system to crash. Most applications should leave any construction activities to more purposeful code at runtime, using only cells obtained through cons.

 Cell (Word_t typ, Word_t w1, Word_t w2)
 Cell (Word_t typ, Sexpr_t w1, Sexpr_t w2)
Low-level cell field manipulation.

Note that the type field occupies all of byte 0, although not all bits are required. This speeds up the very common type-checking operation, because no mask is required to extract the type bits.

The flags are in byte 1, and a mask operation is required to access the individual flag fields.

void zero ()
LL_int32 type (void)
void type (LL_int32 t)
LL_int32 flags (void)
void flags_set (LL_int32 f)
void flags_clr (LL_int32 f)
void rplaca (Word_t p)
void rplacd (Word_t p)
PAIR subtype accessors – the four subtype bits as pairs use them.

Meaningful for T_PAIR and the slot-typed types (T_RATIONAL, T_COMPLEX) only. When another type starts using the subtype bits it will name its own predicates for them; do not reach for these there.

bool is_slot_typed () const
bool car_is_int () const
bool car_is_float () const
bool cdr_is_int () const
bool cdr_is_float () const
bool car_is_embedded () const
bool cdr_is_embedded () const
bool car_is_ptr () const
bool cdr_is_ptr () const
Cell value extractors, dependent on Cell type.
Ptr_t get_car_addr ()
Word_t get_car (void)
Word_t get_cdr (void)
Bool_t as_Bool_t ()
Char_t as_Char_t ()
ll_codepoint_t as_codepoint ()
LL_int32 as_int32 () const
LL_int64 as_int64 () const
LL_float32 as_float32 () const
LL_float64 as_float64 () const
Ptr_t as_Ptr_t ()
Charst_t as_Charst_t ()
Bytest_t as_Bytest_t ()
CharVec_t as_CharVec_t ()
ByteVec_t as_ByteVec_t ()
Portst_t as_Portst_t ()
Cell hash value

Hashing is used extensively throughout LambLisp. Each Cell has a hash value, calculated as follows:

  • If the Cell is a symbol, the Cell's hash value is the hash value of the symbol.
  • Otherwise, the address of the Cell is hashed, and that is the result.
LL_int32 hash_sexpr (void)
LL_int32 hash_contents (void)
LL_int32 hash (void)
Cell setters converting the C types into S-expressions.
Sexpr_t set (LL_int32 typ, Word_t w1, Word_t w2)
Sexpr_t set (LL_int32 typ, LL_int32 a, Sexpr_t b)
Sexpr_t set (LL_int32 typ, Word_t a, Sexpr_t b)
Sexpr_t set (LL_int32 typ, Sexpr_t a, Sexpr_t b)
Sexpr_t set (Bool_t b)
Sexpr_t set (Char_t c)
Sexpr_t set (LL_int32 i)
Sexpr_t set (LL_int64 v)
Sexpr_t set (LL_float32 r)
Sexpr_t set (LL_float64 d)
Sexpr_t set (LL_float32 r, LL_float32 i)
LL_float32 cpx_real () const
LL_float32 cpx_imag () const
Sexpr_t set (LL_int32 num, LL_int32 den)
void rat_set_car_i32 (LL_int32 v)
void rat_set_car_ptr (Sexpr_t p)
void rat_set_cdr_i32 (LL_int32 v)
void rat_set_cdr_ptr (Sexpr_t p)
LL_int64 int_as_i64 () const
Sexpr_t rat_car_ptr () const
Sexpr_t rat_cdr_ptr () const
bool rat_car_big () const
bool rat_cdr_big () const
bool rat_is_big () const
bool exact_is_big () const
LL_int64 as_numerator () const
LL_int64 as_denominator () const
int exact_sign ()
Sexpr_t set (Port_t &p)
Sexpr_t set (LL_int32 typ, LL_int32 a, Charst_t b)
Sexpr_t set (LL_int32 typ, LL_int32 a, Bytest_t b)
Sexpr_t set (LL_int32 typ, LL_int32 a, CharVec_t b)
Sexpr_t set (LL_int32 typ, LL_int32 a, ByteVec_t b)
Accessors for use when the type is known.

If the cell type is already known, then these accessors can be used to efficiently access the cell contents.

Sexpr_t prechecked_anypair_get_car ()
Sexpr_t prechecked_anypair_get_cdr ()
LL_int32 prechecked_sym_heap_get_hash ()
Charst_t prechecked_sym_heap_get_chars ()
void prechecked_sym_heap_get_info (LL_int32 &hsh, Charst_t &chars)
Accessors for use when the cell type is unverified.

The any and mustbe accessors will perform type checking and throw an error if an improper access is attempted. The coerce operators will perform C coercion on its operand if possible, otherwise throw an error.

Bool_t mustbe_Bool_t ()
Char_t mustbe_Char_t ()
ll_codepoint_t mustbe_codepoint ()
LL_int32 mustbe_int32 ()
LL_float32 mustbe_float32 ()
Sexpr_t mustbe_any_str_t ()
Sexpr_t mustbe_cppobj_t ()
CPPDeleterPtr prechecked_cppobj_get_deleter ()
Ptr_t prechecked_cppobj_get_ptr ()
CPPDeleterPtr any_cppobj_get_deleter ()
Ptr_t any_cppobj_get_ptr ()
void any_cppobj_get_info (CPPDeleterPtr &d, Ptr_t &p)
LL_float32 coerce_float32 ()
LL_float64 coerce_float64 ()
LL_int32 coerce_int32 ()
LL_int64 coerce_int64 ()
int exact_rank () const
int inexact_rank () const
bool is_exact () const
bool is_inexact () const
bool is_complex () const
int num_rank () const
LL_int64 numerator64 () const
LL_int64 denominator64 () const
LL_float64 coerce_cpx_real ()
LL_float64 coerce_cpx_imag ()
Sexpr_t error_get_str ()
Sexpr_t error_get_irritants ()
Charst_t error_get_chars ()
Sexpr_t any_error_get_str ()
Sexpr_t any_error_get_irritants ()
LL_int32 any_sym_get_hash ()
Charst_t any_sym_get_chars ()
void any_sym_get_info (LL_int32 &hsh, Charst_t &chars)
Operations on strings

LambLisp supports several subtypes of strings. At the time of writing, there are heap-allocated strings (read-write), strings whose characters outside of LambLisp's managed memory, (aka external or EXT strings), and short immediate strings that are contained completely within a single cell. Additional subtypes (such as load-on-demand strings) may be added in future.

There are functions of the form any_xxx() that can be used with any subtype, and there are type-specific functions of the form any_xxx_yyy() for use where the type is already known, with yyy being a code hint.

CharVec_t any_str_get_chars ()
LL_int32 any_str_get_length ()
void any_str_get_info (LL_int32 &len, CharVec_t &chars)
void str_invalidate_charlength ()
LL_int32 any_str_get_charlength ()
Operations on vectors and sub-types of vectors

Within the LambLisp virtual machine, a Lisp vector is referred to as svec. This is an array of S-expressions having a fixed dimension. There are also bytevectors; these are an array of bytes, also of fixed dimension.

As with strings, there are several subtypes of S-expression vectors. There is a heap-allocated vector, which may be of any size. There is a second type of heap-allocated vector, that is always sized to be a power of 2. These are provided to support efficient hash tables. There are immediate vectors, which may be of 0, 1, or 2 elements. The 2-element immediate vector can also be used as a hash table. This can reduce search time by half without requiring any heap allocation, at the cost of 1 extra cell allocation.

Bytevectors are also diverse, having heap, external, and immediate variants. Heap bytevectors are allocated, obviously, on the system heap, and the heap space is freed when the bytevector is garbage-collected.

External bytevectors operate on bytes provided externally to LambLisp's memory manager. This space may have been dynamically allocated from outside LambLisp, or may be located in read-only memory. When a C++ object os injected into LambLisp, it can optionally be provided with a garbage collector callback; in that case the external object can be automatically garbage collected when no it's longer used in the Lisp program.

Immediate bytevectors are contained within a LambLisp Cell. The maximum size is limited by the word size of the underlying platform.

Within the LambLisp virtual machine, there are generic functions of the form any_xvec_xxx() that can operate on any xvec (svec or bvec) subtype, as well as type-specific functions of the form xvec_yyy_xxx(), where yyy is a code hint for the cell storage type (heap, immediate, ROM).

void any_svec_get_info (LL_int32 &Nelems, Sexpr_t *&elems)
Sexpr_t * any_svec_get_elems ()
LL_int32 any_bvec_get_length ()
Byte access to STRINGS.

strings are indexed by CHARACTER (R7RS), which is correct but removed the only way to reach the underlying UTF-8 BYTES from Scheme. Both views are wanted – a character index for text, a byte index for encoding work – so the bytevector operations accept a string and expose its bytes. (bytevector-length "<alpha>X") is 3 where (string-length "<alpha>X") is 2, and that is the point, not an inconsistency. bytevector? deliberately still answers #f for a string: R7RS type predicates partition the types, and a string is not a bytevector. What is shared is the OPERATIONS, not the type.

THIS IS AN EXTENSION, NOT R7RS – SAY SO IN ANY DOCUMENTATION. R7RS 6.9 defines the bytevector procedures over bytevectors; a string is a type error, and the standard bridge is string->utf8 / utf8->string, both of which LambLisp already provides. Verified against two independent R7RS implementations rather than assumed:

chibi-scheme (R7RS reference)   guile --r7rs
(bytevector-length "abc")   ->  ERROR            ERROR
(bytevector-u8-ref "abc" 0) ->  ERROR            ERROR
(bytevector-length (string->utf8 "<alpha>X")) -> 3   3
(string-length "<alpha>X")                     -> 2   2

So portable code must use string->utf8, and anything written against this extension will not move to another Scheme. What the extension buys, and the reason it is worth having HERE, is that it is a ZERO-COPY VIEW: string->utf8 allocates a bytevector and copies the bytes, which on a microcontroller with a fixed cell pool is exactly the allocation you were trying to avoid when you asked for the bytes in the first place. The length is strlen – strings here are NUL-terminated UTF-8 byte buffers – so a string containing a NUL byte reports short. That is the same limit string-length has always had on this representation, not a new one introduced here.

ByteVec_t any_bvec_get_elems ()
void any_bvec_get_info (LL_int32 &Nelems, ByteVec_t &elems)
Array element-type tag – HEAP AND EXT SHAPES ONLY. NOT VALID ON T_BVEC_IMM.

These read and write Cell byte 3, which is free on T_BVEC_HEAP / T_BVEC_EXT (car holds the length, cdr the payload pointer) – but on T_BVEC_IMM BYTE 3 IS ELEMENT 0. The immediate layout is [0]=type [1]=flags [2]=element count [3..]=elements, so calling these on an immediate READS A DIMENSION BYTE AS A TYPE, or OVERWRITES the first element with one. There is no type check here to stop you: _contents._byte[3] is taken unconditionally. NOT valid for all bvec subtypes, which is the opposite of the truth for the one subtype where it matters. A PLAIN BYTEVECTOR HAS NO ELEMENT TYPE AT ALL. mk_bytevector() sets no tag; an R7RS bytevector is just bytes. This tag belongs to the ARRAY feature (ll_vm_mop3_extra.cpp), where an array is a SHAPE bytevector plus a DATA bytevector: the shape carries the element type and the dims, the data is untagged. array-type reads it from the shape. HOW THE ARRAY CODE HANDLES THE IMMEDIATE CASE, and copy it if you add a caller: it does NOT use these accessors. When the shape fits immediate (1 + 4*ndim <= sizeof(Cell) - 3) it stores the type as sdata[0] – element 0 of the payload – and puts the dims at sdata+1, sizing the vector with that leading byte included. Only the heap branch calls bvec_set_elem_type(). See ll_vm_mop3_extra.cpp:533, which is the whole convention. Element types are Cell::T_INT32, T_INT64, T_FLOAT32, T_FLOAT64.

LL_int32 bvec_elem_type (void) const
void bvec_set_elem_type (LL_int32 t)
Cell conversions to printable representation

These functions convert a Cell (or parts of a Cell) to a printable representation of the S-expression contents of the Cell. Because environments are often included in the descendants of the Cell being printed, the depth of environment recursiveness is limited.

String cell_name (void)
Charst_t type_name (LL_int32 typ)
Charst_t type_name (void)
Charst_t gcstate_name (void)
String dump ()
String str (Sexpr_t sx, Bool_t as_write_or_display, DatumLabels *lbl, LL_int32 max_depth)
String str (Sexpr_t sx, Bool_t as_write_or_display, LL_int32 env_depth, LL_int32 max_depth)
String str (Bool_t as_write_or_display, LL_int32 env_depth, LL_int32 max_depth)
String str (Bool_t as_write_or_display, LL_int32 env_depth)
String str (Bool_t as_write_or_display)
String str (LL_int32 env_depth)
String str (void)

Static Public Attributes

static const int GC_STATE_MASK = F_GC01 | F_GC02 | F_GC04
Cell flags, including gc states for multi-pass incremental gc, tail marker, and spares.
static const int F_GC01 = 0x01
static const int F_GC02 = 0x02
static const int F_GC04 = 0x04
static const int F_TAIL = 0x08
THE SUBTYPE BITS – flags bits 4-7 (<tt>flags \& 0xF0</tt>).
bit 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+
| subtype bits |TAI| gc state |
+---+---+---+---+---+---+---+---+
0xF0 0x08 0x07

Four bits whose meaning is decided by the cell's top-level type. TODAY ONLY PAIRS USE THEM, as the four booleans below: is the car an embedded int, is it an embedded float, and the same two for the cdr. Other types are expected to use these bits soon; when the second user arrives the shared part can be factored out THEN, from two real cases rather than from one imagined one. There is deliberately no generic subtype accessor yet.

WHY THERE IS NO Wt_ ENUM ANY MORE. These were a 2-bit field per slot with an enum WordType { Wt_ptr=0, Wt_int32=1, Wt_float32=2, Wt_reserved=3 }, and the constants were named F_CAR_WT0 / F_CAR_WT1 for "car word-type bit 0/1". That model was more complicated than the facts: Wt_reserved never occurs, and Wt_ptr is not a stored value at all – it is the ABSENCE of both embedded bits. The names made it worse, because F_CAR_WT0 reads as the VALUE Wt_ptr=0 when it means BIT 0. Four named booleans say the same thing with no enum, no shift, and no unreachable fourth state.

A SLOT WITH NEITHER BIT SET HOLDS A Cell* THAT THE COLLECTOR TRACES. That is the invariant the marker and the write barrier depend on, and it is why tcons() zeroing a recycled cell is correct by construction: a fresh cell's slots start as traced pointers.

THE VALUES ARE BAKED INTO GENERATED MACHINE CODE. The four NCG backends emit literal tests against 0x10 and 0x40 – ll_vm_ncg_x86_64.cpp:1116, ll_vm_ncg_aarch64.cpp:880 (tbnz bit 4), ll_vm_ncg_xtensa.cpp:843, ll_vm_ncg_rv32.cpp:717. Changing a VALUE here silently changes what already-compiled code tests; changing a NAME does not.

static const int F_SUBTYPE_MASK = 0xF0
static const int F_CAR_IS_INT = 0x10
static const int F_CAR_IS_FLOAT = 0x20
static const int F_CDR_IS_INT = 0x40
static const int F_CDR_IS_FLOAT = 0x80

Flag testing & setting for garbage collection and tail recursion.

The garbage collection algorithm is based on the tricolor abstraction described in Dijkstra 1978. The original set of 3 colors was enlarged to 4 with Kung and Song 1977, with their correctness proof of the incremental GC algorithm. In LambLisp, is is useful to have a fifth state, and to think of the state or color as a stage in the Cell life cycle. When GC is in progress, Cells may advance in their life cycle, but may also be moved back to an earlier gc state as the result of an assignment. This enumeration allows some tests to be combined in an inequality rather than a sequence of equality tests or a C switch.

LambLisp* uses a trampoline technique to implement tail recursion. The tail of a series of expressions is the last one evaluated; this is the expression that will return the value of the series.

In the trampoline, instead of evaluating the last expression in the series and returning the value (as in C/C++), the expression is returned unevaluated, with the tail flag set. The evaluator checks every result to see if it is a tail that needs additional evaluation, or is a final result to be returned.

This removes the need for an additional stack frame during recursion.

enum  {
  gcst_idle , gcst_issued , gcst_stacked , gcst_marked ,
  gcst_free , Ngcstates
}
static const int F_BC_VARIADIC = 0x01
LL_int32 gc_state (void)
Sexpr_t gc_state (LL_int32 st)
LL_int32 tail_state (void)
Sexpr_t tail_state_set (void)
Sexpr_t tail_state_clr (void)
bool bytecode_is_variadic () const
void bytecode_set_variadic ()

Cell type testing

The cell type enumeration has been designed to group together cell types according to their most common operations.

The main groups are those needing special sweep and finalizing during garbage collection, those that are treated as pairs, and simple atoms. There is a subgroup of those needing special sweep, that also need specialised marking during the garbage collection mark phase.

There are some types that have optimized subtypes (such as immediate types), and they differ in their processing at GC time. Type-testing functions below have is_any_x() predicates that group all subtypes together (e.g., is_any_str_atom() returns true for any type of string (heap, immediate, external). There are also functions further down that will return the contents of complex cells, and throw an error if called with incorrect type. Therefore it is often not necessary to use the predicates before accessing the atom internals with any_x_get_info().

Note also: it is sometimes faster to check the types directly, rather than check the cell feature table. Preliminary testing indicates more than 2 type tests should use the table instead.

static const CellFeatures features [Ntypes]
void init_static_data ()
Bool_t is_atom (void)
Bool_t is_pair (void)
Bool_t is_any_pair (void)
Bool_t is_any_svec_atom ()
Bool_t is_any_svec2n_atom ()
Bool_t is_any_str_atom (void)
Bool_t is_any_sym_atom (void)
Bool_t is_any_bvec_atom (void)
Bool_t is_any_int (void)
Bool_t is_integer_valued (void)
Bool_t is_bignum (void)
Bool_t is_any_real (void)

T_STR_HEAP payload block.

The cdr points at a BLOCK, not at the characters:

block:  [ LL_int32 character length ][ k bytes of UTF-8 ][ NUL ]
        ^ base == cdr                 ^ chars == base + LL_STR_HDR

The BYTE length lives in the car, exactly as a bytevector keeps its own, so the zero-copy byte view of a string reads it from the cell with no dereference. The CHARACTER length lives at the head of the block because it is a property of the CONTENT: a grow or shrink rewrites it inside the block it is already touching, and a realloc carries it along, so there is no second place to forget. Both are full LL_int32 – there is no ceiling on either.

PRINTING IS A KNOWN, ACCEPTED DEVIATION, and it is NOT a bug to re-file. A string may CONTAIN a NUL: both lengths are stored, so string-length, string-ref, substring and the byte view all report the truth. But the payload is kept NUL-TERMINATED so a string can still be handed to C as a char * at no cost, and every consumer of that pointer stops at the first NUL. So such a string reports its true length and PRINTS SHORT – through Cell::str(), display, write, the text of an error message, and any %s logging. That is the deliberate trade for a C-compatible buffer, not an oversight, and the manual records it too ("A string may contain NUL, and string-length is the authority"). If you are here because a string measured 6 and printed 3, this is why; the defect would be a length that lies, and it does not.

FREE THE CDR, NEVER THE CHARS. get_chars() returns an INTERIOR pointer now. Handing it to ll_payload_free() frees base+LL_STR_HDR, which is heap corruption with no symptom at the call site. Every free path takes get_cdr() – the raw slot, the pointer payload_alloc actually returned – so there is no accessor in a free path that could return an interior pointer.

static const int LL_STR_HDR = (int) sizeof(LL_int32)
Ptr_t prechecked_str_heap_get_block ()
CharVec_t prechecked_str_heap_get_chars ()
LL_int32 prechecked_str_heap_get_charlength ()
void prechecked_str_heap_set_charlength (LL_int32 n)

Detailed Description

The Cell class is the foundational class for the LambLisp Virtual Machine.

Nearly all the Cell methods are inline for performance. A Cell has only getters and setters; there are no other side effects. Cell fields are changed only through explicit requests, and not as the result of any other mutations. This means that Cells do not participate in garbage collection, which is managed from outside the Cell class. Indeed, there is no concept within the Cell that there might be a plurality of them; only the behavior of a single Cell is defined.

A Cell may be one of several types. Historically, the number of types varied with the variant of Lisp. In LambLisp, there are Cell types that correspond directly to the types described in the Scheme RxRS specifications (integers, procedures etc), and there are additional types that implement underlying behavior to support the higher-level language (thunks, dictionaries).

For example, LambLisp supports several types of strings internally, depending on whether the character are stored on the heap, in externally-provided memory, or immediately within the cell (for fast operations on short strings). Each of these string types is compatible with the string type in the Scheme RxRS specifications. Likewise, bytevectors have external and immediate variants.

LambLisp also supports specialized pair types, such as the LambLisp dictionary. These act as Lisp pairs for purposes of allocation and garbage collection, but their specialized operators are executed by the underlying LambLisp virtual machine and therefore operate at C++ speed.

The Cell type enumeration is purposefully ordered in such a way as to allow efficient integer comparisons instead of a C switch in most cases, enhancing runtime performance as well as garbage collection.

Cell enumeration characteristics:
Cells requiring submarking
Cells requiring finalizing
Simple atoms: immediate cell types like bool char int real etc
External (non-GC) objects and pointers to native C++ code
Cells that point to C++-allocated objects, paired with optional GC finalizers.
"well-known singleton atoms" such as NIL, undefined, and void types.
Pair - the Cell type that responds to the Lisp pair? predicate.
Other pairs - specialized pair types

With this ordering of Cell types, the solution to many common cases during expression evaluation can be obtained with an inequality, rather than a C++ switch statement. This provides a significant performance boost, because the most common cases are checked first, and the total number of cases is reduced.

Cell optimized type tests
(Types < "simple atoms") need specialized GC marking and heap reclamation.
(Types <= "cells requiring finalizing") need specialized GC heap reclamation.
(Types >= T_NIL) are lists.
(Types > T_NIL) are pairs (but not only Lisp pairs, extended pairs too).
(Types != T_PAIR) are atoms, including numbers, strings, vectors, and singletons such as NIL.
(Types > T_PAIR) are extended pairs, used for specialized lists known to LambVM, but receiving regular pair GC processing.

There are cases where the ordering does not help so much. For example the LambLisp type system allows for several types of strings. Because the type system is ordered according to GC requirements, these are not adjacent in the enumeration and less amenable to inequality tests. For these cases, there is a cell feature matrix available to be queried by type and feature. This allows the determination to be made with an array lookup, which is faster than several sequential tests or a C switch, putting a permanent cap on the cost of a type check. It also allows other details such as is-immediate? to be implemented inexpensively.

The garbage collector states are ordered for similar reasons. See the garbage collector chapter for details on the Cell life cycle.

Member Enumeration Documentation

◆ anonymous enum

anonymous enum

How to read the (car cdr) note on each type below. It names what the two slots HOLD. Sexpr_t means a reference to another cell, which the collector traverses. A named scalar type – LL_int32, Charst_t, Bytest_t – means the slot holds that value directly, with no cell of its own. reserved means the slot is unused. wide means the value is 8 bytes and occupies BOTH slots where a machine word is 4 bytes. slot-typed means the slot may be either: an embedded scalar or a reference, decided per slot at run time and recorded in the cell's flags – ask car_is_ptr() / car_is_embedded() rather than assuming.

A T_PAIR is slot-typed too: cons packs a small integer or float straight into the slot instead of allocating a cell for it, and Lamb::car() / Lamb::cdr() build the number back when you read it. This is invisible from Scheme and matters in C++ only if you reach past those accessors into the raw words.

Enumerator
T_SVEC_HEAP 

(LL_int32 Sexpr_t[]) A vector of Sexprs is a (len Sexpr_t*) pair.

READING (type 0x00 T_SVEC_HEAP) (flags 0x00) (gcstate 0x00 GC_IDLE) (b2 0x00) (b3 0x00) AS A WIPED HEADER IS A MISTAKE. Those five zeros are the NORMAL, HEALTHY values for a live vector: enum 0 is the type, a vector carries no flags, gcst_idle is 0, and b2/b3 belong to bvecs. The fields that say whether the cell is intact are the two WORDS, because T_SVEC_HEAP is (len, Sexpr_t*) – so (w1 0x08) (w2 0xaaab024830c0) is an 8-element vector pointing at its element array, not corruption.

A genuinely zeroed cell has w1 and w2 ZERO TOO (Cell::zero() clears all three words); if the words hold plausible values the cell is fine and the CALLER is wrong to walk it.

WHAT THE DUMP ACTUALLY MEANS, then: something called car/cdr on a vector. T_SVEC_HEAP sits below T_PAIR, so is_any_pair() is false and the raise is correct behaviour. Look at the caller, and at where it got the pointer – a stale pointer that lands on a recycled cell raises only when that cell happens to be a vector, and silently returns garbage when it happens to be a pair. The raise is the lucky case.

T_SVEC2N_HEAP 

(LL_int32 Sexpr_t[]) Same as vector, but length must be a power of 2; useful for hash tables.

T_ANY_HEAP_SVEC 

Any types less than or equal to this are heap-stored vectors of S-expressions. They need specialized marking at GC time.

T_SYM_HEAP 

(LL_int32 Charst_t) Symbol is a (hash char*) pair.

T_BVEC_HEAP 

(LL_int32 Bytest_t) Bytevector is a (len byte*) pair.

T_BIGNUM 

(LL_int32 Bytest_t) Arbitrary-precision integer; same layout as T_BVEC_HEAP: car=byte-length, cdr=ptr to payload [sign_nlimbs int32][limb0 LL_int32]...

T_STR_HEAP 

(LL_int32 Charst_t) car=BYTE length, cdr=ptr to a payload allocated len+1 and ZEROED – EXACTLY the shape of T_BVEC_HEAP above, which is the physical half of "a string is a bytevector". The car IS maintained at every site that builds or resizes a string; what is missing is a READER – any_str_get_length() still calls strlen(), so string-length scans the buffer twice while the byte length sits here. The CHARACTER length is genuinely absent.

T_CPP_HEAP 

(ptr-to-cpp-deleter ptr-to-cpp-object) Deleter is a function void f(void *cpp_obj) of the appropriate type T of the C++ object, and performs delete (T *) cpp_obj;

T_PORT_HEAP 

(reserved Ptr_t) car is unused, cdr is ptr to underlying C++ port instance

T_NEEDS_FINALIZING 

Any types less than or equal to this have data stored in the heap, and need specialized finalizing at GC time.

T_BVEC_EXT 

(LL_int32 Bytest_t) Same as T_BYTEVEC but externally allocated; the byte array is not freed at GC time.

T_STR_EXT 

(reserved Charst_t) Same as T_STR_HEAP but externally allocated; the character array is not freed at GC time. UNLIKE T_STR_HEAP the car really is unused here – OBJ_SYS_STR (ll_vm_cell.cpp:23) is built with car=0 – so an external string's length comes from strlen and nowhere else.

T_BVEC_IMM 

Special format: type and flag bytes as usual, byte 2 is vector length, remaining bytes are vector elements.

T_STR_IMM 

Special format: type and flag bytes as usual, remaining bytes are 0-terminated string embedded in the cell.

T_GENSYM 

Runtime symbol generation with no heap operations.

T_BOOL 

(Bool_t reserved) Boolean atom

T_CHAR 

(Char_t reserved) Character atom

T_INT32 

(LL_int32 reserved) Exact 32-bit integer; value in car via memcpy(4).

T_INT64 

(LL_int64 wide) Exact 64-bit integer; 8-byte value at &car (spans car+cdr on ESP32). GC-safe: type < T_NIL.

T_FLOAT32 

(LL_float32 reserved) Inexact 32-bit LL_float32; value in car via memcpy(4).

T_FLOAT64 

(LL_float64 wide) Inexact 64-bit LL_float64; 8-byte value at &car (spans car+cdr on ESP32). GC-safe: type < T_NIL.

T_MOP3_PROC 

(reserved *Mop3st_t) pointer to native function - args are evaluated before calling

T_MOP3_NPROC 

(reserved *Mop3st_t) pointer to native macro processor - args are not evaluated before calling

T_VOID 

(don't care) VOID has its own type and a singleton instance OBJ_VOID

T_UNDEF 

(ERROR ERROR) UNDEF has its own type and a singleton instance OBJ_UNDEF

T_NIL 
  • The NIL singleton is both a list and an atom, but not a pair.
  • Types <= T_NIL are atoms, types >= T_NIL are lists, types > T_NIL are pairs, but only T_PAIR responds to the Scheme pair? predicate. (ERROR ERROR) NIL has its own type and a singleton instance.
T_COMPLEX 

(slot-typed) Complex number atom; car=real part, cdr=imaginary part. EACH SLOT is an embedded LL_int32/LL_float32 OR a reference to another number cell, so the collector DOES traverse the slots – a complex is an atom to your program but not to the collector.

The type order is meaningful. Every type ordered ABOVE T_NIL carries two slots that the garbage collector traverses; every type ordered at or above T_PAIR is a pair to Scheme. T_COMPLEX and T_RATIONAL sit between the two: their parts are traced by the collector, but they remain NUMBERS to your program, so (car 1/2) raises an error rather than returning 1 – the same answer a full Scheme such as Chez gives.

T_RATIONAL 

(slot-typed) Exact rational atom; car=numerator, cdr=denominator (>=2, GCD-reduced). EACH SLOT is an embedded LL_int32 OR a traced pointer to a boxed T_INT32/T_INT64/T_BIGNUM – so ALONE among the types below T_PAIR, the GC DOES scan it; see the T_RATIONAL case in mark_one_quantum().

T_PAIR 

(slot Sexpr_t) Normal cons cell. EACH SLOT holds a reference to another cell OR an embedded LL_int32 or LL_float32 – cons packs small numbers directly into the slot rather than allocating a cell for them, and Lamb::car()/cdr() materialise them again on the way out. Types below T_PAIR are atoms.

  • Pair types. Types > NIL and types >= T_PAIR are pairs, but only T_PAIR responds to Scheme (pair?).
  • All pair types can be garbage collected without type-specific GC marking or finalizing.
T_SVEC_IMM 

(Sexpr_t Sexpr_t) Vector of 0, 1 or 2 elements.

T_PROC 

(Sexpr_t Sexpr_t) Procedure pair; car is lambda (formals + body containing free variables), cdr is environment.

T_NPROC 

(Sexpr_t Sexpr_t) Non-evaluating procedure pair; car is nlambda (formals + body containing free variables), cdr is environment.

T_MACRO 

(Sexpr_t Sexpr_t) Macro; car is transformer (proc of 1 arg) and cdr is env.

T_DICT 

(Sexpr_t Sexpr_t) Dictionary pair; car is local frame, cdr is list of parent frames, used for environments & object instances.

T_THUNK_SEXPR 

(Sexpr_t Sexpr_t) S-expression thunk pair; car is sexpr, cdr is environment with all variable bindings.

T_THUNK_BODY 

(Sexpr_t Sexpr_t) Code body thunk pair; car is body (i.e., list of sexprs), cdr is environment with all variable bindings.

T_ERROR 

(Sexpr_t Sexpr_t) An error cell; car is a T_STRING, cdr is a pointer to irritants.

T_VALUES 

(Sexpr_t Sexpr_t) Multiple return values; car is first value, cdr is list of remaining values.

T_BYTECODE 

(Sexpr_t Sexpr_t) Compiled bytecode procedure; car is T_SVEC_HEAP[bvec,arity,maxloc,maxstk,const...], cdr is closure env.

T_BYTECODE_N 

(Sexpr_t Sexpr_t) Compiled bytecode nlambda (non-evaluating); same layout as T_BYTECODE, but caller does NOT evaluate arguments.

Ntypes 

Number of LambLisp virtual machine types.

Member Function Documentation

◆ zero()

void Cell::zero ( )
inline

Set all cell bits to zero.

◆ type() [1/2]

LL_int32 Cell::type ( void )
inline

Return the type of the cell as a small integer.

◆ type() [2/2]

void Cell::type ( LL_int32 t)
inline

Set the type of this cell.

◆ flags()

LL_int32 Cell::flags ( void )
inline

Return the entire set of cell flags.

◆ flags_set()

void Cell::flags_set ( LL_int32 f)
inline

Set the selected flags.

◆ flags_clr()

void Cell::flags_clr ( LL_int32 f)
inline

Clear the selected flags.

◆ rplaca()

void Cell::rplaca ( Word_t p)
inline

Replace the cell car field. Called rplaca for historical reasons, and to distinguish it from set-car!, which must respect the GC flags.

◆ rplacd()

void Cell::rplacd ( Word_t p)
inline

Replace the cell cdr field. Called rplacd for historical reasons, and to distinguish it from set-cdr!, which must respect the GC flags.

◆ is_atom()

Bool_t Cell::is_atom ( void )
inline

The type order is meaningful: see the note in the type enumeration above.

True when the cell is an ATOM – anything that is not a pair. Numbers, symbols, characters, strings, bytevectors and the empty list are atoms. So are exact rationals and complex numbers: the garbage collector traverses their internal parts, but your program sees each as a single numeric value, so car and cdr reject them as they would any other number.

◆ is_pair()

Bool_t Cell::is_pair ( void )
inline

Return true if the cell is a cons pair.

◆ is_any_pair()

Bool_t Cell::is_any_pair ( void )
inline

True for every cell whose car and cdr hold Scheme values – not just T_PAIR but procedures, dictionaries, thunks, errors, bytecode and the rest of the pair region. This is the question car and cdr ask. It is NOT the collector's question: see is_slot_typed().

◆ is_any_svec_atom()

Bool_t Cell::is_any_svec_atom ( )
inline

Return true if the cell is any kind of Sexpr_t vector.

◆ is_any_svec2n_atom()

Bool_t Cell::is_any_svec2n_atom ( )
inline

Return true if the cell is Sexpr_t vector of size 2^n.

◆ is_any_str_atom()

Bool_t Cell::is_any_str_atom ( void )
inline

Return true if the cell is any kind of string.

◆ is_any_sym_atom()

Bool_t Cell::is_any_sym_atom ( void )
inline

Return true if the cell is any kind of symbol.

◆ is_any_bvec_atom()

Bool_t Cell::is_any_bvec_atom ( void )
inline

Return true if the cell is any kind of bytevector.

◆ is_any_int()

Bool_t Cell::is_any_int ( void )
inline

Return true if the cell is any exact integer type (T_INT32, T_INT64, or T_BIGNUM).

◆ is_integer_valued()

Bool_t Cell::is_integer_valued ( void )

does this cell's VALUE denote an integer? This is what R7RS integer? asks, and it is NOT is_any_int(): exactness is a different question, so (integer? 2.0) is #t. THE SINGLE IMPLEMENTATION for all three execution paths – mop3_integer_q (interpreter), BC_INTEGER_P (bytecode) and ncg_integer_p (native). They each had their own answer and all three disagreed; keep it that way and they will drift again. Defined in ll_vm_mop3_rxrs.cpp, beside the interpreter entry point it was extracted from.

◆ is_bignum()

Bool_t Cell::is_bignum ( void )
inline

Return true if the cell is T_BIGNUM.

◆ is_any_real()

Bool_t Cell::is_any_real ( void )
inline

Return true if the cell is any inexact real type (T_FLOAT32 or T_FLOAT64).

◆ is_slot_typed()

bool Cell::is_slot_typed ( ) const
inline

True when the collector traverses this cell's two slots.

ASK THIS, NOT is_any_pair(), WHEN YOU ARE REASONING ABOUT THE COLLECTOR. The two questions look alike and are not the same:

  • is_any_pair() asks whether Scheme sees a pair – what car and cdr will accept.
  • is_slot_typed() asks whether the garbage collector follows what is in the two slots.

Exact rationals and complex numbers answer #f to the first and true to the second: your program sees a single number, while the collector traces the parts it is built from. If you write a mop3 that stores a cell reference into a slot, this is the predicate that tells you whether that reference will be traced.

◆ car_is_int()

bool Cell::car_is_int ( ) const
inline

True when the car slot holds an embedded LL_int32.

◆ car_is_float()

bool Cell::car_is_float ( ) const
inline

True when the car slot holds an embedded LL_float32.

◆ cdr_is_int()

bool Cell::cdr_is_int ( ) const
inline

True when the cdr slot holds an embedded LL_int32.

◆ cdr_is_float()

bool Cell::cdr_is_float ( ) const
inline

True when the cdr slot holds an embedded LL_float32.

◆ car_is_embedded()

bool Cell::car_is_embedded ( ) const
inline

True when the car holds an embedded value of either kind (one bitwise op).

◆ cdr_is_embedded()

bool Cell::cdr_is_embedded ( ) const
inline

True when the cdr holds an embedded value of either kind (one bitwise op).

◆ car_is_ptr()

bool Cell::car_is_ptr ( ) const
inline

A slot with NEITHER embedded bit set holds a Cell pointer the collector traces. "Is a pointer" is the absence of the two bits, not a value stored anywhere – which is why a zeroed cell is correct by construction.

◆ car_int32()

LL_int32 Cell::car_int32 ( ) const
inline

Car word as a signed int32. ONLY meaningful when car_is_int(); on any other slot this returns the bit pattern of whatever is there, including a pointer.

REMOVED – is_any_embedded(), which was bool is_any_embedded() const { return (flags & 0xF0) != 0; } It tested the WHOLE subtype nibble with NO TYPE GUARD and answered "a car or cdr holds an embedded value". That reading is only true for T_PAIR and the slot-typed types. It had NO CALLERS, which is the only reason it was ever harmless – and the moment any other type stores a subtype value it would have started answering that about, say, a string. If a whole-nibble test is ever wanted again, write it with a type guard, or use subtype() and compare against the value THAT type defines. Deleted rather than fixed because a predicate whose name is right for exactly one type is a trap regardless of its body.

◆ car_float32()

LL_float32 Cell::car_float32 ( ) const
inline

Car word as a float32. ONLY meaningful when car_is_float().

◆ cdr_int32()

LL_int32 Cell::cdr_int32 ( ) const
inline

Cdr word as a signed int32. Same caution as car_int32().

◆ cdr_float32()

LL_float32 Cell::cdr_float32 ( ) const
inline

Cdr word as a float32. Same caution as car_float32().

◆ gc_state() [1/2]

LL_int32 Cell::gc_state ( void )
inline

Return the garbage-collection state of this cell.

◆ gc_state() [2/2]

Sexpr_t Cell::gc_state ( LL_int32 st)
inline

Set the garbage collection state of this cell, and return the cell.

◆ tail_state()

LL_int32 Cell::tail_state ( void )
inline

Return the tail-call state of this cell.

◆ tail_state_set()

Sexpr_t Cell::tail_state_set ( void )
inline

Set the tail state flag on this cell, and return the cell.

◆ tail_state_clr()

Sexpr_t Cell::tail_state_clr ( void )
inline

Clear the tail state flag on this cell, and return the cell.

◆ bytecode_is_variadic()

bool Cell::bytecode_is_variadic ( ) const
inline

Ph9: true if T_BYTECODE with rest-arg.

◆ bytecode_set_variadic()

void Cell::bytecode_set_variadic ( )
inline

Ph9: mark T_BYTECODE as variadic.

◆ get_car_addr()

Ptr_t Cell::get_car_addr ( )
inline

Return the address of the cell car.

◆ get_car()

Word_t Cell::get_car ( void )
inline

Return the value of the cell car.

◆ get_cdr()

Word_t Cell::get_cdr ( void )
inline

Return the value of the cell cdr.

◆ as_Bool_t()

Bool_t Cell::as_Bool_t ( )
inline

Return the value of this cell as a boolean.

Reading a cell's value – four prefixes, and the difference matters

The accessors come in four families. Choosing the wrong one is the commonest way to write a mop3 that works on your bench and fails on a customer's data.

as_X() Reads the bits and TRUSTS YOU. No type check. If the cell is not the type you assumed, you get a meaningless value rather than an error – an integer made from a pointer, a float made from a length. Use it only where the type is already established. mustbe_X() CHECKS the type and raises a LambLisp error if it is wrong. This is what a procedure should call on its arguments: the error names the offending value and reaches the user as a normal Scheme error rather than a crash. prechecked_X() Unchecked, and named so you can see at a glance that the check was done elsewhere – typically by a mustbe_ or a predicate a line or two earlier. any_X_get_*() The safe compound: checks, then reads. Usually what you want.

The rule of thumb: validate arguments with mustbe_, then work with prechecked_ or the raw accessors. Reach for as_ only inside code that has just tested the type itself.

◆ as_Char_t()

Char_t Cell::as_Char_t ( )
inline

Return the value of this cell as a character.

◆ as_codepoint()

ll_codepoint_t Cell::as_codepoint ( )
inline

Return full Unicode codepoint stored in T_CHAR cell.

◆ as_int32()

LL_int32 Cell::as_int32 ( ) const
inline

T_INT32 cell value.

◆ as_int64()

LL_int64 Cell::as_int64 ( ) const
inline

T_INT64 cell value; spans car+cdr on ESP32.

◆ as_float32()

LL_float32 Cell::as_float32 ( ) const
inline

T_FLOAT32 cell value.

◆ as_float64()

LL_float64 Cell::as_float64 ( ) const
inline

T_FLOAT64 cell value; spans car+cdr on ESP32.

◆ as_Ptr_t()

Ptr_t Cell::as_Ptr_t ( )
inline

Return the value of this cell as a generic pointer (i.e., void*).

◆ as_Charst_t()

Charst_t Cell::as_Charst_t ( )
inline

Return the value of this cell as a pointer to a zero-terminated character array.

◆ as_Bytest_t()

Bytest_t Cell::as_Bytest_t ( )
inline

Return the value of this cell as a pointer to an array of bytes.

◆ as_CharVec_t()

CharVec_t Cell::as_CharVec_t ( )
inline

Return the value of this cell as a pointer to a zero-terminated character array.

◆ as_ByteVec_t()

ByteVec_t Cell::as_ByteVec_t ( )
inline

Return the value of this cell as a pointer to an array of bytes.

◆ as_Portst_t()

Portst_t Cell::as_Portst_t ( )
inline

Return the value of this cell as a pointer to an instance of the system underlying "port" implementation.

◆ hash_sexpr()

LL_int32 Cell::hash_sexpr ( void )

For symbols, the hash of its characters; for numbers, the hash of the number; otherwise the hash of the S-expression itself (i.e., the address of a cell).

◆ hash_contents()

LL_int32 Cell::hash_contents ( void )

For symbols, the hash of its characters; for numbers, strings, vectors, and bytevectors, the hash of the contents; otherwise the hash of the S-expression itself.

◆ hash()

LL_int32 Cell::hash ( void )
inline

Return the hash value of this cell.

◆ set() [1/17]

Sexpr_t Cell::set ( LL_int32 typ,
Word_t w1,
Word_t w2 )
inline

This is the lowest-level generic "set" function.

◆ set() [2/17]

Sexpr_t Cell::set ( LL_int32 typ,
LL_int32 a,
Sexpr_t b )
inline

Convenience: car is a raw (sign-extended) int32. ATOMS only – never a pair type (a pair car must be a full pointer; see the Word_t/Sexpr_t overload below, added for ).

◆ set() [3/17]

Sexpr_t Cell::set ( LL_int32 typ,
Word_t a,
Sexpr_t b )
inline

a (Word_t,Sexpr_t) call formerly resolved to the (LL_int32,Sexpr_t) overload and SIGN-TRUNCATED the car (e.g. expand()'s set(T_PAIR,(Word_t)NIL,free_list_head) stored (LL_int32)NIL). This full-width overload wins that resolution.

◆ set() [4/17]

Sexpr_t Cell::set ( LL_int32 typ,
Sexpr_t a,
Sexpr_t b )
inline

This is a convenience function for common cases.

◆ set() [5/17]

Sexpr_t Cell::set ( Bool_t b)
inline

Set cell as boolean.

◆ set() [6/17]

Sexpr_t Cell::set ( Char_t c)
inline

Set cell as character.

◆ set() [7/17]

Sexpr_t Cell::set ( LL_int32 i)
inline

Set cell as T_INT32.

◆ set() [8/17]

Sexpr_t Cell::set ( LL_int64 v)
inline

Set cell as T_INT64 (8 bytes at &car; spans cdr on ESP32).

◆ set() [9/17]

Sexpr_t Cell::set ( LL_float32 r)
inline

Set cell as T_FLOAT32.

◆ set() [10/17]

Sexpr_t Cell::set ( LL_float64 d)
inline

Set cell as T_FLOAT64 (8 bytes at &car; spans cdr on ESP32)

◆ set() [11/17]

Sexpr_t Cell::set ( LL_float32 r,
LL_float32 i )
inline

Set cell as T_COMPLEX (single precision).

A complex number's two slots each hold either an EMBEDDED int32/float32 or a REFERENCE to another number cell, and the collector traces the references. If you add a setter that stores a reference, it must clear that slot's word-type bits so the slot is marked as a reference – rat_set_car_ptr() is the model. A slot whose word type still says "embedded" is never traced, and its target is collected while you are still using it.

SETTING THE WORD TYPES IS PART OF WRITING A SLOT, not an optimisation. A recycled cell arrives with its slots marked as references, so a setter that writes a raw value and leaves the word type alone hands the collector a float bit pattern to follow as if it were an address. Set the word type in the same expression that writes the value, as the setters here and the T_RATIONAL setter below both do. The low nibble carries GC state and is preserved.

< both slots an embedded float32 (bit1 set, bit0 clear)

◆ cpx_real()

LL_float32 Cell::cpx_real ( ) const
inline

Real part of a T_COMPLEX cell.

◆ cpx_imag()

LL_float32 Cell::cpx_imag ( ) const
inline

Imaginary part of a T_COMPLEX cell.

◆ set() [12/17]

Sexpr_t Cell::set ( LL_int32 num,
LL_int32 den )
inline

< both slots an embedded int32 (preserve gc_state/F_TAIL)

◆ rat_set_car_i32()

void Cell::rat_set_car_i32 ( LL_int32 v)
inline

embedded int32 numerator (embedded)

◆ rat_set_car_ptr()

void Cell::rat_set_car_ptr ( Sexpr_t p)
inline

boxed-int numerator ptr (a traced pointer)

RAW SLOT WRITE – the embed-vs-link DECISION belongs to the caller, and both callers make it: Lamb::rat_store_i64() embeds when the value fits int32, and Lamb::rat_store_cell() embeds a T_INT32. Do not add a fits-in-int32 test here; it would duplicate that decision in a third place and the three would drift.

◆ rat_set_cdr_i32()

void Cell::rat_set_cdr_i32 ( LL_int32 v)
inline

embedded int32 denominator (embedded)

◆ rat_set_cdr_ptr()

void Cell::rat_set_cdr_ptr ( Sexpr_t p)
inline

boxed-int denominator ptr (a traced pointer)

◆ int_as_i64()

LL_int64 Cell::int_as_i64 ( ) const
inline

value of a boxed T_INT32/T_INT64 (NOT T_BIGNUM)

◆ rat_car_ptr()

Sexpr_t Cell::rat_car_ptr ( ) const
inline

boxed numerator cell (valid when car_is_ptr())

◆ rat_cdr_ptr()

Sexpr_t Cell::rat_cdr_ptr ( ) const
inline

boxed denominator cell (valid when cdr_is_ptr())

◆ rat_is_big()

bool Cell::rat_is_big ( ) const
inline

a component is a T_BIGNUM

◆ exact_is_big()

bool Cell::exact_is_big ( ) const
inline

does not fit the int64 fast path

◆ as_numerator()

LL_int64 Cell::as_numerator ( ) const
inline

Numerator as int64 (guard rat_is_big()).

◆ as_denominator()

LL_int64 Cell::as_denominator ( ) const
inline

Denominator as int64 (guard rat_is_big()).

◆ exact_sign()

int Cell::exact_sign ( )
inline

Sign (-1/0/+1) of any exact number; for a rational, the numerator's sign (denominator is positive).

◆ set() [13/17]

Sexpr_t Cell::set ( Port_t & p)
inline

Set cell as port.

◆ set() [14/17]

Sexpr_t Cell::set ( LL_int32 typ,
LL_int32 a,
Charst_t b )
inline

Set cell as a immutable string.

◆ set() [15/17]

Sexpr_t Cell::set ( LL_int32 typ,
LL_int32 a,
Bytest_t b )
inline

Set cell as a immutable bytevector.

◆ set() [16/17]

Sexpr_t Cell::set ( LL_int32 typ,
LL_int32 a,
CharVec_t b )
inline

Set cell as a mutable string.

◆ set() [17/17]

Sexpr_t Cell::set ( LL_int32 typ,
LL_int32 a,
ByteVec_t b )
inline

Set cell as a mutable bytevector.

◆ mk_error() [1/2]

Sexpr_t Cell::mk_error ( const char * fmt,
... )

Fill and return OBJ_SYSERROR, the ONE statically allocated error cell. Allocates nothing, so it still works when the heap cannot – but only one exists, so a second call overwrites the first. For an error you may hold alongside others, use Lamb::mk_error().

◆ mk_error() [2/2]

Sexpr_t Cell::mk_error ( Sexpr_t irritants,
const char * fmt,
... )

As above, with an irritants list. Same single shared cell.

◆ prechecked_str_heap_get_block()

Ptr_t Cell::prechecked_str_heap_get_block ( )
inline

The BLOCK base == the cdr == what payload_alloc returned. This is the only thing that may be freed.

◆ prechecked_str_heap_get_chars()

CharVec_t Cell::prechecked_str_heap_get_chars ( )
inline

The characters, LL_STR_HDR bytes into the block. INTERIOR POINTER – never free this.

◆ prechecked_str_heap_get_charlength()

LL_int32 Cell::prechecked_str_heap_get_charlength ( )
inline

Character count, read from the block head.

◆ prechecked_str_heap_set_charlength()

void Cell::prechecked_str_heap_set_charlength ( LL_int32 n)
inline

Write the character count into the block head.

◆ prechecked_str_ext_get_chars()

CharVec_t Cell::prechecked_str_ext_get_chars ( )
inline

Return a pointer to the character array located outside the heap.

◆ prechecked_str_imm_get_chars()

CharVec_t Cell::prechecked_str_imm_get_chars ( )
inline

Return a pointer to the character array embedded in this cell.

◆ prechecked_gensym_get_chars()

Charst_t Cell::prechecked_gensym_get_chars ( )
inline

THIS MUST COPY. str() returns a String BY VALUE, so the old one-liner return str().c_str(); handed back a pointer into a temporary that was destroyed at the end of that very expression – every caller then read freed memory. The symptom is not a crash but a gensym printing as garbage or as an empty name, most often inside an error message, i.e. precisely where the name was the thing you were trying to read. GCC 8.4.0 (ESP-IDF 4.4.7) does not diagnose it at all. GCC 15.x (Arduino 3.x / IDF 5.x) reports -Wreturn-local-addr against the CALLER, any_sym_get_chars(), so the message points one frame away from the defect – do not go looking in the caller. Size: a gensym renders as "G" followed by hex digits, at most 18 bytes on a 64-bit host, so 32 is ample; the copy is clamped anyway. NOT REENTRANT, BY CONSTRUCTION: two gensym names live at once – printf("%s %s", a->any_sym_get_chars(), b->any_sym_get_chars()) – share this single buffer and both print whichever was evaluated last. If a caller ever needs two simultaneously, give it a caller-supplied buffer; do NOT enlarge this one, which cannot fix that.

< one instance program-wide (inline fn local static)

< keep the temporary alive across the copy

◆ mustbe_Bool_t()

Bool_t Cell::mustbe_Bool_t ( )
inline

Return the value of this cell as a boolean.

◆ mustbe_Char_t()

Char_t Cell::mustbe_Char_t ( )
inline

Return the value of this cell as a character.

◆ mustbe_codepoint()

ll_codepoint_t Cell::mustbe_codepoint ( )
inline

Return full codepoint; throw if not T_CHAR.

◆ mustbe_int32()

LL_int32 Cell::mustbe_int32 ( )
inline

Return the value of this T_INT32 cell as int32.

◆ mustbe_float32()

LL_float32 Cell::mustbe_float32 ( )
inline

Return the value of this T_FLOAT32 cell as a real.

◆ mustbe_any_str_t()

Sexpr_t Cell::mustbe_any_str_t ( )
inline

Return this cell if it is a string.

◆ mustbe_cppobj_t()

Sexpr_t Cell::mustbe_cppobj_t ( )
inline

Return this cell if it is a CPP object.

◆ prechecked_cppobj_get_deleter()

CPPDeleterPtr Cell::prechecked_cppobj_get_deleter ( )
inline

Return the function to be called at garbage collection time to recycle the C++ object.

◆ prechecked_cppobj_get_ptr()

Ptr_t Cell::prechecked_cppobj_get_ptr ( )
inline

Return a pointer to a C++ object obtained earlier.

◆ coerce_float32()

LL_float32 Cell::coerce_float32 ( )
inline

< a component may be a boxed bignum -> coerce it via the boxed cell

◆ coerce_float64()

LL_float64 Cell::coerce_float64 ( )
inline

< a component may be a boxed bignum -> coerce it via the boxed cell

◆ exact_rank()

int Cell::exact_rank ( ) const
inline

Exact numeric rank: 0=T_INT32 1=T_INT64 2=T_RATIONAL 3=T_BIGNUM -1=not exact.

◆ inexact_rank()

int Cell::inexact_rank ( ) const
inline

Inexact numeric rank: 0=T_FLOAT32 1=T_FLOAT64 2=T_COMPLEX -1=not inexact.

◆ is_exact()

bool Cell::is_exact ( ) const
inline

True for T_INT32, T_INT64, T_RATIONAL, T_BIGNUM.

◆ is_inexact()

bool Cell::is_inexact ( ) const
inline

True for T_FLOAT32, T_FLOAT64, T_COMPLEX.

◆ is_complex()

bool Cell::is_complex ( ) const
inline

True iff T_COMPLEX.

◆ num_rank()

int Cell::num_rank ( ) const
inline

Legacy scalar rank: 0=int 1=float 2=rational 3=complex -1=non-numeric.

◆ numerator64()

LL_int64 Cell::numerator64 ( ) const
inline

Numerator of any exact number as LL_int64.

◆ denominator64()

LL_int64 Cell::denominator64 ( ) const
inline

Denominator of any exact number as LL_int64 (1 for integer types).

◆ coerce_cpx_real()

LL_float64 Cell::coerce_cpx_real ( )
inline

Real part: T_COMPLEX → cpx_real; others → coerce_float64.

◆ coerce_cpx_imag()

LL_float64 Cell::coerce_cpx_imag ( )
inline

Imaginary part: T_COMPLEX → cpx_imag; others → 0.

◆ any_error_get_str()

Sexpr_t Cell::any_error_get_str ( )
inline

any_error_get_*: like error_get_* but the T_ERROR typecheck is folded in and a non-error yields a benign sentinel (NIL) instead of throwing – lets a predicate write o->any_error_get_irritants() without a separate type() guard (less source, no missed-check bug).

◆ any_str_get_chars()

CharVec_t Cell::any_str_get_chars ( )
inline

If the cell is any kind of string, return a pointer to the zero-terminated character array.

◆ any_str_get_length()

LL_int32 Cell::any_str_get_length ( )
inline

THESE TWO ARE WHERE THE STRING-LENGTH FIX LANDS. Both derive the length from the TERMINATOR, which is why a LambLisp string cannot contain a NUL: writing a 0 into the middle does not set a byte, it SHORTENS THE OBJECT and strands the tail. Five guards exist solely to hold that invariant up. THE BYTE LENGTH IS ALREADY STORED AND ALREADY MAINTAINED – T_STR_HEAP keeps it in the car at all three sites that build or resize one (ll_vm_mk.cpp:57, ll_vm_mop3_rxrs.cpp:4266, :4301). NOTHING READS IT. So mop3_string_length computes a strlen here, DISCARDS it, and then walks the same buffer again with ll_u8_strlen – two full scans for a number sitting in the car. Pointing these at the car for T_STR_HEAP (byte 2 for T_STR_IMM, strlen for T_STR_EXT, whose car really is 0) is the cheap win and is behaviour-preserving WHILE those guards stand. ORDER MATTERS AND IT IS EASY TO GET BACKWARDS: today a stray NUL makes strlen return SHORT (the string reads truncated); once these read a stored length the same corruption returns the FULL length and callers read PAST the NUL. So the guards must come down AFTER every consumer stops deriving length from the terminator, never in the same change. BYTE length. T_STR_HEAP keeps it in the car – that field was always maintained and never read, so mop3_string_length used to compute a strlen here and discard it. T_STR_IMM is bounded by imm_limit so a scan is bounded by construction; T_STR_EXT is a static literal whose car really is 0, and which mutation already refuses, so it can never contain a NUL either.

◆ str_invalidate_charlength()

void Cell::str_invalidate_charlength ( )
inline

Mark a heap string's cached character count UNKNOWN. No-op for anything else.

CHARACTER length – what R7RS string-length returns. Stored for a heap string; counted for the other two, where the buffer is bounded by the cell.

CALL THIS AFTER ANY WRITE THAT REACHES A STRING'S BYTES WITHOUT RESIZING IT. The resize path (ll_str_splice) already invalidates, because it is the only way the BYTE length can change – but the CHARACTER count can change with the byte count fixed. Writing one UTF-8 lead byte over an ASCII byte merges it with its successor: 26 bytes stay 26 bytes and become 25 characters. The byte view of a string is exactly such a path: bytevector-u8-set! and bytevector-copy! accept a string and write its bytes raw, with no UTF-8 validation and without going through any string entry point. A stale flag is a WRONG ANSWER, not a slow one" – and it was measured true before this existed: after (bytevector-u8-set! s 0 206) on a 26-char ASCII string, string-length still said 26.

◆ any_str_get_charlength()

LL_int32 Cell::any_str_get_charlength ( )
inline

A STORED 0 MEANS "NOT KNOWN", AND THAT POLARITY IS DELIBERATE. ll_str_splice() only makes ROOM – its callers write the new bytes AFTER it returns – so the count cannot be computed there; the buffer is not final yet. Every resize therefore stores 0 and the count is recomputed on the next ask and cached here. Choosing 0 rather than -1 as the sentinel makes the failure mode free in both directions: a freshly payload_alloc'd block is memzero'd, and a construction path that forgets to write the header gets a RECOMPUTE (slow, correct) instead of a silent 0-length string. The one value it collides with is a genuinely empty string, whose recount is a scan of zero bytes. A missed update costs speed, never correctness – the same rule as the GC state bits.

◆ any_svec_get_info()

void Cell::any_svec_get_info ( LL_int32 & Nelems,
Sexpr_t *& elems )
inline

<NOT OBJ_VOID – that is a real value

◆ any_bvec_get_length()

LL_int32 Cell::any_bvec_get_length ( )
inline

< BYTE length of a string

◆ any_bvec_get_elems()

ByteVec_t Cell::any_bvec_get_elems ( )
inline

< byte view of a string

◆ any_bvec_get_info()

void Cell::any_bvec_get_info ( LL_int32 & Nelems,
ByteVec_t & elems )
inline

< byte view of a string – see the note above

< strlen: BYTES, not characters

◆ bvec_elem_type()

LL_int32 Cell::bvec_elem_type ( void ) const
inline

Get the array element-type tag from Cell byte 3. HEAP/EXT SHAPES ONLY – on T_BVEC_IMM this is element 0, not a type.

◆ bvec_set_elem_type()

void Cell::bvec_set_elem_type ( LL_int32 t)
inline

Set the array element-type tag in Cell byte 3. HEAP/EXT SHAPES ONLY – on T_BVEC_IMM this OVERWRITES element 0.

◆ cell_name()

String Cell::cell_name ( void )

A convenience feature to produce a string name for cells which are "well known" like NIL. Otherwise the name is the hex representation of the cell address.

◆ type_name()

Charst_t Cell::type_name ( LL_int32 typ)

Return a pointer to the C string corresponding to the cell type.

◆ gcstate_name()

Charst_t Cell::gcstate_name ( void )

Return a pointer to a C string corresponding to the given GC state.

◆ dump()

String Cell::dump ( )

Return a printable representation of the Cell internals.

Member Data Documentation

◆ F_GC01

const int Cell::F_GC01 = 0x01
static

gc flag

◆ F_GC02

const int Cell::F_GC02 = 0x02
static

gc flag

◆ F_GC04

const int Cell::F_GC04 = 0x04
static

gc flag

◆ F_TAIL

const int Cell::F_TAIL = 0x08
static

trampoline tail marker

◆ F_SUBTYPE_MASK

const int Cell::F_SUBTYPE_MASK = 0xF0
static

The subtype bits. Meaning is per top-level type; today only pairs use them.

◆ F_CAR_IS_INT

const int Cell::F_CAR_IS_INT = 0x10
static

PAIR subtype: car holds an embedded LL_int32 (not a traced pointer).

◆ F_CAR_IS_FLOAT

const int Cell::F_CAR_IS_FLOAT = 0x20
static

PAIR subtype: car holds an embedded LL_float32 (not a traced pointer).

◆ F_CDR_IS_INT

const int Cell::F_CDR_IS_INT = 0x40
static

PAIR subtype: cdr holds an embedded LL_int32 (not a traced pointer).

◆ F_CDR_IS_FLOAT

const int Cell::F_CDR_IS_FLOAT = 0x80
static

PAIR subtype: cdr holds an embedded LL_float32 (not a traced pointer).

◆ GC_STATE_MASK

const int Cell::GC_STATE_MASK = F_GC01 | F_GC02 | F_GC04
static

Mask for obtaining garbage collection state bits from Cell flag byte.

◆ F_BC_VARIADIC

const int Cell::F_BC_VARIADIC = 0x01
static

byte 2 bit 0: final locals slot holds rest-arg list (T_BYTECODE only).

◆ LL_STR_HDR

const int Cell::LL_STR_HDR = (int) sizeof(LL_int32)
static

Bytes of block header before the characters.