Compiler Design 2019 Solved
Compiler Design 2019 Solved
Visit = Biharengineeringuniversity.com
2.bWhat is an activation record? Explain how they are used to access various local and global
variables
An activation record, also known as a stack frame or activation frame, is a data structure used to
manage the information needed to execute a function or procedure in computer programs.
When a function or procedure is called, an activation record is created and pushed onto the call
stack, which is a region of memory dedicated to managing function calls.
2.
3.
4.
5.
6. Control Link: A link to the activation record of the calling function or procedure,
establishing a chain of nested function calls.
7. Status Flags: Flags indicating the status of the function or procedure.
The activation record is crucial for managing the flow of control and data during program
execution. When a function is called, a new activation record is created and pushed onto the
stack. When the function completes, its activation record is popped off the stack, and control
returns to the address stored in the return address field.
Now, let's discuss how activation records are used to access various local and global variables in a
pointwise manner:
In a pointwise manner, accessing variables involves using specific addresses or offsets within the
activation record. For example, to access the first local variable, you might use something like
activation_record_base + offset_of_first_local_variable . Similarly, accessing parameters or other variables
follows a similar addressing scheme. The key is to maintain a consistent and welldefined structure
for the activation record to enable efficient and reliable variable access during program
execution.
2.(b) What is bottom-up parsing? Discuss shift reduce parsing technique in brief What is a handle
Bottom-Up Parsing:
Bottom-up parsing is a parsing technique that starts from the input symbols and works its way
up to the start symbol of the grammar. It's also known as shift-reduce parsing because it involves
shifting input symbols onto a stack and then reducing them to higher-level structures according
to the production rules of a grammar.
reduce parsing, the parser operates by shifting input symbols onto a stack until it can
Shift-Reduce
reduce the symbols on the top of the stack to a higher-level construct in the grammar. The two
Parsing:
reduce parsing are:
In shiftmain actions
in shift-
1. Shift
:
• Move the next input symbol onto the top of the stack.
• This operation represents incorporating the next input symbol into the current
partial parse.
2. Reduce:
The handle is essential because it represents a portion of the input that can be replaced by a non-
terminal, effectively reducing the complexity of the parse. The parser looks for handles in the
stack and applies reduction whenever a handle is found.
The success of shift-reduce parsing depends on the grammar being LALR(1) (Look-Ahead LR(1)),
meaning it uses a one-symbol look-ahead for making parsing decisions. Popular algorithms like
LR(1) and LALR(1) are used to generate parsers for shift-reduce parsing. Examples of shift-reduce
parsing algorithms include LR parsing and SLR parsing.
3.(b) What is the use of a symbol table? How are the identifiers stored in the symbol table?
A symbol table is a data structure used by compilers and interpreters to store information about
the variables, functions, objects, and other language constructs used in a program. It serves as a
central repository for mapping identifiers (such as variable names) to information about their
properties, types, scope, and memory locations. The primary purposes of a symbol table include:
1. Name Management:
• Keeping track of all identifiers used in the program and ensuring they have unique
names within their respective scopes.
2. Type Checking:
• Associating data types with variables and ensuring that operations are performed
on compatible data types.
3. Scope Management:
• Tracking the scope of variables and ensuring that identifiers are used in the
correct scope.
4. Memory Allocation:
Identifiers, such as variable names, are typically stored in the symbol table using an entry for each
identifier. Each entry contains information about the identifier, and the structure of this entry can
include the following components:
1. Name: The actual name of the identifier (e.g., variable name).
2. Type: The data type associated with the identifier (e.g., integer, float).
3. The scope or visibility of the identifier (e.g., local, global).
4. Scope
5. :
6. Memory Location: The address or location where the identifier is stored in memory.
Initialization Status: Whether the identifier has been initialized.
Additional Properties: Other properties specific to the programming language and
compiler requirements.
The symbol table itself can be implemented as a hash table, a linked list, a tree structure, or a
combination of these, depending on the requirements and design choices of the compiler or
interpreter. The goal is to efficiently store and retrieve information about identifiers during the
various stages of the compilation or interpretation process.
4(a).What is the pass of a compiler? Explain how the single and multi-pass compilers work
In the context of compilers, a pass refers to a complete traversal of the source code
by the compiler. Each pass performs a specific set of tasks or analyses on the source
code, and a compiler may consist of one or more passes. The organization of passes
in a compiler can be classified into two main types: single-pass and multi-pass
compilers.
Single-Pass Compiler:
because they process the source code linearly without the need to store intermediate
results between passes.
Multi-Pass Compiler:
In a multi-pass compiler, the compilation process is divided into multiple passes, each
dedicated to a specific task. The source code is read and analyzed in multiple passes,
with intermediate results stored between passes. The passes are organized such that
each pass refines the information gathered in previous passes. The final pass typically
generates the target code.
The advantages of multi-pass compilers include the ability to handle more complex
language features, perform advanced optimizations, and achieve a better
understanding of the entire program. However, multi-pass compilers are generally
more complex to implement and may require more memory to store intermediate
data structures.
1. Parsing Table:
• A parsing table is a data structure that guides the parser in making decisions
about which production to apply based on the current input symbol. It is typically
implemented as a two-dimensional table where one dimension represents the
non-terminals, and the other represents the terminals (and possibly the end-
ofinput marker).
• Each entry in the table contains the production rule to apply when the parser is in
a particular state (non-terminal) and sees a particular input symbol.
2. Stack:
• The stack is used to maintain the parsing state. It holds a combination of
nonterminals and terminals representing the current position in the parse tree.
• Initially, the stack contains the start symbol of the grammar.
3. Input Buffer:
• The input buffer holds the remaining input symbols to be parsed. It is consumed
as the parsing process progresses.
4. Output:
• The parser produces the parse tree or abstract syntax tree as the output. This tree
represents the hierarchical structure of the input code.
Algorithm for Non-Recursive Predictive Parsing:
The parsing table is crucial to the success of non-recursive predictive parsing. Constructing the
parsing table involves carefully analyzing the grammar to predict the next production rule based
on the current non-terminal and input symbol. The table should be constructed to handle
nonambiguous grammars.
The non-recursive predictive parsing algorithm is efficient and avoids the issues of left recursion
present in some recursive descent parsers. However, constructing a parsing table can be
challenging for grammars with certain ambiguities or conflicts. Additionally, the grammar must be
LL(1), meaning that it must be left-to-right scanning, leftmost derivation, and have onesymbol
lookahead to work effectively with this parsing technique.
5.(b) What is the syntax directed translation and why are they important?
A compiler is a complex software tool that translates high-level source code written in a
programming language into machine code or an intermediate code that can be executed by a
computer. The compilation process is typically divided into several phases, each responsible for
specific tasks. The different phases of a compiler are:
Type checking and error reporting are crucial aspects of the semantic analysis phase in a compiler.
These processes ensure that the source code adheres to the language's rules regarding variable
types, expressions, and other semantic constraints. Let's explore how type checking and error
reporting are typically performed in a compiler:
Type Checking:
1. Expression Type Inference:
• The compiler determines the data types of expressions in the source code. It
verifies that the operations applied to variables and literals are consistent with the
expected types according to the language's rules.
2. Type Compatibility:
• The compiler checks the compatibility of types in assignments, comparisons, and
other operations. It ensures that operands of binary operations have compatible
types and that assignments are made to variables of compatible types.
3. Function and Procedure Calls:
• Type checking extends to function and procedure calls. The compiler verifies that
the number and types of arguments passed to a function or procedure match the
declared parameters in terms of order and data type.
4. Array Indexing:
• For languages supporting arrays, the compiler checks that array indices are of integer
type and that the index is within the valid range for the array.
5. Pointer and Reference Types:
• If the language includes pointers or references, the compiler ensures that pointer
dereferences and reference assignments are type-safe.
6. Structural Typing:
• Some languages support structural typing, where types are based on the structure
of the data rather than explicit declarations. Type checking in such languages
involves ensuring that structures have compatible structures.
Error Reporting:
1. Location Information:
• When an error is detected, the compiler provides detailed information about the
location of the error in the source code. This information helps programmers
identify and correct the issues.
2. Error Messages:
• The compiler generates informative error messages that describe the nature of
the error. These messages often include suggestions for fixing the problem, aiding
developers in the debugging process.
3. Multiple Errors:
By integrating thorough type checking and effective error reporting mechanisms, compilers
contribute to the reliability and correctness of software development. These features assist
programmers in identifying and addressing issues early in the development process, ultimately
leading to more robust and maintainable code.
Creating a syntax tree and directed acyclic graph (DAG) involves representing the syntactic
structure of the given expression. I'll provide textual representations of both structures.
Syntax Tree:
The syntax tree represents the hierarchical structure of the expression:
^
/\
/ \
/ \
* +
/\ /\
/ \ / \
^ +b c
/\
/ \
= *
/\ / a
+b \
/\
* a c /
b
In the DAG:
• Shared subexpressions are represented only once.
• Each node corresponds to an operation or a value.
• The DAG is acyclic, meaning there are no cycles in the graph.
These representations capture the structure of the given expression, showing the
relationships between different subexpressions. Keep in mind that the actual
visual representation may vary based on the tool or method used for drawing the
tree or DAG.
7.(a). Explain various targets for code optimization with examples
Code optimization is a crucial phase in the compilation process where the compiler aims to
improve the efficiency, speed, and resource utilization of the generated code. Different targets for
code optimization address various aspects of program performance. Here are various targets for
code optimization along with examples:
1. Execution Time:
These targets for code optimization represent a broad spectrum of considerations aimed at
enhancing the performance of compiled programs. The choice of optimization targets depends
on factors such as the nature of the application, the target hardware architecture, and the
tradeoffs between execution time, code size, and resource usage.
7.(b) How are CPU registers allocated while creating machine code?
Register allocation is a crucial step in the code generation phase of a compiler. The goal is to map
variables in the source code to a limited number of CPU registers efficiently. Registers are small,
fast storage locations within the CPU, and using them effectively can significantly impact program
performance.
Here's a general overview of how CPU registers are allocated during the process of creating
machine code:
5. Graph Coloring:
• The interference graph is colored in such a way that no two interfering variables share the
same color (register).
• Graph coloring algorithms are used for this purpose. The number of colors needed
corresponds to the number of available registers.
6. Spill Handling:
• If there are not enough registers to allocate to all variables without interference, the
compiler must handle spills. A spill involves storing a variable temporarily in memory
when it cannot be kept in a register.
• The spill code is inserted at points where a variable's value is spilled to memory or loaded
back into a register.
7. Optimizations:
8. Generate Machine
Code:
• Finally, the compiler generates machine code based on the register allocation
decisions made during the earlier stages.
• The generated code uses the allocated registers for variable storage and
manipulation.
Example:
Consider the following snippet in a hypothetical assembly-like language:
In this example, the compiler would perform register allocation for R1 , R2 , R3 , R4 , and
R5 . The live ranges of these registers would be analyzed, and an interference graph
would be constructed. The graph coloring algorithm would then determine which
registers to assign to each variable.
S-attribute and L-attribute are two classifications of syntax-directed translation (SDT) schemes in
compiler design. Syntax-directed translation refers to the process of associating attributes with
the grammar productions to guide the translation of a source program into a target code. These
attributes are often associated with nodes in the abstract syntax tree (AST). S-attributes and
Lattributes differ in terms of when the attributes are evaluated and synthesized.
Associated with each production, we have synthesized attributes val for nonterminals
E , T , and F . The attributes represent the values of the corresponding expressions.
• Synthesized Attribute Rules:
• E.val = E1.val + T.val for the production E -> E + T
• E.val = T.val for the production E -> T
• T.val = T1.val * F.val for the production T -> T * F
• T.val = F.val for the production T -> F
• F.val = E.val for the production F -> ( E )
• F.val = id.lexval for the production F -> id
Associated with each production, we have inherited attributes inh for non-terminals E , T , E' , T' , and
F . The attributes represent information that needs to be passed down from the parent to its
children.
• Inherited Attribute Rules:
• E'.inh = E.inh
• E.val = E1.val + T.val
• T'.inh = T.inh
• T.val = T1.val * F.val
• F.inh = T'.inh
• F.val = E.val
• E'.val = E'.inh + T.val (Note: Here, E' has both synthesized and inherited attributes)
Summary:
• S-Attribute SDT:
• Synthesized attributes.
• Bottom-up evaluation.
• Suitable for bottom-up parsing.
• Example: Values are synthesized up the syntax tree, starting from the leaves.
• L-Attribute SDT:
• Inherited attributes.
• Top-down evaluation.
• Suitable for top-down parsing.
• Example: Information is inherited down the syntax tree, starting from the root.
9.(b) I explain about lexical phase error
In the context of compiler design, the lexical phase, also known as lexical analysis or scanning, is
the initial stage where the source code is processed to identify and tokenize its basic elements,
such as keywords, identifiers, literals, and operators. A lexical phase error refers to an issue
encountered during this tokenization process. Lexical errors occur when the compiler encounters
sequences of characters in the source code that do not form valid tokens according to the
programming language's syntax rules.
1. Illegal Characters:
• When the compiler encounters characters that are not part of the language's valid
character set or reserved for specific purposes. For example, using special
characters in an identifier name.
pythonCopy code
# Lexical error: '$' is not a valid character in an identifier illegal$id = 42
2. Unrecognized Tokens:
• When the compiler encounters sequences of characters that do not match any
valid token in the language. This can happen when using undefined or misspelled
keywords.
4. Subexpression
Elimination:
• Dynamic programming can be used to identify common subexpressions in the
intermediate code and eliminate redundant computation.
• Example: If the expression X * Y has been computed before, the result is stored, and
subsequent occurrences of X * Y reuse the stored result.
5. Instruction Selection
Optimization:
• Dynamic programming is applied to optimize the selection of machine instructions
for translating high-level operations into machine code.
• The goal is to find an optimal sequence of instructions that minimizes the overall cost,
considering factors like execution time and resource utilization.
6. Register
Allocation:
• Dynamic programming can be used to optimize register allocation, determining how
variables are mapped to processor registers during execution.
• The goal is to minimize the number of spills to memory and improve the efficiency of
register usage.
7. Control Flow
Optimization:
• Dynamic programming can optimize control flow constructs, such as loops and
conditionals, by analyzing the program's behavior and making informed decisions
about loop unrolling, loop fusion, and other optimizations.
8. Benefits and
Challenges:
• Benefits: Dynamic programming can lead to more efficient code by reusing computed
results, eliminating redundant computations, and optimizing the allocation of
resources.
• Challenges: Implementing dynamic programming in the context of code generation
requires careful analysis of the program's structure and behavior, as well as
consideration of trade-offs between time complexity and space complexity.
1. Purpose:
• The primary purpose of a syntax tree is to represent the syntactic structure of a source
code snippet in a way that reflects the grammar of the programming language.
2. Hierarchical Structure:
• Syntax trees have a hierarchical structure that reflects the nested and structured nature of
the programming language constructs.
• Each node in the tree corresponds to a language construct, such as a statement,
expression, or declaration.
4. Grammar Rules:
• The structure of the syntax tree is directly derived from the production rules of the formal
grammar defining the programming language.
• Each production rule corresponds to a specific node or subtree in the syntax tree.
5. Example:
• Consider a simple arithmetic expression: 3 + 4 * (5 - 2)
• The corresponding syntax tree might look like:
+
/\
3 *
/\
4 - /\
5 2