#ifndef EXEC_H__ #define EXEC_H__ 1 /*--------------------------------------------------------------------------- * Types and Macros used to describe and store compiled programs. * *--------------------------------------------------------------------------- * Basics * ------ * * LPMud distinguished between objects and their programs. Objects hold * the live data which is unique to each of them. Programs otoh are shared * among objects through inheritance or cloning and hold the program code, * variable specification and similar information. Due to the separation * programs maintain their own reference count, and it is this separation * which allows the separate swapping of programs and variables. * * A compiled program consists of several data blocks which are allocated * together in one memory block. Not only this is advantageous for the * memory locality, but also simplifies swapping as all data for a program * can be read or written in one go, with a simple relocation computation * on the pointers involved. * * The blocks of a program in correct allocation order are these: * * struct program_s: The basic structure of the program, with pointers to * the other data blocks. * * bytecode program[]: the actual bytecode for the program. * * unsigned short function_names[]: Lookup table of function name index * to the offset of the function within the functions[] table. * function_names[] is allocated together with and right after the * bytecode. If a function redefines an inherited function, this table * points to the redefinition. * Read 'function_name' as 'function_index'. * * uint32 functions[]: One word for each function, giving the type * and the offset within the program[] codearray. Additional information * for each function is embedded into the program code. * If a program uses undefined functions, the compiler generates * dummy functions. * Through inheritance and cross-definition these functions can * be resolved on a higher level. * * char *strings[]: An array of pointers to the string literals (stored * as shared strings) used by the program. This way the program can * use the strings simply by an (easily swappable) index. The compiler * makes sure that each string is unique. * * When a program is swapped, the reference counts to these strings are * not removed so that the string literals stay in memory all the time. * This saves time on swap-in, and the string sharing saves more memory * than swapping might gain. * * struct variable_s variable_names[]: an array describing all variables * with type and name, inherited or own. When a program is swapped, the * reference counts to these strings are not removed so that the * string literals stay in memory all the time. * * struct inherit inherit[]: an array describing all inherited programs. * * vartype_t argument_types[]: (only with #pragma save_types) * The types of all function arguments of the program in the * order they were encountered. * * unsigned short type_start[]: (only with #pragma save_types) * Lookup table [.num_function_names] function index * -> index in .argument_types[], which gives the index of * the first argument type. If this index is INDEX_START_NONE, * the function has no type information. * * The linenumber information is allocated separately from the main program * block so that it can be swapped separately more easily. The struct * linenumbers_s is allocated to size and holds the line number information * as an array of bytecodes: * * include_t includes[]: an array listing all include files used * to compile the program, in the order they were encountered. * When a program is swapped, the reference counts to these strings * are not removed so that the string literals stay in memory all * the time. * * bytecode_t line_numbers[]: the line number information, * encoded in a kind of delta compression. When a program * is swapped in, the line numbers are allocated separately * and not swapped in until needed. * * TODO: If the program_s is allocated separately from the rest, * TODO:: we could swap even if a program is used by clones. * TODO:: However, find out how often that would be useful. * * * Bytecode * -------- * * As the name conveys, the LPC programs are compiled into a bytecode, * assuming 8-Bit-Bytes. Since we have more than 256 opcodes, the less * often used instructions are encoded in two-byte opcodes: the highbyte * is (instruction / 128), the low byte (instruction % 128). The resulting * values for the highbyte are 'coincidentally' the values of the prefix * opcodes F_ESCAPE, F_TEFUN and F_VEFUN. The divisor 128 is symbolically * defined by F_ESCAPE_BITS below. * * To achieve platform independance, the driver does not operate directly * with 'char's and 'char *'s, but instead with 'bytecode_t' and * 'bytecode_p's. Combined with some macros this allows the implementation * even on platforms with CHAR_BITs != 8. * TODO: This support is far from complete/comprehensive, and some values * TODO:: in the bytecode are stored in host-sizes and -layouts. * * The bytecode itself is divided into functions: the code for every * function is (except for absolute jumps) selfcontained and prepended * by a header holding the name of the function, and the number and types * of expected arguments. The advantage is that for a given function * the driver does not need to know if it is part of a program or a * lambda closure compiled at runtime: in both cases all necessary * information about the function is right where its code is. * * The maximum size of a program is limited by the biggest offset * that can be stored in the 'functions' array, currently 1 MByte. * A lot of internal offset counters are shorts even, though so far * this never caused a problem. * * * Inheritance * ----------- * * Inheritance is handled such that all the variable and function * information of the inherited programs are appended to the programs * own variables and functions. The inherit entries then give away * where in the programs tables the inherited information can be found. * * Imagine: A and B inherit nothing, C inherits A, D inherits C and B. * Then the function and variable blocks are set up like this ('-funs' are * real function entries, '-desc' are pointers to inherit descriptors): * * A-fblock: A-funs (B similar) * A-vblock: A-vars (B similar) * * C-fblock: C-funs A-desc * C-vblock: C-vars A-vars * * D-fblock: D-funs (C-desc A-desc) B-desc * D-vblock: D-vars C-vars A-vars B-vars * and fblock has the twist that (C-desc A-desc) together are * considered being 'the' C-desc block. * * If a program is inherited virtually several times, each virtual inherit * gets its own struct inherit; the variable block however is reserved * just once. To mark the virtual inherit, the variable types receive the * modifier TYPE_MOD_VIRTUAL. During the inheritance of the compiler, functions * from non-virtual inherits temporarily receive the flag * NON_VIRTUAL_OFFSET_TAG in their variable indices, too. * * However, accesses to virtually inherited variables only use the first * inherit instance - if the variable index in the child's program code indexes * the variable block associated with a later instance, the driver finds * the first instance by comparing the .prog entries and uses the 'real' * variable associated with this first instance. * * The compiler places virtual variables always at the begin of the variable * block and limits the number to 256. * * TODO: Find a way to avoid the virtual var searching - either by encoding the * TODO:: inherit index in the code, or by adding a ref to the 'first * TODO:: instance' to this structure. See interpret.c:find_virtual_value(). * * Old Comments: * ------------- * When an object is compiled with type testing (#pragma strict_types), all * types are saved of the arguments for that function during compilation. * If the #pragma save_types is specified, then the types are saved even * after compilation, to be used when the object is inherited. *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" /* --- Type Constants --- * * These constants and types are used to encode types and visibility * of variables and functions. They are used by both the compiler and * the interpreter. * * You'll find the "TYPE_MOD_" visibility constants below with the * function flags. */ typedef unsigned short vartype_t; /* Basic: just the datatype */ typedef uint32 fulltype_t; /* Full: type and visibility */ /* Basic type values */ #define TYPE_UNKNOWN 0 /* This type must be casted */ #define TYPE_NUMBER 1 #define TYPE_STRING 2 #define TYPE_VOID 3 #define TYPE_OBJECT 4 #define TYPE_MAPPING 5 #define TYPE_FLOAT 6 #define TYPE_ANY 7 /* Will match any type */ #define TYPE_CLOSURE 8 #define TYPE_SYMBOL 9 #define TYPE_QUOTED_ARRAY 10 #define TYPEMAP_SIZE 11 /* Number of types */ /* Flags, or'ed on top of the basic type */ #define TYPE_MOD_POINTER 0x0040 /* Pointer to a basic type */ #define TYPE_MOD_REFERENCE 0x0080 /* Reference to a type */ #define TYPE_MOD_MASK 0x000000ff /* Mask for basic type and flags. */ #define PRIMARY_TYPE_MASK (TYPE_MOD_MASK & ~TYPE_MOD_REFERENCE & ~TYPE_MOD_POINTER) /* Mask to retriefe the basic type value. */ #define TYPE_MOD_RMASK (TYPE_MOD_MASK & ~TYPE_MOD_REFERENCE) /* Mask to delete TYPE_MOD_REFERENCE and the visibility mods from * a type value. */ #define VIRTUAL_VAR_TAG 0x4000 /* Flag set in virtual variables, also interpreted as offset * in the variable index for virtual variables. */ /* --- struct instr_s: description of stackmachine instructions --- * * Stackmachine instructions are both 'internal' codes with no external * representation, as well as efuns. * * The information about all instructions is collected in the table * instrs[] which is indexed by the bytecode of the instructions. * * The table is declared in instrs.h and defined in the file efun_defs.c * both of which are created by make_func from the func_spec file. * The table is compiled into the lexer module and exported from there. */ struct instr_s { short max_arg; /* Maximum number of arguments, -1 for '...' */ short min_arg; /* Minimum number of arguments. * The number 0 marks incallable closures: instructions which * are used as syntactic markers in lambda closures, but by * themselves can't be executed. */ char type[2]; /* Types of arguments 1 and 2, using the svalue codes */ short Default; /* An efun to use as default value for last argument. * > 0: index into instrs[] to the efun to use. * 0: no default value. * -1: this whole entry describes an internal stackmachine code, * not a normal efun (an 'operator' in closure lingo). */ vartype_t ret_type; /* The return type used by the compiler. */ short arg_index; /* Indexes the efun_arg_types[] array (see make_func). */ char *name; /* The printable name of the instruction. */ char *deprecated; /* Usually NULL, for deprecated efuns this is * the warning message to print. */ }; /* --- Byte code --- * * The program code is stored as byte code. The following definitions * and macros allow its implementation even on platforms with more than * 8 bits per character. * TODO: This portability is far from complete, and not used everywhere, * TODO:: not even in the compiler. * * bytecode_t: an integral type holding the numbers 0..255. * bytecode_p: an integral type addressing a bytecode. This need not * be a pointer. * * bytecode_t GET_CODE(bytecode_p p) * bytecode_t LOAD_CODE(bytecode_p p) * Return the bytecode from *p, the LOAD_ variant then increments p. * * void PUT_CODE(bytecode_p p, bytecode_t c) * void STORE_CODE(bytecode_p p, bytecode_t c) * void RSTORE_CODE(bytecode_p p, bytecode_t c) * Store the bytecode c in *p, the STORE_ variant then increments p. * The RSTORE_ variant pre-decrements p. * * char GET_INT8(p) , LOAD_INT8(p) * uchar GET_UINT8(p), LOAD_UINT8(p) * Return the 8-Bit (unsigned) int stored at <p>, the LOAD_ variants * then increment <p>. * * void PUT_INT8(p, char c), STORE_INT8(p, char c) * void PUT_UINT8(p, uchar c), STORE_UINT8(p, uchar c) * Store the 8-Bit (unsigned) int <c> into <p>, the STORE_ variants * then increment <p>. * * void GET_SHORT ([unsigned] short d, bytecode_p p) * void LOAD_SHORT([unsigned] short d, bytecode_p p) * Load the (unsigned) short 'd' stored at <p>, the LOAD_ variant * then increments <p>. * * void PUT_SHORT (bytecode_p p, [unsigned] short d) * void STORE_SHORT(bytecode_p p, [unsigned] short d) * void RSTORE_SHORT(bytecode_p p, [unsigned] short d) * Store the (unsigned) short <d> into <p>, the STORE_ variant * then increments <p>. The RSTORE_ variant pre-decrements <p>. * * void GET_INT16 ([unsigned] int16 d, bytecode_p p) * void LOAD_INT16([unsigned] int16 d, bytecode_p p) * Load the (unsigned) int16 'd' stored at <p>, the LOAD_ variant * then increments <p>. * * void LOAD_INT32([unsigned] int32 d, bytecode_p p) * Load the (unsigned) in32 'd' stored at <p>, then increment <p>. */ #define F_ESCAPE_BITS 7 /* The number of bits the lowbyte of a dual-byte-opcode can hold */ #if CHAR_BIT == 8 typedef unsigned char bytecode_t; typedef bytecode_t * bytecode_p; #define GET_CODE(p) (*(p)) #define LOAD_CODE(p) (*(p)++) #define PUT_CODE(p,c) (*(p) = (c)) #define STORE_CODE(p,c) (*(p)++ = (c)) #define RSTORE_CODE(p,c) (*--(p) = (c)) /* TODO: all these casts yield rvalues, so they shouldn't compile * TODO:: (and on xlc on AIX some of them indeed don't). */ #define GET_UINT8(p) (*((unsigned char *)(p))) #define GET_INT8(p) (*((signed char *)(p))) #define LOAD_UINT8(p) (*((unsigned char *)(p)++)) #define LOAD_INT8(p) (*((signed char *)(p)++)) #define PUT_UINT8(p,c) (*((unsigned char *)(p)) = (c)) #define PUT_INT8(p,c) (*((signed char *)(p)) = (c)) #define STORE_UINT8(p,c) (*((unsigned char *)(p)++) = (c)) #define STORE_INT8(p,c) (*((signed char *)(p)++) = (c)) /* TODO: These generic mem-macros should go into a macros.h. See also * TODO:: how mudos does it. * Note: the lowlevel BYTE macros can't use MACRO(), since this is * needed on the next abstraction level, and a macro can't be nested * into itself. */ #define LOAD_2BYTE(d,p) ( ((char *)&(d))[0] = *(char *)(p)++, \ ((char *)&(d))[1] = *(char *)(p)++) #define GET_2BYTE(d,p) ( ((char *)&(d))[0] = ((char *)(p))[0], \ ((char *)&(d))[1] = ((char *)(p))[1] ) #define STORE_2BYTE(p,d) do {\ unsigned char * _q, ** _qq; \ _q = (unsigned char *)(p); \ _qq = (unsigned char **)&(p); \ _q[0] = ((unsigned char *)&(d))[0]; \ _q[1] = ((unsigned char *)&(d))[1]; \ *_qq += 2; \ } while(0) #define RSTORE_2BYTE(p,d) do {\ unsigned char * _q, ** _qq; \ _q = (unsigned char *)(p); \ _qq = (unsigned char **)&(p); \ _q[-2] = ((unsigned char *)&(d))[0]; \ _q[-1] = ((unsigned char *)&(d))[1]; \ *_qq -= 2; \ } while(0) #define PUT_2BYTE(p,d) ( ((char *)(p))[0] = ((char *)&(d))[0], \ ((char *)(p))[1] = ((char *)&(d))[1] ) #define LOAD_4BYTE(d,p) ( ((char *)&(d))[0] = *(char *)(p)++, \ ((char *)&(d))[1] = *(char *)(p)++, \ ((char *)&(d))[2] = *(char *)(p)++, \ ((char *)&(d))[3] = *(char *)(p)++ ) #define GET_4BYTE(d,p) ( ((char *)&(d))[0] = ((char *)(p))[0], \ ((char *)&(d))[1] = ((char *)(p))[1], \ ((char *)&(d))[2] = ((char *)(p))[2], \ ((char *)&(d))[3] = ((char *)(p))[3] ) #define STORE_4BYTE(p,d) ( *(unsigned char *)(p)++ = ((char *)&(d))[0], \ *(unsigned char *)(p)++ = ((char *)&(d))[1], \ *(unsigned char *)(p)++ = ((char *)&(d))[2], \ *(unsigned char *)(p)++ = ((char *)&(d))[3] ) #define PUT_4BYTE(p,d) ( ((char *)(p))[0] = ((char *)&(d))[0], \ ((char *)(p))[1] = ((char *)&(d))[1], \ ((char *)(p))[2] = ((char *)&(d))[2], \ ((char *)(p))[3] = ((char *)&(d))[3] ) #define GET_SHORT(d,p) GET_2BYTE(d,p) #define LOAD_SHORT(d,p) LOAD_2BYTE(d,p) #define PUT_SHORT(p,d) MACRO(unsigned short _us = (unsigned short)d; \ PUT_2BYTE(p,_us);) #define STORE_SHORT(p,d) MACRO(unsigned short _us = (unsigned short)d; \ STORE_2BYTE(p,_us);) #define RSTORE_SHORT(p,d) MACRO(unsigned short _us = (unsigned short)d; \ RSTORE_2BYTE(p,_us);) /* TODO: This assumes sizeof(short) == 2. */ #define LOAD_INT16(d,p) LOAD_2BYTE(d,p) #define LOAD_INT32(d,p) LOAD_2BYTE(d,p) #define GET_INT32(d,p) GET_4BYTE(d,p) #define PUT_INT32(p,d) PUT_4BYTE(p,d) #endif #ifndef GET_CODE # error "No bytecode type defined." #endif /* --- Function flags --- * * Programs know about their functions from an array of uint32s which hold * the flags for the function and, in one field in the low bits, the * address of the code. * * Some flags (TYPE_MOD_*) are also used for variable types. * * Using a bitfield is tempting, but generates inefficient code due to the * lack of a real 'bool' datatype. */ typedef fulltype_t funflag_t; /* Function flags */ #define NAME_INHERITED 0x80000000 /* defined by inheritance */ #define TYPE_MOD_STATIC 0x40000000 /* Static function or variable */ #define TYPE_MOD_NO_MASK 0x20000000 /* The nomask => not redefineable */ #define TYPE_MOD_PRIVATE 0x10000000 /* Can't be inherited */ #define TYPE_MOD_PUBLIC 0x08000000 /* Force inherit through private */ #define TYPE_MOD_VARARGS 0x04000000 /* Used for type checking */ #define NAME_INITIALIZED 0x04000000 /* only used for variables */ #define TYPE_MOD_VIRTUAL 0x02000000 /* can be re- and cross- defined */ #define TYPE_MOD_PROTECTED 0x01000000 /* cannot be called externally */ #define TYPE_MOD_XVARARGS 0x00800000 /* accepts optional arguments */ #define TYPE_MOD_NOSAVE 0x00400000 /* vars: can't be saved */ #define FUNSTART_MASK 0x000fffff /* Function not inherited: unsigned address of the function code relative * to the begin of the program block (struct program_s->program). */ #define NAME_CROSS_DEFINED 0x00080000 /* Two functions with the same name inherited from A and B into C. * The compiler usually prefers A, and the value 'flags & INHERIT_MASK' * (in bias-0x20000 representation) stored as B.offset.func is the * difference between to the real functions index (also see below). * A special use is A uses a function from B. The function is marked * in A as undefined, but the compiler will use cross-defining in C * to resolve the function calls in A to call the function in B. */ #define INHERIT_MASK 0x0003ffff /* Function inherited: unsigned index of the parent program descriptor * in the struct program_s->inherit[] array. In the parent program, this * function may again be inherited. * Function crossdefined: signed difference (in bias-0x20000 notation) * from the current function index to the real function index * (real = this + offset). */ #define NAME_HIDDEN 0x00000800 /* Not visible for inheritance */ #define NAME_PROTOTYPE 0x00000400 /* Defined by a prototype only */ #define NAME_UNDEFINED 0x00000200 /* Not defined yet */ #define NAME_TYPES_LOST 0x00000100 /* inherited, no save_types */ #define CROSSDEF_NAME_OFFSET(flags) \ (((flags) & INHERIT_MASK) - ((INHERIT_MASK + 1) >> 1)) /* For a given NAME_CROSS_DEFINED function, extract the difference to * the real functions index from the flags. */ #define GET_CROSSDEF_OFFSET(value) \ ((value) - ((INHERIT_MASK + 1) >> 1)) /* The index difference <value> in bias-notation is converted back into * the real number. The difference to CROSSDEF_NAME_OFFSET is that <value> * is supposed to be the plain number, not a real function flags word. */ #define MAKE_CROSSDEF_OFFSET(value) \ ((value) + ((INHERIT_MASK + 1) >> 1)) /* Convert the index difference <value> into the bias-notation for storage * in the function flags. */ #define MAKE_CROSSDEF_ROFFSET(value) \ ((value) - ((INHERIT_MASK + 1) >> 1)) /* Convert the reverse index difference <value> into the bias-notation for * storage in the function flags. * TODO: What is this exactly? */ /* --- Function header --- * * The bytecode for every function is preceeded by a header with the name * and information about arguments and types: * * struct fun_hdr { * shared char * name_of_function; (4 Bytes) * byte return_type; (1 Byte) * --> byte number_formal_args; (1 Byte) * Bit 7: set if the function has a 'varargs' argument * TODO: some code makes use of the fact that this makes the number negative * Bit 6..0: the number of arguments * byte number_local_vars; (1 Byte) * This includes the svalues needed for the break stack for * switch() statements. * bytecode_t opcode[...] * } * * The function address given in the program's function block points to * .number_formal_args. * * Since structs introduce uncontrollable padding, access of all fields * is implemented using macros taking the 'function address', typedef'd * as fun_hdr_p, as argument and evaluate to the desired value. * * WARNING: if ALIGN_FUNCTIONS is not defined, the name_of_function pointer * is not aligned properly to be directly used as a pointer! * * Note: Changes here can affect the struct lambda layout and associated * constants. * TODO: the other fields should have proper types, too. * TODO: the whole information should be in a table, and not in the * TODO:: bytecode. See struct program_s. */ typedef bytecode_p fun_hdr_p; /* TODO: Make this a void* for now? */ #define SIMUL_EFUN_FUNSTART ((bytecode_p) -1) /* Special value used for inter_pc and funstart to mark simul_efuns * for dump_trace(). * TODO: Invent something similar for efun/operator closures? */ #define EFUN_FUNSTART ((bytecode_p) -2) /* Special value used for funstart to mark efuns for dump_trace. */ #define FUNCTION_NAMEP(p) ((void*)((char *)p - sizeof(char) - sizeof(char *))) #define FUNCTION_TYPE(p) (*((unsigned char *)((char *)p - sizeof(char)))) #define FUNCTION_NUM_ARGS(p) EXTRACT_SCHAR((char *)p) #define FUNCTION_NUM_VARS(p) (*((unsigned char *)((char *)p + sizeof(char)))) #define FUNCTION_CODE(p) ((bytecode_p)((unsigned char *)p + 2* sizeof(char))) #define FUNCTION_FROM_CODE(p) ((fun_hdr_p)((unsigned char *)p - 2* sizeof(char))) #define FUNCTION_HDR_SIZE (sizeof(char*) + 3) /* --- struct variable_s: description of one variable * * This structure describes one variable, inherited or own. * The type part of the .flags is used just by the compiler. */ struct variable_s { char *name; /* Name of the variable (shared string) */ fulltype_t flags; /* Flags and type of the variable. * If a variable is inherited virtually, the function flag * TYPE_MOD_VIRTUAL is or'ed on this. */ }; /* --- struct inherit: description of one inherited program * * The information about inherited programs ("objects") for a given * program is stored in an array of these structure, and the inherited * programs are accessed from the childs' program code by indexing this array. */ struct inherit_s { program_t *prog; /* Pointer to the inherited program */ unsigned short function_index_offset; /* Offset of the inherited program's function block within the * inheriting program's function block. */ unsigned short variable_index_offset; /* Offset of the inherited program's variables block within the * inheriting program's variable block. * The NON_VIRTUAL_OFFSET_TAG marks the variables of non-virtual * inherits temporarily during compiles. */ unsigned short inherit_type; /* Type of inherit */ # define INHERIT_TYPE_NORMAL 0x0000 /* Type: Normal inherit */ # define INHERIT_TYPE_VIRTUAL 0x0001 /* Type: Virtual inherit */ # define INHERIT_TYPE_EXTRA 0x0002 /* Type: Extra inherit added by * copy_variables() */ # define INHERIT_TYPE_DUPLICATE 0x0004 /* Flag: Duplicate virtual inherit */ unsigned short inherit_depth; /* Depth of inherit */ }; /* --- struct include_s: description of one include file * * This structure describes one include file used to compile the * program. */ struct include_s { char *name; /* Name (shared string) as it was found in the program. First and last * character are the delimiters - either "" or <>. */ char *filename; /* Actual filename (shared string) of the include file, in compat mode * without leading slash. */ int depth; /* The absolute value is the include depth, counting from 1 upwards. * If the include did not generate code, the value is stored negative. */ }; /* --- struct program_s: the program head structure * * This structure is actually just the head of the memory block * with all the programs data. */ /* TODO: We seem to need a datatype for program offsets (right now: unsigned short). * TODO:: It shows up in quite a lot of places. * TODO: Replace the on program_s structure with a header/data couple. The * TODO:: header keeps vars likes refs, blueprint, swap_num, *data; and the * TODO:: data keeps the static bytecodes and similar. This way we can swap * TODO:: the program even for clones. */ struct program_s { p_int ref; /* Reference count */ p_int total_size; /* Total size of the memory block allocated for this program. */ #ifdef DEBUG p_int extra_ref; /* Used to verify ref count */ #endif bytecode_p program; /* The binary instructions */ char *name; /* Name of file that defined prog (allocated, no leading '/', * but a trailing '.c') */ object_t *blueprint; /* Counted pointer to the (possibly destructed) blueprint object, * or NULL if the blueprint has been destructed in a previous cycle. */ int32 id_number; /* The id-number is unique among all programs and used to store * information for this program without actually pointing to * the structure. */ mp_int load_time; /* When has it been compiled ? */ linenumbers_t *line_numbers; /* Line number information, NULL when not swapped in. * If swapped out, the data is stored in the swap file at * .swapnum+.total_size . */ unsigned short *function_names; #define PROGRAM_END(program) ((bytecode_p)(program).function_names) /* Lookup table [.num_function_names] function-index -> offset of * the function within the functions[] table. function_names[] is * stored right after the the bytecode within the same allocation * unit. * The table is sorted in descending order of the pointers(!) * of the shared function name strings. If the program contains * redefinitions of inherited functions, the entry here points * to the redefinition, the inherited function can then be * found from there. */ funflag_t *functions; /* Array [.num_functions] with the flags and addresses of all * functions, inherited and own. * Nameless functions (those without an entry in function_names[]) * are collected at the end of the table. * TODO: Instead of hiding the function information in the bytecode * TODO:: it should be tabled here. */ char **strings; /* Array [.num_strings] of the shared strings used by the program. * Stored in reverse order at the end of the array are the pointers * to the names of all included files which generated code - these * are used when retrieving line numbers. */ variable_t *variable_names; /* Array [.num_variables] with the flags, types and names of all * variables. */ include_t *includes; /* Array [.num_includes] of descriptors for included files. */ inherit_t *inherit; /* Array [.num_inherited] of descriptors for (directly) inherited programs. */ unsigned short flags; /* Flags for the program: */ # define P_REPLACE_ACTIVE 0x0001 /* This program will be or has been replaced at least once. * As this flag is never reset, the caller must check the * obj_list_replace if his object is affected or not. */ # define P_NO_INHERIT 0x0002 /* Program must not be inherited */ # define P_NO_CLONE 0x0004 /* No clones allowed */ # define P_NO_SHADOW 0x0008 /* No shadows allowed */ short heart_beat; /* Index of the heart beat function. -1 means no heart beat */ /* The types of all function arguments are saved in the * .argument_types[]. To look up the arguments types for * function <n>, retrieve the start index from the .type_start[] * as .type_start[n]. If this index is INDEX_START_NONE, the function * has no type information. * * Both arrays will only be allocated if '#pragma save_types' has * been specified. */ vartype_t *argument_types; unsigned short *type_start; /* TODO: Some code relies on this being unsigned short */ # define INDEX_START_NONE 65535 p_int swap_num; /* The swap number (swap file offset) for an unswapped program * It is set to -1 if it hasn't been swapped yet. */ /* * And now some general size information. */ unsigned short num_function_names; /* Number of function names listed in the lookup table. * This number is <= .num_functions as the redefinition of * of inherited functions does not need an additional name * entry. Also, private functions have no visible name. */ unsigned short num_functions; /* Number of functions (inherited and own) of this program */ unsigned short num_strings; /* Number of shared strings (including filenames) used by the program */ unsigned short num_includes; /* Number of stored include filenames */ unsigned short num_variables; /* Number of variables (inherited and own) of this program */ unsigned short num_inherited; /* Number of (directly) inherited programs */ }; /* --- struct linenumbers_s: the linenumber head structure * * This structure is the head of the memory block with the linenumbers * data. */ struct linenumbers_s { size_t size; /* Total allocated size of this structure */ bytecode_t line_numbers[1]; /* Array [.size - sizeof(.size)] with the delta-compressed * line number information. This is actually one byte too many, but * that simplifies the swapping code. */ }; /* --- struct function_s: Function description * * Structures of this type hold various important pieces of * information about a function. * The compiler uses this structure to collect the function information * of newly defined and inherited functions, of which the former will also * be compiled into the function header * The simul_efun module uses this structure to look up quickly functions. */ struct function_s { char *name; /* Name of function (shared string) */ union { uint32 pc; /* lfuns: Address of function header */ uint32 inherit; /* Inherit table index from where inherited. */ int32 func; /* For cross-defined functions, this is the index offset * to the original function in bias-0x200000 notation. * Semantik: real-index = this-index + offset. * The offset is also stored in the function flags in * the program_t.functions[] array. * * simul_efun.c also uses this field as a 'next' * index in the simul_efun function table for * functions that have been discarded due to a * change in the number of arguments. */ function_t *next; /* used for mergesort */ } offset; funflag_t flags; /* Function flags */ vartype_t type; /* Return type of function. */ unsigned char num_local; /* Number of local variables */ unsigned char num_arg; /* Number of arguments needed. */ # define SIMUL_EFUN_VARARGS 0xff /* Magic num_arg value for varargs */ }; /* --- Bytecodes used to encode line numbers --- * * The line number information is a block of bytecode associating small * blocks of bytecode with the generating line number ranges. * * To save space, the information uses a delta compression, necessitating * to read all information from the beginning and counting up a line number * and bytecode offset counter to retrieve a specific bytecode/line number * association. * * The names of included files are stored in the order of appearance * at the end of the string table. Multiple included files are stored * several times. */ /* Bytecodes 0x00..0x3a: The amount of program bytes generated for the * current line; this entry is complete with that. */ #define LI_MAXOFFSET 0x3b /* The current line generated 0x3b bytes (more), but the entry * is not complete. This code used after the linecodes 0xC0..0xFF. */ #define LI_BACK 0x3c /* Followed by unsigned byte <off>. * The current line counter was set back by <off>+1. */ #define LI_INCLUDE 0x3d /* The following program bytes were generated from the next included * file. Reset the current line number to 0. * LI_INCLUDEs can be nested. */ #define LI_INCLUDE_END 0x3e /* End of the included program part. */ #define LI_L_RELOCATED 0x3f /* Followed by bytes <hi> <lo> LI_L_RELOCATED * The lines were relocated from a line by a delta to the current_line-2. * The delta, an signed int16, is encoded in <hi> and <lo>. */ /* Bytecodes 0x40..0x7f encode actual code/line number relations: * codesize: 1..8 * line incr: 2..9 * -> bytecode = (lineincr+6) << 3 | (codesize-1) * Each code is a complete entry. */ #define LI_RELOCATED 0xc0 /* Bytecodes 0x80..0xDF: a small line relocation. * The lines were relocated from a line by a delta -0x20..0x1F * to the current_line-2. * The delta is encoded as delta+LI_RELOCATED. */ #define LI_SMALL_REL 0x20 /* The allowed magnitude of a relocation to fit into a small * relocation code. */ #define LI_MAXEMPTY 0x20 /* Bytecodes 0xE0..0xFF: 'use' a number of lines between 1 and 32 * -> bytecode = (0x100 - lines) * The entry is not complete. */ /***************************************************************************/ #endif /* EXEC_H__ */