4. wamcc Method
The common characteristics of the above three suggested methods are that, in a single module, a large function is triggered, and the C compiler compilation is quite painful. If possible, additional module calls are more costly than internal module calls. Therefore, the way a program is decomposed into modules not only affects the Compilation Time, but also the execution time, which is significantly inversely proportional.
The second version of our wamcc system aims to translate the AWAM Branch to a local code jump. Due to the forced decomposition into several functions, these redirects should reach the internal code block of a function. To generate direct branches, we must determine static labels (during compilation), rather than dynamic (during execution ). The "compiler + connector" combination is suitable for function addresses. In wamcc, the method taken is to insert a tag and in each function enter thanks to the ASM (...) command. To manipulate the address of these tags, say l, you just need to fool the computer and make it believe l is an external function, declare it as a prototype function L and use the symbol L (the name of the function in C is its address ). Then, the compiler generates a directive with holes that will be filled by the connector based on all the knowledge of inserting labels (internal and external. The cost of calling an additional module is exactly the same as that of calling a module. The following code is generated to complete our favorite example:
void label_p(); /* prototypes */void label_p1();void label_q();void label_r();#define Direct_Goto(lab)#define Indirect_Goto(p_lab)void fct_p() /* p:- q,r. */{ asm("label_p:"); push(CP); /* allocate */ CP=label-p1; /* call(q) */ Direct_Goto(label_q); /* : */}void fct_p1(){ asm("label_p1:"); pop(CP); /* deallocate */ Direct_Goto(label_r); /* execute(r) */}void fct_q() /* q. */{ asm("label_q:"); Indirect_Toto(CP); /* proceed */}
Only two macros need to implement branches directly or indirectly. They depend on their architecture. For example, for a assume that we have the following resources:
* Direct_goto (LAB) simple call Tag function.
* Direct_goto (p_lab) calls a function. The name (Address) is stored in p_lab.
In fact, a function call instruction on a assume server controls a given address (such as a jump) and initializes the processor's continue pointer. This command is a simple and fast jump because of the RISC architecture. Without accumulation, it can be used for Branch (in fact, it is not important to continue pointer update, because we know whether this is a real function call, rather than just a jump ). This avoids the need to insert the assembly code of the jump command. In addition, the access code of the RISC branch instruction is close to the current instruction, and the function call instruction does not follow this restriction. Due to the module decomposition, this requires a deep access to the code. Let's finally notice the advantages of leaving the C compiler that generates function calls and allowing it to optimize the latency slot command pipeline [2. Summary:
* Direct jump is executed as quickly as possible because they are translated into the machine code jump (or enters the function call with the same overhead in the case of a RISC architecture ).
* The number of calls of an additional module cannot exceed that of an internal module.
* Compared to previous methods, all predicates of a single module are compiled into a single function, this method produces the same number of functions (Head and first target count) as in the clause body ). Therefore, the generated code compilation speed is faster (see later ).
* Each function has only one direct start point. Therefore, only jump has passed the preface. To allow local variables, an intermediate function in an array is defined to start computing. Some (large enough) space is retained in the C Control Stack. In this way, the C Stack pointer SP points to the end of the array. Local variables will be allocated in this array (see below ).
* This is just the assumption of this method. Therefore, the preface does nothing except the decreasing sp. This is generally the case. Except in a few cases, the machine's C compiler does not reference local variables through sp, but uses another FP register (frame pointer) to set the function entry to sp. This is intended to help the debugger, which may disable this operation based on compiler options. In this case, it is impossible to always generate an assembly command to initialize this FP register.
* Real function calls may exist within these pseudo functions. In particular, most macros related to WAM commands are extended to the wamcc library. This may change the code size (Compilation speed) and cause (small) Damage to the execution speed.
Now let's describe in detail the Code required for the above calculation. Suppose the first predicate (usually the top layer) Address p_lab:
#include<setjmp.h> jmp_buf jumper; void Label_Success(); void Label_Fail(); Bool Call_Prolog(WamCont p_lab) { Create_Choice_Point(); ALTB(B)=Label_Fail; CP=Label_Success; ret_val=setjmp(jumper); if (ret_val==0) Call_Next(p_lab); Delete_Choice_Point(); return ret_val==2;}void Call_Next(WamCont p_lab){ int t[1024]; Indirect_Goto(p_lab);}void Call_Prolog_Success(void){ asm("Label_Success:"); longjmp(jumper,2);}void Call_Prolog_Fail(void){ asm("Label_Fail:"); longjmp(jumper,3);}
The predicate has been executed for the call_prolog function. The address is p_lab. It starts to create a selection point to record the Branch Address after the failure (Label failure. CP (call_prolog) indicates the code executed after the predicate is successful, which is initialized by label_success. Finally, execute a setjmp command to return the command. Call the call_next function in the C stack to reserve sufficient space for possible local variables (against: array t Declaration. Control is then transferred to the predicate, which will be executed as carefully as before. When the call succeeds (or fails), the control is transferred to label_success (or label_failure ). Set the second parameter of longjmp to value.
2 (or 3) is returned to the call_prolog function.
2. Execute a jump or function call (delay slot) immediately after the instruction in some server-defined CPUs ). This is because the pipeline is ready. The compiler tries to use this feature after a branch by moving a related instruction. If this is not possible, a NOP command will be generated.