sima/autoconf/
sima/hosts/i386/
sima/mudlib/
sima/mudlib/kernel/
sima/mudlib/obj/
sima/mudlib/sys/
sima/synhash/mips/
The package is called 'Simulation Machine' or short sima.

The implementation language is ansi C, with some common extensions:
- All non-NULL pointers are required to be distinct from 0,1,2
  The NULL pointer is required to be even.
- Pointer arithmetic has to work for arbitrary sizes ('flat memory model')
- having more than 8 bits for a char wastes memory so that the
  implementation becomes unusable.
  Therefore a char is simply assumed to be 8 bits.
- There needs to be an integer type that has the size of a char *

The prototype stage will use zero sized arrays to express some things
more naturally.

All data allocated has a type. Memory allocation is built-in and knows
about these types.
svalues are either pointers to allocated typed data, pointers to constant
memory regions with typed data, or integers.
pointers are all odd, and actually point at the second byte.
All integers are represented as numbers twice as large as their value,
and thus appear to be even in memory.

The usual way to pass a string to a function is either as an svalue, or
as a pointer to the start and a count, without a terminating byte.

string svalue:
short global		long global
1 byte type		1 byte type
1 byte ref		1 byute ref
1 byte major ref	2 byte major ref
1 byte size		4 byte next_hash
4 byte next_hash	4 byte size
n bytes data		n bytes data

object svalue:
1 byte type
1 byte minor ref
2 bytes flags
4 bytes wizlist / ref expander
...

mapping:
1 byte type
1 byte minor ref
2 bytes num_values
4 bytes wizlist or hash_mapping or ref/size expander
4 bytes size
misc values

ref/size expander:
1 byte T_INTERNAL
1 byte IT_X_ARRAY / IT_X_MAP
2 bytes major ref
-->
4 bytes wizlist
4 bytes size      (not for objects/mappings)
hashmapping stuff (not for objects/arrays)

1 byte T_INTERNAL
1 byte IT_X_OBJ
2 bytes major ref
-->
4 bytes wizlist
4 bytes shadowed_by
4 bytes shadowing
4 bytes interactive

array: 
1 byte type
1 byte minor ref
2 bytes size
4 bytes wizlist or ref/size expander & wizlist

When the ref count for a short string is exceeded, it is stored as
long string.

There are distinct hash tables for short and long strings. The short
string table is searched first for short strings, then the index is
ANDed with some constant to get an index in the smaller large string
hash table.

shared strings have the same format as global strings, and are
slowly transformed to global strings by walking through the heap in
small chunks every backend loop.

setjmp()/longjmp() is not used, instead there is a global variable
inter_errno. Some functions might hint with a error return code.

Error codes are passed as numbers to the mudlib. There will be a data file
that maps numbers to english sprintf format strings in the mudlib section.
There will be a gobal array for error message parameters. The error handling
of the driver will send the errno and these parameters to the error handler 
closure.
There are distinct handlers for 'plain' errors and errors that are caught.
Errors are only logged hard-coded when there is a triple fault. Such logs will
also have a numeric errno instead of a message.

Input from interactive users will be sent to a hook settable on a per-user
basis.
void set_interactive_hook(int)
H_TEXT_INPUT
H_TELNET_NEG

all output is sent by text_message() and binary_message().

concepts like snoop, input_to(), write(), tell_object(), add_action(), ed(),
inventories are implemeted in the mudlib.

some of the object flags are acessible to the mudlib for read and write.

destructed objects are left on the stack, but since their type is transformed
to T_DESTRUCTED, this is not that much of a problem.

Branch instructions have to test for integer anyways to figure out if the
value needs freeing.

lvalues have minor ref 255, and are dereferenced in copy_svalue()

vargarg is implemented by using an array that holds all dereferenced values,
and a hidden local variable that references this array and holds all
dereferenced lvalues. When this variable is freed at function return,
the values from the array are copied back via the stored lvalues.
If no lvalues are found at creation, only a 0 gets stored in the hidden
variable, to save copy-back overhead.

A varargs function starts with F_VARARGS . The varargs variable is not counted
as argument, and is the first auto variable.
when F_VARARGS is executed, it pushes the null_vector.
When there are more actual arguments than declared, the first byte of code
is checked if it is F_VARARGS.

The varags array may be modified with ordinary aray operations. Thus, lvalues
corresponding to its values are stored in a separate hidden variable.
This is 0-initialized by default. It is also left zero if there are members
to the varargs array, but no corresponding lvalues.
This hidden variable can't be used explicitly, thus it is safe that it has a
ref count of 1, which makes automatic deallocation at function return,
which does the necessary assignments.

When there are fewer arguments than expected, the parameters are filled with
the nil_object, which is of type T_DESTRUCTED.

When an object is loaded, the master is asked for the uid first, then the
struct object gets allocated, its program set to nil_program, and then the
file gets compiled with the new object as current_object.
This needs a control stack push.

The control stack has a member for the correct stack pointer at return.
This allows lazy popping.

Moreover, the break stack can be integrated into the value stack again.
Break values are represented as numbers.
There is an extra break code for closures that allows a return value above
the break value.

get_map_lvalue gives an lvalue to a trash area if malloc fails.

The amount of mapping compaction and string sharing done depends on wether
there is other work to do or not.

precompiled header files: header files that consist only of a single
embracing #ifdef and a bunch of #defines can be precompiled to .i files
with pre-hashed #defines with pre-scanned token streams prepared. There
is a #pragma keep_values that will allow to share symbols and floats
between programs, by keeping all values at scanning time and only
deallocating the ones that have ref count one after scanning, i.e. not
incorporated in any program yet. Another #pragma keep_all_values keeps
values irrespective of ref count, to speed up compilation speeed even
further, at some memory cost.
precompiled header files are created on demand by placing a
#pragma precompile_header directive in the .h file; .i files have a
version field that has to match the current .i file format version,
else they are re-created.
The H_INCLUDE_DIRS hook specifies if .i files should be checked first or
.h files should be tried first and only upon reading #pragma precompile_header
the presense if a .i file is checked and used/recreated accordingly.

the parser generator should recognize when there is a shift to a state
with a rulefree default reduction resulting in a goto. This should be
transformed to an immedaite shift to the goto destination.

inline simul_efuns: these are compiled in the simul_efun object, but
instead of the usual return code it uses a special code. The function name
is recognized in -> call_other contexts and translated to a special call code.
when interpreted, it saves the current object and pc in a register, sets
this_object to its first argument, and jumps without frame into the simul_efun
code.

shared variables get space assigned when they are used for the first time,
thus you can conveniently use them in header files.

identifiers in macro expansions can re-use existing I_TYPE_GLOBAL and
I_TYPE_RESWORD type shared identifiers; if there are none, a I_TYPE_GLOBAL
type shared identifier is created, with all members cleared.

If an out of memory error occurs before ipc is set up, the driver aborts.
This saves some checks and thus memory...

The funtion table contains an index into inherits for every function.
Inherit 0 is reserved for newly-defined functions.
These indexes can be 8 or 16 bits. Cross_defines are implemented by adding
extra inherits.  function/variable static-ness of an inherit is expressed
with the two lower bits of the struct program * in the inherit.
If an inherit has other modifiers, this is expressed with a struct
inherit_modifier.
Individual functions/variables can also have modifiers of their own; however,
to change static-ness of a function, you need an extra struct inherit.

When a lot of functions are deep inherited from the same program, an extra
inherit is used.  For 16 bit inherit indices, every function is described with
an index that points straight to the definition.  Thus, no looping code is
needed to deal with 16 bit inherit indices.

programs that have exactly one inherit, are not inherited itself, and
do no code-less redefinition of a function's protectedness are marked
as leaf_inherit, and have the new_function table alphasorted. They need
no function_name table.  Otherwise, the new_function table is numerically
sorted, to speed up error backtrace lookups.

When cache_call is called on a leaf_inherit program, it starts to recurse,
looking up in the inherited program.  If an entry is found, it is
checked in the virtual table of the original program if the function
has been redefined; no cache entry is generated for the leaf_inherit
program unless the recursive lookup yielded 'not found'.  This way,
the call cache hit rate is boosted so that replace_program becomes
obsolete.

For every virtual base program defining variables, there is an entry in the
function table describing the virtual variable offset.
The slot is copied & filled in accordingly at inherit time.
union { short *variables, int32 *functions} current_virtual;
union svalue *current_variables;
variable access:
current_variables[ current_virtual.variables[pc[0]] + pc[1] ]
function access:
current_virtual.functions[funnum]

An individual variable can be declared to be virtual.  When this is done,
an individual entry is done for this variable in the virtual table.
Such a variable can be redefined to be shared.  Thus, you can have a generic,
configurable object, and inherit it by a special item that redefines some
variables to be shared, so that they don't take up any memory in clones.

the *cindex* functions are used when there is a constant index in the range
0..255 . They are not usefule to speed up & compact ordinary LPC code,
but also to implement user defined structures using arrays.

call_outs are stored in a balanced heap. The members are also entered in a
hash table with the object pointer as key; the hash lists are circular.

destruct() checks for master object, and reloads immediately if its the one
that was destructed. Thus, destruct2() can free stuff that a reactivated
master needs. Moreover, there is no point for a assert_master_ob_loaded()
function: either the master is there or it is loading.

eval_time using real CPU time does not work reasonably: the times() system
call overhead is prohibitive, the clock resolution unusable for accounting,
and pretty low to reserve chunks of eval_time for the master; moreover, it
makes it unpredictable if a piece of code can execute in the time it is
given.
The only way this could possibly make working would be to have direct access
to a hardware timer, and have a dedicated machine with no other processes.
Alas, complete predictability is not possible, because of cache structures
and because sima needs virtual memory, wich does not mix well with
predictable realtime behaviour.

CATCH: there is a 8 bit offset for the end adress.  The end adress is
calculated only in the error case.

invalid mapping entries are represented by pointers that are
3 mod sizeof(char *) for values that can be compared by pointer.
for values that are compared by  content, an adjectant value is copied,
unless it is at the border to pointer-comparable values. If there are values,
the first one is replaced by an lvalue to mark it invalid so that a subsequent
search will avoid to return a pointer to these values.
It is necessary to use an lvalue because pending pass by reference might
want to overwrite it.

virtual variables are placed at the end. The reason why Lpmud 3.2.x had them
at the start was that it needed searching; this is no longer the case.
There are two advantages in having them at the end:
i)  We can use unsigned shorts for fp->vitual.variable
ii) Finding non-virtual variables is simpler.

Alas, we also loose something from having fp->vitual.variable : to compile a
local function call, we have to know first how much space is taken by
fp->virtual.variable, so that we can adjust the index.

Implement only if it doesn't cost too much for normal function calls:
Allow a const modifier for functions. When a const function without arguments
returns, it modifies the call cache to contain the call result rather than
the way to call it. Use some heuristics to infer that a function is const
even if it is not declared as such.

Alternate low-level memory allocater pentalloc.c:
/* optimization for cache line size >= 8 * sizeof(void *) */
struct free_block { /* slightly modified B-TREE node */
    uint8 type;
    unsigned empty: 2;
    p_int size;
    struct free_block *parent;
    struct free_block child[5];
};

or

struct free_block { /* slightly modified B-TREE node , 3..5 childs */
    uint8 type;
    unsigned empty: 2;
    p_int size;
    struct free_block *parent;
    p_int child_size[5];
    struct free_block child[5];
};

or

struct free_block { /* slightly modified B-TREE node , 4..7 childs */
    uint8 type;
    unsigned empty: 2;
    p_int child_size[7];
    struct free_block child[7];
    struct free_block *parent;
    p_int size; /* probably in separate cache line, but not needed in searches */
};

When a destructed object is used in an untyped equality comparison, it will
compare equal to 0; however, when it is used in another context, the result
is undefined: it may either behave like the number 0 or like an object.
Untyped equality comparisons are ==, !=, !, and implicit in the condition
arguments to if, while and for, and the searching of efun member in a
mapping or a proper array.