6.3.2 assembly code for arithmetic operations generated by the Emitassign function
In this section, we are going to discuss an intermediate directive shaped like "t1:a+b;" or "T2:&number", these instructions are used for a unary or two-dollar arithmetic operation, and the result of the operation is stored in the temporary variable T1 or T2. The format of the UCC Intermediate directive is as follows:
< operator opcode, destination operand dst, source operand SRC1, source operand src2>
<ADD,t1,a,b>//T1:a+b;
<ADDR,t2,number,NULL>//T2: &number;
Since only 2 operands are allowed in a x86 assembly instruction, and the intermediate instruction "DST:SRC1+SRC2" has 3 operands, we need to generate multiple x86 assembly instructions to implement the intermediate instruction. In assembly instructions, the integer addition operation has no special requirements for registers, and we can handle the following steps as follows:
(1) Call the Allocatereg function to the SRC1, SRC2, and DST allocation registers in turn. DST is a temporary variable that is used to hold the result of the operation, and must be assigned to a register, which may be remembered as R0. If SRC1 and SRC2 are not temporary variables, they are not assigned to registers.
(2) If the DST and SRC1 corresponding registers are not the same, we can produce a MOVL instruction to transfer the SRC1 value to the register R0.
(3) The addition instruction is generated, the addition of SRC2 and R0 is performed, and the result is stored in the register R0.
According to this way of thinking, we can be "t1:a+b;" Generate the following assembly code:
Movl A,%eax
Addl B,%eax
And the T2: &number in the form of the intermediate instruction only two operands, in the above step (1), we do not have to
SRC2 The allocation register, the other steps are similar, we can produce the following assembly code for "T2: &number":
Leal A,%ECX
However, some x86 assembly directives have specific requirements for registers, such as the multiplication of integers to find that the value of the source operand SRC1 is loaded into the register EAX. The integer division operation or the take-rest operation requires that the value of the source operand SRC1 be loaded into the eax, and if the division operation of the signed number is to be performed, all bits of the register edx are set to the sign bit of SRC1, and if the division of the unsigned number is to be performed, the register edx is placed at full 0. For example, we can "t3:a/b" for the intermediate command. Produces the following assembly code, where A and b are signed integers.
MOVL A,%eax//load SRC1 to EAX
CDQ//Extend the sign bit to the EDX register
IDIVL b//divide operation [Edx:eax]/SRC2, quotient in eax, remainder in edx,
At this point the value in EAX is the value of the temporary variable t3
In assembly instructions that x86 "move left or right", if you want to keep the number of bits left or right shifted in the register, you must use single-byte register cl, as follows:
int A, C; char len = 3;
c = a <<len;
Intermediate Code//////////////
T4: (int) (char) Len; promote char to int
T5:a << T4;
c = T5;
The corresponding assembly code is shown below, and we can see that in the assembly instruction "Shll%cl,%edx" We are using the single-byte register CL to hold the value of the operand len.
MOVSBL len,%eax//T4: (int) (char) Len;
MOVL%eax,%ecx//t5:a << T4
Movl A,%edx
Shll%CL,%edx
MOVL%edx, c//C = T5;
With these basics, we can talk about the function emitassign,6.3.5 generating assembly code for arithmetic operations, as shown in. The 47th to 56th line deals with a two-dollar operation with no special requirements for registers, such as "DST:SRC1+SRC2;". Line 44th to 45th is used to handle unary operations with a form such as "DST: ~src1", where we do not have to allocate registers for SRC2. The 33rd to 43rd Line is used to assign a register ecx to the SRC2 in the left or right shift instruction, and the SRC2 load register ecx on line 39th, then the SRC2 to a single byte register cl, which is the low 8 bits of the 4-byte register ecx, in line 41st.
Figure 6.3.5 Emitassign ()
The 8th to 32nd line of Figure 6.3.5 is used to generate assembly instructions for integer multiplication, division, and take-rest operations, the 11th to 16th line holds the value of SRC1 in register eax, the 17th line writes the value of the EDX register back into memory, and we store the SRC1 after the symbolic bit extension in [Edx:eax]. Line 19th to 24th is used to allocate the necessary registers for the SRC2, and the 26th line produces assembly instructions for multiplication or division, after which the register eax is the "result of multiplication" or "quotient of division", while the register edx holds the remainder of the division operation, and the 27th to 31st row is used to record "The temporary variable that holds the result of the operation DST corresponds to the register for edx or eax".
Because the assembly instruction of the floating-point operation is different from the integer, we need to call the Emitx87assign function on line 4th of Figure 6.3.5 to deal with the floating-point arithmetic operation. Let's take an example to illustrate that for a two-dollar floating-point operation that is shaped like "dst:src1+src2", we can generate assembly code by following these steps:
(1) If the SRC1 is not in the x87 stack top register, the x87 stack top register is required to write back, and then the SRC1 loaded into the x87 stack top register.
(2) The x87 stack top register and the SRC2 add operation, the result is stored in the x87 stack top register.
According to this idea, we can generate the following assembly code for the floating-point intermediate instruction "T6:d+e":
FLDS d//load floating point D from memory to x87 stack top register
Fadds E//After the addition operation is completed, the x87 stack top register is the value of T6
And for the form of "Dst:-src1" of the unary operation, we in the above step (2), as long as the x87 stack top register to do a unary operation, the result of the operation is still stored in the x87 stack top register. For example, we can generate the following assembly code for "T7:-D".
FLDS d//load floating point D from memory to x87 stack top register
FCHS//The symbol bit is reversed, x87 the top register is the value of T7
Now we can analyze the function emitx87assign,6.3.6 for generating these floating-point arithmetic assembly code. If SRC1 has not yet been loaded into the x87 stack top register, we will make the necessary write-back through line 5th and then load the SRC1 from memory into the x87 stack top register on line 6th. If the value of SRC1 is already in the x87 stack top register, at this point SRC1 must be a temporary variable, if SRC1 also be used in the subsequent intermediate instruction, we need to write the value of SRC1 back to memory in line 10th, because after floating-point operation, the top register holds the DST value instead of the SRC1 value. If the operand SRC2 is SRC1, because we are encountering the template "Faddl%2" in the form of line 13th, we will add the x87 stack top register to the SRC2 in memory, and SRC2 is SRC1 at this point, so we also write the value of SRC1 back to memory in line 10th. The 15th line calls the assembly instruction of the Putasmcode function to generate the floating-point arithmetic according to the assembly instruction template.
Figure 6.3.6 Emitx87assign ()
C Compiler anatomy _6.3.2 assembly Code Generation _ assembly code for arithmetic operations generated by the Emitassign function