C Compiler anatomy _6.2 assembly code Generation _ Register Management

Source: Internet
Author: User

In the computer, the CPU speed is much faster than the memory speed, the compiler should make the best use of register resources, reduce unnecessary access to memory, thereby increasing the compiler generated by the assembly code run speed. In the intermediate code generation phase, the UCC compiler holds a temporary variable T to hold the shape as "t:a+b;" The value of the common subexpression of the assembly code generation, the UCC compiler will try to keep the values of these common sub-expressions in registers, which can be obtained directly from the corresponding registers when reuse is required. However, the resources of the registers in the CPU are very limited, on the 32-bit x86 chip, assembler programmer available registers are {EAX,EBX, Ecx,edx, ESI,EDI,ESP,EBP}, but register ESP is usually used to point to the top of the stack, The EBP is typically used to point to the bottom of the activity record. The real choice of registers is only {Eax,ebx,ecx,edx,esi,edi} These 6, when the number of public sub-expressions more than the available registers, we have to write some of the value of the Register (WriteBack) to the "temporary variable corresponding to the memory unit", To free up the available registers to hold other values. Of course, if the value in the register to be written back is no longer needed, or the memory is loaded into the CPU and it doesn't change, we don't have to waste time doing a writeback operation. This is somewhat similar to the page replacement algorithm in the operating system request paging. To simplify the allocation algorithm for registers, the UCC compiler allocates registers only for temporary variables, which means we want to reuse the shape as "t:a+b;" as much as possible. This is a common sub-expression. We still use a simple example to illustrate the UCC compiler register allocation, 6.2.1, the code of the 2nd to 8th behavior function f, the code of the 9th to 13th behavior function g, after the function f 5th line to complete the assignment of S3, we intentionally in the 6th and 7th line again using the common subexpression (a+b) and ( C+D). The 32nd to 46th line is the assembly code corresponding to the function F, and the 48th to 58th line is the assembly code corresponding to the function G.


Figure 6.2.1 Example of register allocation

From the middle code in line 15th to 23rd of Figure 6.2.1, we can find that there are 3 temporary variables in function f, each for storing integers, a total of 12 bytes of stack space, and the 34th line of "Subl $%ESP" to open 12 bytes of memory in the stack to hold these 3 temporary variables. Although we loaded the global variable A from memory into the register eax with the "MOVL a,%eax" directive on line 36th, after executing the "addl b,%eax" command on line 37th, the value of the temporary variable "EAX" is saved in the Register t0:a+b. The register EAX is then assigned to T0 until the t0 is no longer in use, or when the register is not sufficient. It can be found that in lines 42nd and 45th, we have reused the common subexpression (a+b) stored in the register eax, and in line 44th we save the value of "(a+b) + (c+d)" in the register edx. In contrast to the 57th line of function g, we put the value of "(a+b) + (c+d)" In the register eax, because after line 57th, the temporary variable "t0:a+b" of line 25th is no longer used in the basic block BB1.

It is also important to note that an intermediate code may correspond to the assembly instructions of several strips, such as "T0:A+B;" in line 16th of 6.2.1. For the corresponding figure 6.2.1 36th and 37 of this two-article assembly instruction.

    If you extend the range of register allocations to well-known variables (that is, global, static, and local variables named by the C programmer), you can further reduce the number of memory accesses. For example, when we read the global variable A into a register R1, and then need the value of a again, it can be obtained directly from the register R1, no need to access the memory. However, because C programmers can access "well-known variables" through variable names, they can also be accessed indirectly through the variable's address addr, which is more flexible. If you access global variable A in the form of *addr, we may load the value of a into another register R2, and when you write to global variable A, there may be an inconsistency between the contents of the register R1 and R2. To avoid this problem, the compiler needs to do a more complex analysis. While temporary variables are generated only by the compiler and not visible to C programmers, the UCC compiler typically assigns only temporary variables once, with the example 6.2.1 the temporary variable on line 15th to 29th. With one exception, the UCC compiler does have multiple assignments for temporary variables when dealing with conditional expressions such as "A>0?B:C", which we'll discuss later.

For the sake of simplicity, avoid complex data flow analysis, the UCC compiler allocates registers only for temporary variables. Next, let's take a look at the function for allocating registers getreginternal,6.2.2 line 2nd to 18th shows that the parameter width of line 2nd represents the width of the required register, which can be 1 bytes (corresponding to the Register AL, Cl, and DL) or 2 bytes (corresponding to the register ax, Cx,dx,bx,si and DI), can also be a 4-byte register (corresponding to Eax,ecx,edx,ebx,esi and EDI). Line 12th calls the Findemptyreg function to get a register that is not yet allocated, and if there is no empty register, we will select a register to write back through the Selectspillreg function on line 14th. The value saved in the register is then written back to memory by the Spillreg function in line 15th, so that the register can be allocated again. The 17th line sets the corresponding flag bit in the variable Usedregs, indicating that the I register has already been used. The UCC compiler will usedregs the variable to 0 before generating assembly instructions for an intermediate code, which can be seen later when analyzing the function Emitblock. The variable usedregs on line 1th is used to record the individual registers that have been assigned to the current intermediate code.


Figure 6.2.2 Getreginternal ()

    Figure 6.2.2 the 19th to 28th line of the function Findemptyreg is used to find the unassigned register, the 22nd line of the condition "x86regs[i"!=null "will exclude the two stack registers ESP and EBP, The condition of line 23rd indicates that the value of the temporary variable is not saved in the register (empty register Emptyregister), and the condition of line 24th indicates that the register has not been assigned to the current intermediate instruction. If no empty registers are found, the No_reg is returned on line 27th. When all the registers are allocated, we need to select a register to be retired through the function Selectspillreg of line 29th to 48th. This is much like the paging algorithm in the request paging system, we need to eliminate some pages by FIFO or LRU algorithms. The UCC compiler makes a choice based on the sum of references to the temporary variables for registers, writes back the register with the least number of references, and the 32nd to 45th Line handles this. The 48th to 58th line of the function Spillreg writes the values of the temporary variables stored in the register back into memory, which is often referred to as "register overflow". Figure 6.2.2 The 52nd line of "P->NEEDWB" is not 0 o'clock, indicating that the temporary variable p in the Register and memory values are inconsistent, and "P->ref > 0" indicates that the temporary variable p also needs to be used again, when both conditions are met, We will call the 53-line Storevar function to generate the write memory instruction. Of course, in the current version of the UCC compiler, a register typically holds only one temporary variable's value, so the loop body of the two while statements in lines 38th and 50th is executed only once. In other words, there will be no more than 1 elements on the list reg->link of the linked list X86regs[i]->link and 49th rows in line 37th. Line 51st sets P->reg to NULL, indicating that the value of the temporary variable p is no longer saved in the register.

To speed up the operation of floating-point numbers, Intel also provides a floating-point coprocessor x87,x87 provides a stack of multiple floating-point registers, but for simplicity's sake, the UCC compiler actually uses a floating-point register at the top of the stack to hold a floating-point temporary variable. The pointer variable x87top in the UCC compiler is used to point to the temporary variable, and when x87top is not NULL, the value that represents the corresponding temporary variable is saved at the top of the coprocessor X87 stack.

Static Symbol X87top;

On this basis, let's look at the function "Generating assembly code for the base block", as shown in line 1th to 24th of emitblock,6.2.3, and the while loop of line 3rd iterates through all the intermediate code in the base block. The Emitirinst function called in line 10th implements the operation of generating assembly instructions for the intermediate Code inst. The 25th to 29th row of Figure 6.2.3 can be found, this is by looking up the table to implement the corresponding function of the call, the 27th row of the table emitter in the form of the 33rd row of Emitjump function name. The Emitjump function in line 33rd to 38th is used to generate assembly code for unconditional jump instructions.


Figure 6.2.3 Emitbblock ()

While we are reusing common sub-expressions within the same base block during the intermediate code generation phase, when we encounter the conditional expression "(A>0?B:C)", there is a "temporary variable within a basic block that may be used in another base block", as shown below. The temporary variable t0 is assigned to multiple base blocks, and we need to write back the value t0 the temporary variable when we leave the base block BB3 through "gotoBB5".

D = (a>0?b:c) +1;

The corresponding intermediate code/////////////////

if (a <= 0) goto BB4;

BB3:

T0 = b; Assigning a t0 to a temporary variable

Goto BB5;

BB4:

T0 = c; Assigning a t0 to a temporary variable

BB5:

T1:t0 + 1;

d= T1;

Therefore, when the control flow to go through the jump statement to leave the base block, or encounter function calls, we want to write back to the register operation, figure 6.2.3 8th call Savex87top used to write back X87 stack top register, and the X86 register write-back operation, we will defer to Emitjump, Functions such as Emitbranch, Emitindirectjump, and Emitcall, respectively, correspond to "unconditional jump", "Conditional jump", "Jump by Jump Table" and "function call". As specified in the C standard, when a function is called, the keynote function simply writes back the 3 registers of EAX, ECX, and edx. When we encounter a jump statement, we call the Clearregs function to write back and forth "eax, EBX, ECX, edx, ESP and EBP" 6 registers, 6.2.3 the 37th line.

The code of the function savex87top in line 48th to 57th of Figure 6.2.3, the Putasmcode function called by line 53rd produces the write-back instruction of the floating-point number, we will parse the Putasmcode function in subsequent chapters, and the 56th row x87top is null to indicate that no temporary variables need to be written back.

We also find that when the control flow leaves the above basic block BB4, we also write the value of the temporary variable t0 back into memory, which can be implemented by Savex87top functions called Clearregs and 23rd rows of the 22nd row of the graph 6.2.3. When we are in Figure 6.2.3 10th Act an intermediate code generates assembly instructions, the reference count of each operand can be reduced by 1 on line 14th to 18th, which affects the Selectspillreg function of the selection overflow register described earlier.

in subsequent chapters, we will discuss functions such as Emitbranch, Emitindirectjump, and Emitcall, which generate assembly instructions for intermediate code.

C Compiler anatomy _6.2 assembly code Generation _ Register Management

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.