Remove obsolete internals documentation
parent
c4f5f56405
commit
e9e00211be
|
@ -1,541 +0,0 @@
|
||||||
FACTOR INTERNALS GUIDE
|
|
||||||
|
|
||||||
This guide focuses on portion of Factor that is written in C. The
|
|
||||||
sources can be found in the native/ subdirectory.
|
|
||||||
|
|
||||||
Factor defines a few new integer types; they will be mentioned without
|
|
||||||
further explanation:
|
|
||||||
|
|
||||||
- CELL: unsigned word-size field.
|
|
||||||
- FIXNUM: signed word-size field.
|
|
||||||
|
|
||||||
The 'CELLS' constant is defined as sizeof(CELL). The idea is to be able
|
|
||||||
to write expressions like 4*CELLS.
|
|
||||||
|
|
||||||
* The memory manager
|
|
||||||
|
|
||||||
Factor's memory manager is concentrated in the files memory.c and gc.c.
|
|
||||||
|
|
||||||
A guard page is allocated above the memory heap, and an out of memory
|
|
||||||
condition is raised if an attempt is made to write to the guard page.
|
|
||||||
Out of memory errors are fatal -- the garbage collector must be
|
|
||||||
triggered before this happends.
|
|
||||||
|
|
||||||
The alloc_guarded(CELL size) function allocates a block of memory with
|
|
||||||
guard pages above and below.
|
|
||||||
|
|
||||||
There are two memory zones of identical size; only one is in use at any
|
|
||||||
one time -- this is denoted as the 'active' zone. The other is the
|
|
||||||
'prior' zone. Each zone is identified with a ZONE struct stored in a
|
|
||||||
global variable named after the zone's role.
|
|
||||||
|
|
||||||
Memory is allocated in the zone using the allot(CELL a) function. This
|
|
||||||
function takes the size of a memory block to allocate. Memory blocks are
|
|
||||||
aligned on 8 byte boundaries, due to tagged representation of pointers.
|
|
||||||
If a non-8-byte-aligned block is passed in, allot() will round up the
|
|
||||||
size accordingly. Memory is allocated in a linear fashion, simply by
|
|
||||||
incrementing the 'here' field of the 'active' ZONE struct.
|
|
||||||
|
|
||||||
Note that allot() cannot be used in an arbitrary fashion, since doing so
|
|
||||||
will confuse the garbage collector. See the section on object
|
|
||||||
representation below.
|
|
||||||
|
|
||||||
* Tagged pointer representation
|
|
||||||
|
|
||||||
A ``tagged value'' is a CELL whose three least significant bits are a
|
|
||||||
``type tag''. The type tags are defined in types.h:
|
|
||||||
|
|
||||||
#define FIXNUM_TYPE 0
|
|
||||||
#define WORD_TYPE 1
|
|
||||||
#define CONS_TYPE 2
|
|
||||||
#define OBJECT_TYPE 3
|
|
||||||
#define RATIO_TYPE 4
|
|
||||||
#define COMPLEX_TYPE 5
|
|
||||||
#define HEADER_TYPE 6
|
|
||||||
#define GC_COLLECTED 7
|
|
||||||
|
|
||||||
If the tag is OBJECT_TYPE and the remaining bits of the cell are zero --
|
|
||||||
that is, if the tagged cell has integer value 3 -- it is taken to be the
|
|
||||||
canonical falsity value, and also the empty list. There is a convinient
|
|
||||||
macro:
|
|
||||||
|
|
||||||
#define F RETAG(0,OBJECT_TYPE)
|
|
||||||
|
|
||||||
If the tag is FIXNUM_TYPE, the most significant 29 bits of the cell are
|
|
||||||
taken to be a literal integer value. To decode the integer value, the
|
|
||||||
cell must be shifted to the right by three bits.
|
|
||||||
|
|
||||||
The role of the header tag is described below.
|
|
||||||
|
|
||||||
Any other tag signifies that the cell is a pointer to a 8-byte-aligned
|
|
||||||
block of memory; the address of the block is obtained by masking off the
|
|
||||||
least significant three bits of the tag.
|
|
||||||
|
|
||||||
Some macros for working with tagged cells are provided:
|
|
||||||
|
|
||||||
- TAG(cell) -- the tag of the cell
|
|
||||||
|
|
||||||
- UNTAG(cell) -- mask off the tag, turning it into a pointer
|
|
||||||
|
|
||||||
- RETAG(cell,tag) -- set the tag of a cell
|
|
||||||
|
|
||||||
- untag_fixnum_fast(cell) -- shift the cell to the right by three bits,
|
|
||||||
without actually checking that the tag is FIXNUM_TYPE. If it is not
|
|
||||||
FIXNUM_TYPE, a meaningless value is returned.
|
|
||||||
|
|
||||||
* Built-in data types
|
|
||||||
|
|
||||||
In Factor, all data types are defined as part of the runtime. There are
|
|
||||||
no user-defined types.
|
|
||||||
|
|
||||||
For some cells, the type of the object they point to is encoded entirely
|
|
||||||
in the tagged cell. This is true for the following types:
|
|
||||||
|
|
||||||
#define FIXNUM_TYPE 0
|
|
||||||
#define WORD_TYPE 1
|
|
||||||
#define CONS_TYPE 2
|
|
||||||
#define RATIO_TYPE 4
|
|
||||||
#define COMPLEX_TYPE 5
|
|
||||||
|
|
||||||
All other data types are pointed to by cells with a tag of OBJECT_TYPE,
|
|
||||||
and the first cell of the object must then be a tagged cell with tag
|
|
||||||
HEADER_TYPE, and the remaining bits must be one of the following values:
|
|
||||||
|
|
||||||
#define T_TYPE 7
|
|
||||||
#define ARRAY_TYPE 8
|
|
||||||
#define BIGNUM_TYPE 9
|
|
||||||
#define FLOAT_TYPE 10
|
|
||||||
#define VECTOR_TYPE 11
|
|
||||||
#define STRING_TYPE 12
|
|
||||||
#define SBUF_TYPE 13
|
|
||||||
#define PORT_TYPE 14
|
|
||||||
#define DLL_TYPE 15
|
|
||||||
#define ALIEN_TYPE 16
|
|
||||||
|
|
||||||
There are three fundamental functions for working with types:
|
|
||||||
|
|
||||||
- type_of(CELL tagged) -- return one of the above values. Never returns
|
|
||||||
OBJECT_TYPE or HEADER_TYPE.
|
|
||||||
|
|
||||||
- typep(CELL type, CELL tagged) -- return a boolean value. Behaves
|
|
||||||
identically to: type_of(tagged) == type.
|
|
||||||
|
|
||||||
- type_check(CELL type, CELL tagged) -- raise an error if the tagged
|
|
||||||
cell does not point to an object of the given type.
|
|
||||||
|
|
||||||
* Object representation
|
|
||||||
|
|
||||||
Object representation details can be found in types.[ch].
|
|
||||||
|
|
||||||
There is a fundamental principle guiding object representation in
|
|
||||||
Factor: When given a tagged cell, one must be able to determine the size
|
|
||||||
of the object it points to, and what other objects this object points
|
|
||||||
to.
|
|
||||||
|
|
||||||
There are two primary classes of objects:
|
|
||||||
|
|
||||||
- Conslikes. The type of a conslike is encoded in the tag. Conslikes are
|
|
||||||
represented 'naked' in the heap, with no header. These are exactly
|
|
||||||
objects of type CONS_TYPE, RATIO_TYPE, and COMPLEX_TYPE. Note that
|
|
||||||
WORD_TYPE also has its own tag, however words do have a header.
|
|
||||||
|
|
||||||
Since conslikes lack a header, the garbage collector and relocator
|
|
||||||
cannot distinglish between them while doing a linear scan of the heap.
|
|
||||||
This has an important consequence: the fields of conslikes must all be
|
|
||||||
tagged cells.
|
|
||||||
|
|
||||||
- Large objects. So called because the have a header and are larger than
|
|
||||||
conslikes. Unlike conslikes, an object with a header permits the garbage
|
|
||||||
collector and relocator to behave accordingly. For example, because
|
|
||||||
strings have a header, the relocator can skip over the characters of a
|
|
||||||
string, instead of treating them as tagged cells and crashing.
|
|
||||||
|
|
||||||
Tagged cells pointing to headed objects all have a tag OBJECT_TYPE,
|
|
||||||
except for pointers to words which have a tag WORD_TYPE.
|
|
||||||
|
|
||||||
* Conses and related types
|
|
||||||
|
|
||||||
Conslikes are found in the files cons.[ch], ratio.[ch] and complex.[ch].
|
|
||||||
|
|
||||||
Conslikes are one of the most important data types in Factor. They use
|
|
||||||
exactly two words of storage in the heap. This may seem surprising --
|
|
||||||
however, since all pointers to conslikes are tagged as being such, no
|
|
||||||
ambiguity results.
|
|
||||||
|
|
||||||
The most important is the CONS_TYPE.
|
|
||||||
|
|
||||||
Given a tagged cell pointing to a CONS_TYPE, one can call
|
|
||||||
untag_cons(CELL cell) to obtain a pointer to a F_CONS. F_CONS is a
|
|
||||||
struct defined as:
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
CELL car;
|
|
||||||
CELL cdr;
|
|
||||||
} F_CONS;
|
|
||||||
|
|
||||||
Given an untagged pointer to an F_CONS, one can obtain a tagged cell by
|
|
||||||
calling tag_cons(F_CONS* cons).
|
|
||||||
|
|
||||||
There are corresponding taggers and untaggers for the other two
|
|
||||||
conslikes:
|
|
||||||
|
|
||||||
untag_ratio(), tag_ratio(), untag_complex(), tag_complex().
|
|
||||||
|
|
||||||
* Image format
|
|
||||||
|
|
||||||
Image loading and saving is performed by image.[ch] and relocate.[ch].
|
|
||||||
|
|
||||||
On startup Factor loads an image file. The image file format depends on
|
|
||||||
the CPU word size and byte order, since it is directly loaded into
|
|
||||||
memory.
|
|
||||||
|
|
||||||
The image begins with two constants that must exactly equal:
|
|
||||||
|
|
||||||
- CELL magic -- 0x0f0e0d0c
|
|
||||||
- CELL version -- 0
|
|
||||||
|
|
||||||
The next value is used to relocate the image:
|
|
||||||
|
|
||||||
- CELL relocation_base
|
|
||||||
|
|
||||||
The following two are tagged pointers for user space:
|
|
||||||
|
|
||||||
- CELL boot -- quotation to interpret on startup.
|
|
||||||
- CELL global -- global namespace.
|
|
||||||
|
|
||||||
The last value is the image size, for verification purposes:
|
|
||||||
|
|
||||||
- CELL size.
|
|
||||||
|
|
||||||
* Image relocation
|
|
||||||
|
|
||||||
After the image has been loaded into the heap, a relocation procedure is
|
|
||||||
performed. The idea behind relocation is as follows: in memory, the
|
|
||||||
image contains absolute addresses to other parts of the image. However,
|
|
||||||
it is desirable to be able to load the image into another offset of
|
|
||||||
memory; depending on absolute memory mapping is unportable and
|
|
||||||
troublesome.
|
|
||||||
|
|
||||||
When saving the image, Factor stores the start address of the heap into
|
|
||||||
the header field relocation_base.
|
|
||||||
|
|
||||||
When loading the image, each address must be manipulated like so:
|
|
||||||
|
|
||||||
void fixup(CELL* cell)
|
|
||||||
{
|
|
||||||
if(TAG(*cell) != FIXNUM_TYPE && *cell != F)
|
|
||||||
*cell += (active.base - relocation_base);
|
|
||||||
}
|
|
||||||
|
|
||||||
Where 'active.base' is the heap start offset of the current instance,
|
|
||||||
and 'relocation_base' is the value found in the image header.
|
|
||||||
|
|
||||||
Relocation relies on total knowledge of object structure; it does this
|
|
||||||
by inspecting type tags.
|
|
||||||
|
|
||||||
Also the unportable 'xt' field of F_WORD structs is reset during
|
|
||||||
relocation, by looking up the word's primitive number in a global table
|
|
||||||
of primitives.
|
|
||||||
|
|
||||||
* Garbage collection
|
|
||||||
|
|
||||||
The garbage collector is defined in gc.c.
|
|
||||||
|
|
||||||
Factor's garbage collector is a standard two-space copying collector,
|
|
||||||
using Cheney's algorithm. Much has been said about this algorithm in the
|
|
||||||
literature, and this information will not be duplicated here.
|
|
||||||
|
|
||||||
Garbage collection is manually triggered by calling
|
|
||||||
maybe_garbage_collection(). This function checks if free memory is below
|
|
||||||
a certain threshold, and if so, commences a garbage collection.
|
|
||||||
|
|
||||||
*Beware!* Any local variables in the C stack are not visible to the
|
|
||||||
garbage collector, and are not taken as roots. Therefore, the only place
|
|
||||||
it is safe to call maybe_garbage_collection() is from inside a primitive
|
|
||||||
that was called directly from run(), before any values are stored in
|
|
||||||
locals.
|
|
||||||
|
|
||||||
For example, consider the following is a safe call to
|
|
||||||
maybe_garbage_collection():
|
|
||||||
|
|
||||||
void primitive_cons(void)
|
|
||||||
{
|
|
||||||
CELL car, cdr;
|
|
||||||
maybe_garbage_collection();
|
|
||||||
cdr = dpop();
|
|
||||||
car = dpop();
|
|
||||||
dpush(cons(car,cdr));
|
|
||||||
}
|
|
||||||
|
|
||||||
The function primitive_cons() is only ever called from run() or
|
|
||||||
primitive_execute(). In both these cases, the C stack does not store any
|
|
||||||
heap-allocated values in local variables.
|
|
||||||
|
|
||||||
However, the following would not be safe, and would result in a runtime
|
|
||||||
crash sooner or later:
|
|
||||||
|
|
||||||
void primitive_cons(void)
|
|
||||||
{
|
|
||||||
CELL cdr = dpop();
|
|
||||||
CELL car = dpop();
|
|
||||||
maybe_garbage_collection();
|
|
||||||
dpush(cons(car,cdr));
|
|
||||||
}
|
|
||||||
|
|
||||||
The garbage collector would not update the car and cdr local variables
|
|
||||||
to point to their new locations; so after it returned, the primitive
|
|
||||||
might have allocated pushed a cons cell that refers to oldspace. A crash
|
|
||||||
would follow soon after.
|
|
||||||
|
|
||||||
* The stacks
|
|
||||||
|
|
||||||
The stacks are defined in run.h.
|
|
||||||
|
|
||||||
The runtime maintains exactly two stacks -- the data stack and the
|
|
||||||
return stack. (The name stack and catch stack are purely library
|
|
||||||
phenomena.) Both the data and return stacks are allocated using
|
|
||||||
alloc_guarded(), so stack over/underflow checks are done in hardware
|
|
||||||
with no runtime overhead.
|
|
||||||
|
|
||||||
Both stacks hold tagged cells, and grow down -- that is, pushing
|
|
||||||
increments the pointer.
|
|
||||||
|
|
||||||
The data and return stacks are scoped by two global variables each:
|
|
||||||
|
|
||||||
- CELL ds_bot/cs_bot -- a pointer to the bottom of the data/return
|
|
||||||
stack; this is the first value you can write to, right above the guard
|
|
||||||
page.
|
|
||||||
|
|
||||||
- CELL ds/cs -- a pointer to the value at the top of the data/return
|
|
||||||
stack.
|
|
||||||
|
|
||||||
A set of inline functions are provided for pushing and popping the
|
|
||||||
stacks:
|
|
||||||
|
|
||||||
- dpop()/cpop() -- pop a value off the data/return stack.
|
|
||||||
|
|
||||||
- dpush()/cpush() -- push a value on the data/return stack.
|
|
||||||
|
|
||||||
- dpeek() -- return the top value on the data stack without popping.
|
|
||||||
|
|
||||||
- drepl() -- replace the top of the stack with a given value.
|
|
||||||
|
|
||||||
You can acess other values using the get() and set() inline functions.
|
|
||||||
For example, to get the value on the data stack under the top value,
|
|
||||||
without popping:
|
|
||||||
|
|
||||||
get(ds - CELLS);
|
|
||||||
|
|
||||||
* The inner interpreter
|
|
||||||
|
|
||||||
The inner interpreter is defined in the run() function of run.c.
|
|
||||||
|
|
||||||
The inner interpreter is a loop. It does not call itself recursively;
|
|
||||||
rather, it pushes and pops the Factor return stack to maintain recursive
|
|
||||||
state for interpreted definitions.
|
|
||||||
|
|
||||||
The currently executing quotation is stored in the global variable 'cf'.
|
|
||||||
This is a tagged cell that must point to a CONS_TYPE; otherwise a type
|
|
||||||
error is raised. This quotation will be called the ``call frame''.
|
|
||||||
|
|
||||||
If the end of the call frame quotation is reached -- that is, if it
|
|
||||||
becomes identically equal to F -- the return stack is popped, and the
|
|
||||||
popped value becomes the new call frame.
|
|
||||||
|
|
||||||
Each step of the interpreter takes the car of the call frame, referred
|
|
||||||
to as ``next'', and advances execution state by setting the call frame
|
|
||||||
to its cdr.
|
|
||||||
|
|
||||||
The type tag of next is inspected. If it is anything but WORD_TYPE, it
|
|
||||||
is pushed on the stack using dpush(). Otherwise, the word is executed as
|
|
||||||
described below.
|
|
||||||
|
|
||||||
When a word is executed by the inner interpreter, first the global
|
|
||||||
variable 'executing' is set to an untagged pointer to the F_WORD struct.
|
|
||||||
|
|
||||||
Next, the interpreter calls the C function whose address is stored in
|
|
||||||
the 'xt' field of the word using the EXECUTE macro:
|
|
||||||
|
|
||||||
#define EXECUTE(w) ((XT)(w->xt))()
|
|
||||||
|
|
||||||
An 'xt' is just a function that takes no arguments:
|
|
||||||
|
|
||||||
typedef void (*XT)(void);
|
|
||||||
|
|
||||||
As soon as the function returns, the inner interpreter loop continues
|
|
||||||
iterating down the call frame, pushing literals, and executing words,
|
|
||||||
then finally it reaches the end of the call frame, pops the return
|
|
||||||
stack, and the whole cycle repeats again.
|
|
||||||
|
|
||||||
The run() function never returns. The only way to exit the inner
|
|
||||||
interpreter is by calling the exit* primitive, defined in the function
|
|
||||||
primitive_exit() in misc.c
|
|
||||||
|
|
||||||
* Primitives
|
|
||||||
|
|
||||||
Primitives are exported to user space via numbers -- each F_WORD struct
|
|
||||||
has a 'primitive' field. During relocation, the 'xt' field of the F_WORD
|
|
||||||
is set to the corresponding C function pointer by looking up the
|
|
||||||
primitive number in a table.
|
|
||||||
|
|
||||||
Six fundamental primitives are:
|
|
||||||
|
|
||||||
- #0: undefined() -- undefined words have this primitive/XT. It simply
|
|
||||||
raises an error.
|
|
||||||
|
|
||||||
- #1: docol() -- compound definitions have this primitive/XT. Recall
|
|
||||||
that the inner interpreter sets the 'executing' global variable to the
|
|
||||||
word object before calling its XT. The docol() function accesses the
|
|
||||||
quotation stored in the 'parameter' field of the executing word, and
|
|
||||||
calls it. What exactly is meant by 'calls' is described below.
|
|
||||||
|
|
||||||
- #2: dosym() -- symbol definitions have this primitive/XT. It simply
|
|
||||||
pushes the executing word on the data stack, thus making it behave like
|
|
||||||
a literal.
|
|
||||||
|
|
||||||
- #3: primitive_execute() -- defined in user space as 'execute' in the
|
|
||||||
'words' vocabulary. Pops a word off the datastack, stores it in the
|
|
||||||
'executing' global variable, and calls its XT.
|
|
||||||
|
|
||||||
- #4: primitive_call() -- defined in user space as 'call' in the
|
|
||||||
'kernel' vocabulary. Pops a cons cell off the datastack, and calls it.
|
|
||||||
What exactly is meant by 'calls' is described below.
|
|
||||||
|
|
||||||
- #5: primitive_ifte() -- defined in user space as 'ifte' in the
|
|
||||||
'kernel' vocabulary. Executes one of two quotations on the stack,
|
|
||||||
depending on a boolean value stored below.
|
|
||||||
|
|
||||||
A 'boolean value' is of course 'false' if it is identically equal to F,
|
|
||||||
and 'true' otherwise.
|
|
||||||
* Quotations
|
|
||||||
|
|
||||||
Notice that docol(), primitive_call() and primitive_ifte() all take a
|
|
||||||
quotation from some source, and 'call' it. They do this using the inline
|
|
||||||
function call():
|
|
||||||
|
|
||||||
INLINE void call(CELL quot)
|
|
||||||
{
|
|
||||||
/* tail call optimization */
|
|
||||||
if(callframe != F)
|
|
||||||
{
|
|
||||||
cpush(tag_word(executing));
|
|
||||||
cpush(callframe);
|
|
||||||
}
|
|
||||||
callframe = quot;
|
|
||||||
}
|
|
||||||
|
|
||||||
Indeed, this function does not actually call the quotation itself. It
|
|
||||||
merely changes inner interpreter state in such a way that the next
|
|
||||||
iteration of the interpreter loop will begin executing this quotation;
|
|
||||||
then when it is done, the previous quotation is popped off the return
|
|
||||||
stack.
|
|
||||||
|
|
||||||
Two final points should be said about call(). It also pushes the
|
|
||||||
currently executing word for the profiler's sake; the inner interpreter
|
|
||||||
itself does not use the word that was pushed on the return stack.
|
|
||||||
|
|
||||||
Finally, call() performs tail call optimization. If the current call
|
|
||||||
frame is F -- in other words, if the occurrence of docol(),
|
|
||||||
primitive_call() or primitive_ifte() was the last in a quotation -- the
|
|
||||||
call frame is not pushed on the return stack, since there is nothing to
|
|
||||||
return to. If F was pushed on the return stack, it would simply be
|
|
||||||
popped again at a later time.
|
|
||||||
|
|
||||||
* User environment
|
|
||||||
|
|
||||||
At startup, the last thing done by the C code is setting the call frame
|
|
||||||
to point to the boot quotation (as defined in the image header). Then,
|
|
||||||
it calls run() and execution of the boot quotation begins.
|
|
||||||
|
|
||||||
User space makes use of a 'user environment' to store state that does
|
|
||||||
not belong on the data stack. The user environment is roughly like
|
|
||||||
global variables, however their number is fixed.
|
|
||||||
|
|
||||||
The user environment consists of 16 numbered slots, each slot holding
|
|
||||||
one tagged cell. User environment slots are accessed using the getenv (
|
|
||||||
slot -- value ) and setenv ( value slot -- ) primitives. The slots have
|
|
||||||
the following assignment:
|
|
||||||
|
|
||||||
#define STDIN_ENV 0
|
|
||||||
#define STDOUT_ENV 1
|
|
||||||
#define STDERR_ENV 2
|
|
||||||
#define NAMESTACK_ENV 3
|
|
||||||
#define GLOBAL_ENV 4
|
|
||||||
#define BREAK_ENV 5
|
|
||||||
#define CATCHSTACK_ENV 6
|
|
||||||
#define CPU_ENV 7
|
|
||||||
#define BOOT_ENV 8
|
|
||||||
#define RUNQUEUE_ENV 9
|
|
||||||
#define ARGS_ENV 10
|
|
||||||
#define OS_ENV 11
|
|
||||||
#define RESERVED_ENV_1 12
|
|
||||||
#define RESERVED_ENV_2 13
|
|
||||||
#define RESERVED_ENV_3 14
|
|
||||||
#define RESERVED_ENV_4 15
|
|
||||||
|
|
||||||
STDIN_ENV and STDOUT_ENV are set by the I/O code to point to F_PORTs for
|
|
||||||
the appropriate system streams. STDERR_ENV is unused.
|
|
||||||
|
|
||||||
NAMESTACK_ENV is not used by the runtime; it is for user space use only.
|
|
||||||
GLOBAL_ENV is set by the runtime on startup to the same value as the
|
|
||||||
'global' field of the image header. User space makes use of
|
|
||||||
NAMESTACK_ENV and GLOBAL_ENV to implement named variables and nested
|
|
||||||
namespaces. Both values are initialized in 'init-namespaces' of the
|
|
||||||
'namespaces' vocabulary.
|
|
||||||
|
|
||||||
BREAK_ENV is a quotation, set by user space. This quotation is called
|
|
||||||
when the runtime raises an error. CATCHSTACK_ENV is not set or read by
|
|
||||||
the runtime, it is for user space use only. Both are initialized in
|
|
||||||
'init-errors' of the 'errors' vocabulary.
|
|
||||||
|
|
||||||
CPU_ENV is set by the runtime to one of the two strings "x86" or
|
|
||||||
"unknown". It is not intended to be written. The 'cpu' word of the
|
|
||||||
'kernel' vocabulary is defined for reading it.
|
|
||||||
|
|
||||||
BOOT_ENV is set by the runtime to the same value as the 'boot' field of
|
|
||||||
the image header -- that is, the boot quotation. If it is set to a
|
|
||||||
different value and then an image is saved, the new image will have this
|
|
||||||
boot quotation. This can be used to make 'turnkey' images that start a
|
|
||||||
custom app, instead of the listener loop.
|
|
||||||
|
|
||||||
RUNQUEUE_ENV is used exclusively by user space, for threading. See the
|
|
||||||
'threads' vocabulary.
|
|
||||||
|
|
||||||
ARGS_ENV is set by the runtime to a linked list of strings,
|
|
||||||
corresponding to command line arguments given to the Factor runtime
|
|
||||||
executable. The first of these arguments will always be an image name.
|
|
||||||
|
|
||||||
OS_ENV is set by the runtime to one of the two strings "unix" or
|
|
||||||
"win32". It is not intended to be written. The 'os' word of the 'kernel'
|
|
||||||
vocabulary is defined for reading it.
|
|
||||||
|
|
||||||
The remaining four user environment slots must not be read or written by
|
|
||||||
user space code.
|
|
||||||
|
|
||||||
* Error handling
|
|
||||||
|
|
||||||
Error handling goes on inside the error.c file, as well as run.c.
|
|
||||||
|
|
||||||
When the inner interpreter loop begins, setjmp is called to save the
|
|
||||||
current C stack in the global variable 'toplevel'.
|
|
||||||
|
|
||||||
When an error is raised inside the runtime, one of two things happen:
|
|
||||||
|
|
||||||
- BREAK_ENV is not yet set, so the interpreter prints an error message
|
|
||||||
and immediately exits. This is the case during bootstrapping.
|
|
||||||
|
|
||||||
- BREAK_ENV is set, in which case the error is stored in the
|
|
||||||
'thrown_error' global variable, and the error handler long-jumps to the
|
|
||||||
top level. Execution then resumes at the inner interpreter loop, which
|
|
||||||
then checks if there was a thrown error, and if so, it pushes the error
|
|
||||||
on the data stack, and calls the quotation stored in BREAK_ENV.
|
|
||||||
|
|
||||||
User space takes over from here. The BREAK_ENV is defined as follows in
|
|
||||||
the 'init-error-handler' word of debugger.factor:
|
|
||||||
|
|
||||||
[ dup save-error rethrow ] 5 setenv
|
|
||||||
|
|
||||||
The 'save-error' word snapshots the current stacks -- this is how :s,
|
|
||||||
:r, :n and :c work. 'rethrow' then pops a continuation from the catch
|
|
||||||
stack and calls it, which results in a certain 'catch' block being
|
|
||||||
executed.
|
|
Loading…
Reference in New Issue