Intermediate code:
The grammar of an intermediate language (IL) treats program as a set of instructions. The instructions are
1) a label or marker for jump statements
2) an assignment to a variable
3) a unary operator applied to an atomic expression with the result stored in a variable.
4) a binary operator applied to a variable and an atomic expression with the result stored in a variable
5) a transfer from a memory to a variable
6) a transfer from a variable to memory
7) a jump to a label
8) a conditional selection between two labels
9) a function call ( note the procedure calls are treated the same as function calls and argument and result are variables )
The compiler tries to do as much work during compile time as possible as compared to doing the same work at runtime. In general, this helps us prepare for the execution.
Now getting back to the conversion to the intermediary language, let us take a look at how to translate expressions, here the main challenge is that the expression is structured as a tree while the intermediary language is flat and the result of every operation is stored in a variable and every non constant argument is stored in another variable. A translation function is used to describe the overall technique. This function returns the code as a synthesized attribute. It translates variables and functions to the names that correspond to the intermediary language. The symbol tables are passed as inherited attributes to the translation function. In addition, attributes are used to decide where to store the values evaluated from sub-expressions. There are two ways to do this:
1) the location of the values of a sub-expression can be passed up as a synthesized attribute to a parent expression which can decide where to place its own value
2) the parent expression can decide where it wants to find the values of its sub-expressions and pass this information down as inherited attributes.
Method 1 doesn't have to generate code as it can return the name of the variable that holds the value. However, this only works under the assumption that the variable isn't updated before the value is used by the parent expression. If the expression has function calls, that may also have side-effects. Method 2 doesn't have this problem since the value is created immediately before the assignment is executed. In addition, this method can also generate code to calculate the expression result directly into the desired variable and not use temporary variables.
An inherited attribute say 'place' is the IL variable used to store the result of the expression.
The translation function uses a switch case construct for the type of expression and evaluates this way:
1) if the expression is a constant, the value of that is stored in place
2) if the expression is a variable, the equivalent IL variable is found in vtable and then copied to the place
3) if it's a unary operation, a new IL variable is generated to hold the value of the argument of the operation, then it is translated using the newly generated variable for place. Then an IL operation assigns the result to the inherited place. The operator++ concatenates the two lists of instructions.
4) if its a binary operation, two new IL variables are generated to hold the values of the arguments, then the values are translated and finally a binary operation in the intermediate language assigns the final result to the inherited place.
5) if its a function call, the arguments are translated first, then a function call is generated with the result assigned to the inherited place. The name of the function is looked up in the ftable for the corresponding name in the IL. (text from Mogensen)
In the gcc compiler, you can specify a -march parameter that indicates the instruction set the compiler can use and on the other hand you can specify -mtune=cpu-type, to indicate the processor for which the code is optimized.
The grammar of an intermediate language (IL) treats program as a set of instructions. The instructions are
1) a label or marker for jump statements
2) an assignment to a variable
3) a unary operator applied to an atomic expression with the result stored in a variable.
4) a binary operator applied to a variable and an atomic expression with the result stored in a variable
5) a transfer from a memory to a variable
6) a transfer from a variable to memory
7) a jump to a label
8) a conditional selection between two labels
9) a function call ( note the procedure calls are treated the same as function calls and argument and result are variables )
The compiler tries to do as much work during compile time as possible as compared to doing the same work at runtime. In general, this helps us prepare for the execution.
Now getting back to the conversion to the intermediary language, let us take a look at how to translate expressions, here the main challenge is that the expression is structured as a tree while the intermediary language is flat and the result of every operation is stored in a variable and every non constant argument is stored in another variable. A translation function is used to describe the overall technique. This function returns the code as a synthesized attribute. It translates variables and functions to the names that correspond to the intermediary language. The symbol tables are passed as inherited attributes to the translation function. In addition, attributes are used to decide where to store the values evaluated from sub-expressions. There are two ways to do this:
1) the location of the values of a sub-expression can be passed up as a synthesized attribute to a parent expression which can decide where to place its own value
2) the parent expression can decide where it wants to find the values of its sub-expressions and pass this information down as inherited attributes.
Method 1 doesn't have to generate code as it can return the name of the variable that holds the value. However, this only works under the assumption that the variable isn't updated before the value is used by the parent expression. If the expression has function calls, that may also have side-effects. Method 2 doesn't have this problem since the value is created immediately before the assignment is executed. In addition, this method can also generate code to calculate the expression result directly into the desired variable and not use temporary variables.
An inherited attribute say 'place' is the IL variable used to store the result of the expression.
The translation function uses a switch case construct for the type of expression and evaluates this way:
1) if the expression is a constant, the value of that is stored in place
2) if the expression is a variable, the equivalent IL variable is found in vtable and then copied to the place
3) if it's a unary operation, a new IL variable is generated to hold the value of the argument of the operation, then it is translated using the newly generated variable for place. Then an IL operation assigns the result to the inherited place. The operator++ concatenates the two lists of instructions.
4) if its a binary operation, two new IL variables are generated to hold the values of the arguments, then the values are translated and finally a binary operation in the intermediate language assigns the final result to the inherited place.
5) if its a function call, the arguments are translated first, then a function call is generated with the result assigned to the inherited place. The name of the function is looked up in the ftable for the corresponding name in the IL. (text from Mogensen)
In the gcc compiler, you can specify a -march parameter that indicates the instruction set the compiler can use and on the other hand you can specify -mtune=cpu-type, to indicate the processor for which the code is optimized.
No comments:
Post a Comment