C Compiler anatomy _6.3.1 assembly code Generation _ the main process of generating assembly code from intermediate directives

Source: Internet
Author: User

6.3.1 The main process of generating assembly code from intermediate directives

In this section, we can focus on "how to translate an intermediate code into assembly code". The intermediate code for the UCC compiler is a four-tuple, including operators and 3 operands, as shown below.

< operator opcode, destination operand dst, source operand SRC1, source operand src2>

Of course, some intermediate code only need to use opcode and DST, for example, the unconditional jump instruction "goto BB2;" There is no need for SRC1 and SRC2. To facilitate the generation of assembly code, the UCC compiler defines many templates for assembly instructions in UCL\X86LINUX.TPL. On the X86 platform, the assembly instruction for the unconditional jump instruction is jmp, and the corresponding instruction template is as follows:

Unconditional Jump

TEMPLATE (x86_jmp, "JMP%0")

Where x86_jmp is the number of the template "jmp%0", the corresponding template can be found by number. The "%0" in the template acts as a placeholder, representing the No. 0 operand, the purpose operand, DST, which is replaced by the destination operand DST when the assembly code is generated, while "%1" represents the 1th operand, which is the source operand SRC1, and "%2" represents the 2nd operand, which is the SRC2 of the source operand. The macro DST, SRC1, and SRC2 in line 29th to 31st of Figure 6.3.1 represent these 3 operands in the intermediate instruction inst respectively. Assuming that the goal of the jump is BB2, we can generate the assembly instruction "JMP BB2" according to the template "jmp%0".

Let's take a look at an example of how the operator of the intermediate instruction opcode to find the corresponding assembly instruction template number. At the intermediate code level, the UCC compiler represents the addition instruction, but at the assembly level, the assembly instructions corresponding to the integer addition and floating-point addition are not the same, as shown in line 22nd to 25th of 6.3.1. In order to be able to quickly find the corresponding assembly instruction template number through the operator add of the intermediate code, the templates in Ucl\x86linux.tpl are arranged in a certain order. For example, when we want to do the addition of two double type floating-point number, the operator in the inter-command opcode is add, the type is double, through the 1th to 15th line of the function TypeCode, we can get a double corresponding to the type encoding F8. With add and F8 as the macro parameter, through the 27th line of Macro Asm_code (), we can get the corresponding template number x86_addf8,6.3.1 26th line is shown.


Figure 6.3.1 the flow from the intermediate instruction to the assembly code

With the template number X86_addf8, we can look up the table in the 33rd line of Figure 6.3.1 and get the corresponding assembly instruction template "Faddl%2". Of course, before we call the function Putasmcode of line 32nd, we need to allocate the necessary registers for the operands in the intermediate instruction first. Figure 6.3.1 The While loop on line 35th to 56th is used to process the assembly instruction template with the form "Faddl%2", and on line 43rd to 50th we replace the placeholder "%0", "%1" and "%2" with the corresponding operand name. If the value of the operand has already been loaded into the register, the name of the corresponding register is output in line 46th, such as "%eax"; otherwise, in line 48th we call the function getaccessname that we analyzed in section 6.1, the name of the output operand, such as "number" or "" (%EBP )”。 The 41st to 42nd line deals with strings in the template, such as "%%eax", in the assembly instruction of AT/T, the register name is preceded by a "%", because in the UCC assembly instruction template, the symbol "%" has been treated as an escape character, so "percent" is used to denote "%" itself. The "%%eax" in the template is processed by the Putasmcode function on line 32nd, and the "EAX" will be output from line 52nd to 53rd with the "%eax".

It is important to note that although the original author of UCC tries to arrange the assembly instruction template in X86linux.tpl in an orderly manner, there are some intermediate instructions that cannot be used to find the corresponding template number using the macro Asm_code in line 6.3.1 27th. The UCC compiler makes special processing when generating assembly code, such as the intermediate Instruction Inc for "self-add", and we will find the corresponding template number with "X86_inci1 +typecode (Inst->ty)" Instead of the macro Asm_code, as shown below.

static void Emitinc (Irinst inst) {

/****************************************

TEMPLATE (X86_INCI1, "INCB%0")

TEMPLATE (X86_INCU1, "INCB%0")

TEMPLATE (X86_inci2, "INCW%0")

TEMPLATE (X86_INCU2, "INCW%0")

TEMPLATE (X86_inci4, "incl%0")

TEMPLATE (X86_incu4, "incl%0")

TEMPLATE (X86_INCF4, "Fld1;fadds%0;fstps%0")

*****************************************/

Putasmcode (x86_inci1+ typecode (inst->ty), INST->OPDS);

}

In section 6.2, we introduce the function getreginternal for allocating registers, on which we further analyze the function allocatereg,6.3.2 "allocating registers for I4 or U4 type operands in intermediate directives". The 2nd row of the parameter, index, indicates which operand to allocate the register to, with a value range of {0,1,2}, corresponding to DST, SRC1, and SRC2, respectively. The UCC compiler allocates registers only for temporary variables, and this is checked on line 5th. If the current operand is a temporary variable of "already assigned register", the flag bit is set on line 9th, indicating that the corresponding register will be used for "translation of the current intermediate instruction". If we need to allocate a register for the destination operand, and the source operand SRC1 is already in the register, and after the current intermediate instruction, the source operand SRC1 is no longer in use, you can let DST directly reuse the SRC1 register, which is handled in line 12th to 18th. For other cases, we call the Getreg function on line 19th to allocate a 4-byte register and load the source operand into the register with the MOV instruction on line 21st, and call Addvartoreg in line 23rd to add the temporary variable p to the Register Reg's list Reg->link. Within the UCC compiler, only the temporary variable p is added to the list Reg->link of register Reg, which means that we do assign the Register Reg "long term" to the TEMP variable p. In the current version of UCC, the linked list holds up to only one temporary variable.


Figure 6.3.2 Allocating registers for the I4 or U4 type operand in the intermediate instruction

The code of the function Addvartoreg 6.3.2 the 25th to 32nd Line, the 29th to 30th line implements the list insert operation, and the 31st line is used to record "in which register the temporary variable is stored". If a variable is stored in a register, when we modify the value in the register, we do not immediately write back to memory, but instead set a flag bit NEEEDWB, which means that we need to write back writeback later, which can be done by the function Modifyvar of line 36th.

When the operand is a floating-point number, we need to assign it a floating-point register, and for the sake of simplicity, the UCC compiler uses only the X87 stack top register. The management of floating-point registers is not done through functions such as Getreg or Allocatereg, and we specialize in floating-point numbers when the assembly code is generated. When the operand is of type I1 or U1, we obtain a single byte register by Getbytereg of the 53rd row of the figure 6.3.3, and when the operand is I2 or U2, the double-byte register is obtained by 6.3.3 of the 56th row of the diagram Getwordreg.

Next, we use the Emitmove function as an example to discuss the intermediate directives that the function handles as shown below.

<mov, DST, SRC1, null>

The intermediate instruction executes "DST = SRC1;" The assignment operation, which is shown in code 6.3.3 of the function Emitmove.

The 4th to 7th line deals with the assignment between floating-point numbers, and we call the Emitx87move function on line 5th to produce the corresponding assembly code. For the assignment between struct objects, we deal with the Emitmoveblock function on line 9th. We will analyze them later with these two functions.


Figure 6.3.3 Emitmove ()

When DST and SRC1 are char (or unsigned char), we execute the code on line 13th to 22nd of the diagram 6.3.3. According to the addressing requirements of the X86 assembly instruction, the two operands of the same assembly instruction may not be in memory. So, when we face the following assignment statement "a = B;" , we are going to get a single byte register {AL,CL,DL} on line 18th through the Getbytereg function, then generate the MOVB instruction on line 19th, load the source operand b from memory into the register, and then generate another MOVB instruction on line 20th. By passing the value in the register to the destination operand A, the use of the Register has ended, and we have not assigned the register "long term" to a or B. Of course, if the source operand is an integer constant, we generate a MOVB instruction on line 16th, such as the following "Movb $49,a".

Char A, B;

A = b;

A = ' 1 ';

The corresponding assembly code//////////

Movb B,%al//a = B;

Movb%al, a

Movb $49, A//a = ' 1 ';

For an arithmetic expression of the form "a+b" or "~a", when the semantic check, UCC implicitly promotes the integer operand below int to int, so the type of the public subexpression is actually I4 or U4. Figure 6.3.3 the 32nd to 48th Line is used to handle the assignment operation "DST=SRC1;" of the I4 or U4 type. Figure 6.3.3 Line 34th is used to deal with the shape as "x = 2015;" , X can be a temporary variable t and a well-known variable a. Line 36th to 37th calls the Allocatereg function to allocate 4-byte registers for DST and SRC1. The 38th to 43rd Line is used to deal with the shape of "a=b;" The assignment between the famous variables, according to the x86 assembly instruction, we can get a register in line 41st through the Getreg function to do the relay, resulting in two MOVL instructions. Line 45th is used for processing a shape such as "t = x;" or "x = t;" Assignment, where T is a temporary variable, because the value of the temporary variable is in the register, at which point we produce a MOVL assembly instruction. When DST is a temporary variable, due to "t=x;" Assignment, the contents of the T corresponding register will change, and we will call the Modifyvar function on line 48th to set the write-back flag bit.

The 23rd to 31st line of Figure 6.3.3 is used to handle the assignment of the I2 or U2 type "DST=SRC1;" The basic process is similar to line 13th to 22nd, and we are no longer verbose. Next, we analyze the two functions of emitx87move and Emitmoveblock used in 16.3.3.

Figure 6.3.4 the 1th to 25th line of the function emitx87move is used to implement F4 or F8 floating-point assignment "DST = SRC1;" When the value of SRC1 is not in the x87 stack top register, we write back the value of the current stack top register through the savex87top of line 4th, The assembly instruction is then generated on line 6th and the SRC1 is loaded from memory to the x87 stack top register. When you execute to line 8th, the value of SRC1 is saved in the top register, and when DST is not a temporary variable, if SRC1 is a temporary variable and in DST = SRC1; After the SRC1 is also used, we will transfer the value of the x87 stack top register to DST in line 11th, but do not eject the top register, that is, the value of the SRC1 is still stored in the register, otherwise the value of the x87 stack top register is transferred to DST on line 17th, and then the stack top register to the air-to-air stack, The x87top is then set to null on line 18th, indicating that the value of any temporary variable is not saved in the top register of the x87 stack. If DST is a temporary variable, we set its write-back flag bit NEEDWB on line 21st, change x87top to DST on line 22nd, and the value of DST for temporary variables is saved in the x87 stack top register.


Figure 6.3.4 Emitx87move and Emitmoveblock

Figure 6.3.4 the Emitmoveblock of line 26th to 55th is used for assignment between struct objects, and for line 39th, "a = B;" , the structure object A to occupy 40 bytes, the corresponding assembly code in line 43rd to 46th, where the register {EDI, ESI, Ecx},esi used to store the address of B, EDI for the address of a, ECX storage to copy the number of bytes, the 46th line of "Rep MOVSB" Memory replication is implemented. Due to the use of the registers of {EDI, ESI, ecx}, we need to perform the necessary write-back operations on line 30th to 32nd through the Spillreg function. The 48th line, based on the type information, gets the number of bytes to be copied, and the 54th line produces the assembly instructions for the memory copy.

C Compiler anatomy _6.3.1 assembly code Generation _ the main process of generating assembly code from intermediate directives

Related Article

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.