How would a live variable analysis be useful in generating good quality executable code? A: It can help to avoid generating instructions to store some values from registers into memory. Note: Live variable analysis cannot allow more registers to be used, since it does not increase the number of registers. It does not obviate the need for control flow analysis, and it is not the right kind of dataflow analysis for detecting uses of uninitialized variables.
Which of the following would be least likely to be stored in the symbol table of a compiler? A: the type of a complex expression. All of the others (the type of an identifier, the location where an identifier will be stored, the number of bytes required for a particular named type) are typically stored in a symbol table. They are characteristics of the things referred to by names.
In a C- compiler, types of expressions can be computed: A: as synthesized attributes. The type information flows up the tree.
Translators typically fall into one of two kinds. The first kind is like the translators that you wrote, performing actions during the parse that write out the translation. The second kind instead builds up syntax trees during parsing, and then passes those syntax trees to a separate semantic section of the compiler for translation. Which of the following is {\bf not} an advantage of the first approach over the second.
A: Compilers written using the first approach are generally more flexible and easier to modify. In fact, compilers written using the first approach are generally quite limited in how they can perform translations, since they produce the translation in the same order as what they see in the parse tree. Changing the order can involve a lot of trickery.
In a recursive descent compiler, an inherited attribute typically shows up as: A: a parameter to a function.
In a recursive descent compiler, a synthesized attribute typically shows up as: A: A value returned from a function. Note: sometimes a synthesized attribute is returned through an out (reference) parameter. That is just another mechanism for returning a value from a function.
Assume that a and b are global variables of type int,
where a is at offset 1 and b is at offset 2. Write a translation
of expression a + b + b into code for the abstract
machine used in your compiler project. The code should
compute the value of this expression and store the result
onto the stack.
M_FETCH_GLOBAL_INTEGER
1
M_FETCH_GLOBAL_INTEGER
2
M_INTEGER_ADD
M_FETCH_GLOBAL_INTEGER
2
M_INTEGER_ADD
In C, the for statement has the following form.
for ( e1 ; e2 ; e3 ) stmtand has the same meaning as
e1; while ( e2 ) { stmt; e3; }A for loop is described by production
for-statement -> for ( expr ; expr ; expr ) statementWrite attribute equations to produce the code for these expressions, using the byte code that your C- compiler targets. Assume that an expression e has an attribute e.code that is a sequence of instructions that will compute the value of e and push its result onto the stack. (For this exercise assume that Boolean values are pushed on the stack, with 0 standing for false and 1 standing for true.) Similarly, a statement s has attribute s.code that performs statement s, leaving nothing extra on the stack. Give an equation for for-statement.code. Use the following functions as helpers.
Production: for-statement -> for ( expr1 ; expr2 ; expr3 ) statement
Semantic actions
int L1 = newlabel(); int L2 = newlable(); for-statement.code = expr1.code + byte(M_POP) + byte(M_LABEL) + byte(L1) + expr2.code + byte(M_GOTO_IF_ZERO) + byte(L2) + statement.code + expr3.code + byte(M_POP) + byte(M_GOTO) + byte(L1) + byte(M_LABEL) + byte(L2)
You are given the following grammar for variable declarations.
declaration -> type identifierList identifierList -> identifier identifierList -> identifier identifierListfor example, declaration
int x,y,z;declares x, y and z to have type int. The lexical analyzer produces an attribute called name for an identifier, and the parser produces an attribute called typ for a type. A function insert(name,type) is available that makes an entry in the symbol table indicating that the type of variable name is type, where name is a string and type is the same kind of thing that is the typ attribute of the type nonterminal.
Write attribute equations and actions that make all of the symbol table entries called for by a variable declaration. For example, declaration int x,y,z; should produce three symbol table entries, one for each of x, y and z. Assume that the action will be performed only after all of the attributes of a node have been computed. You may use inherited attributes or synthesized attributes or any combination of them.
Note: The grammar is missing the commas that are shown in the example. That is a minor issue. I will ignore the commas.
The problem is to get the type to the places where it needs to be used. But that can be done by passing the type down the tree as an inherited attritute.
declaration -> type identifierList identifierList.type = type.typ identifierList -> identifier insert(identifier.name, identifierList.type) identifierList -> identifier identifierList1 identifierList1.type = identifierList.type; insert(identifier.name, indentifierList.type);