Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
Procedure Calls
2.1.
Calling Sequence
3.
Intermediate Code for Procedures
4.
Intermediate representation
4.1.
1. High Level intermediate code
4.2.
2. Low Level intermediate code
5.
Need for Intermediate Code Generation
6.
Advantages of Intermediate Code Generation
7.
Disadvantages of Intermediate Code Generation
8.
Frequently Asked Questions
8.1.
Why do we need intermediate code generation?
8.2.
What role does intermediate code play?
8.3.
In which phase does the intermediate code is generated?
8.4.
What type of intermediate code is used in compiler?
9.
Conclusion
Last Updated: Mar 27, 2024
Medium

What is Intermediate Code Generation?

Author Apoorv Dixit
0 upvote
Leveraging ChatGPT - GenAI as a Microsoft Data Expert
Speaker
Prerita Agarwal
Data Specialist @
23 Jul, 2024 @ 01:30 PM

Introduction

Compilers transform the code we write in languages like Python or Java into a form that computers can understand. This process happens in steps, and one key step is called "intermediate code generation." Instead of turning our code directly into machine language (which would mean needing a different compiler for every type of computer), compilers first convert it into a "middleman" code. This makes the process more flexible. One common way to create this middleman code is through something called "three-address code," which helps break down complex operations into simpler steps. This step is crucial in making sure our code runs smoothly on different devices.

intermediate code generation in compiler design

In this article, we will generate the Intermediate Code for Procedures. 

A procedure is similar to a function. Technically, it is an essential and frequently used programming construct in compiler design that generates good code for procedure calls and returns. For simplicity, we assume that parameters are passed by value. 

Now let's understand what a procedure call is and then move our discussion to intermediate code for procedures.

Procedure Calls

A procedure call is a simple statement that includes the procedure name, parentheses with actual parameter names or values, and a semicolon at the end.

The types of the actual parameters must match the types of the formal parameters created when the procedure was first declared (if any). The compiler will refuse to compile the compilation unit in which the call is made if this is not done.

General Form

Procedure_Name(actual_parameter_list);

-- where commas separate the parameters

Calling Sequence

A call's translation provides a list of activities executed at the beginning and end of each operation. In a calling sequence, the following actions occur:

  • Space is made available for the activation record when a procedure is called.
     
  • Evaluate the called procedure's argument.
     
  • To allow the called method to access data in enclosing blocks, set the environment pointers.
     
  • The caller procedure's state is saved so that it can resume execution following the call.
     
  • Save the return address as well. It is the location to which the called procedure must transfer after it has been completed.
     
  • Finally, for the called procedure, generate a jump to the beginning of the code.
Get the tech career you deserve, faster!
Connect with our expert counsellors to understand how to hack your way to success
User rating 4.7/5
1:1 doubt support
95% placement record
Akash Pal
Senior Software Engineer
326% Hike After Job Bootcamp
Himanshu Gusain
Programmer Analyst
32 LPA After Job Bootcamp
After Job
Bootcamp

Intermediate Code for Procedures

Let there be a function f(a1, a2, a3, a4), a function f with four parameters a1,a2,a3,a4.

Three address code for the above procedure call(f(a1, a2, a3, a4)).

param a1
param a2
param a3
param a4
call  f, n 


‘call’ is a calling function with f and n, here f represents name of the procedure and n represents number of parameters

Now, let’s first take an example of a program to understand function definition and a function call.

main()
{
swap(x,y);  //calling function
}

void swap(int a, int b)   // called function
{
// set of statements
}


In the above program we have the main function and inside the main function a function call swap(x,y), where x and y are actual arguments. We also have a function definition for swap, swap(int a, int b) where parameters a and b are formal parameters.

In the three-address code, a function call is unraveled into the evaluation of parameters in preparation for a call, followed by the call itself. Let’s understand this with another example:

 n= f(a[i])

Here, f is a function containing an array of integers a[i]. This function will return some value and the value is stored in ‘n’ which is also an integer variable.

A→array of integers

F→ function from integer to an integer.

Three address codes for the above function can be written as:

t1= i*4
t2=a[t1]
param t2
t3= call f,1
n=t3


t1= i*4

In this instruction, we are calculating the value of i which can be passed as index value for array a.

t2=a[t1]

In this instruction, we are getting value at a particular index in array a. Since t1 contains an index, here t2 will contain a value.  The above two expressions are used to compute the value of the expression(a[i]) and then store it in t2.

param t2

The value t2 is passed as a parameter of function f(a[i])

t3= call f,1

This instruction is a function call, where position 1 represents the number of parameters in the function call. It can vary for different function calls but here it is 1. The calling function will return some value and the value is stored in t3.

n=t3

The returned value will be assigned to variable n.

Let's see the production with function definition and function call. Several nonterminals like D, F, S, E, A are used to represent intermediate code.

D → define T id ( F ) { S }
F → 𝜖 | T id, F
S → return E ;
E → id ( A )
A → 𝜖 | E , A


In D→ define T id (F) {S}, the nonterminal D is for declaration, and T is for type. In the function declaration, we are going to define the type of the function(T), function name(id), parameters and code to be executed. (F) represent parameters/arguments of the function and {S} is code to be executed.

Now let’s see what can be a formal parameter.

F → 𝜖 | T id, F

Here the parameter can be empty(𝜖) or of some type(T) followed by the name(id). F at the end represents the sequence of formal parameters. For example, add(int x, int y, int w,.........).

S → return E;

Here S is code(set of statements to be executed) which will return a value of an expression(E).

E → id ( A )

Expression has some function call with the actual parameters. id represents the name of function and (A) represents actual parameters. Actual parameters can be generated by the nonterminal A.

A → 𝜖 | E, A. 

Actual parameters can be generated with expression E, it can also be a sequence of parameters A. For example, in add function there can be multiple parameters w,x,y………, etc, add(x,y,z……..).

Intermediate representation

Intermediate code can be represented in two ways:

1. High Level intermediate code

A compiler or an interpreter generates high-level intermediate code, which stands between the source code and the machine code. It is portable and platform-independent, allowing it to run on various platforms. High-level intermediate code is frequently used to optimise code for a certain target platform or to make it more maintainable and modifiable.

2. Low Level intermediate code

Low-level intermediate code is a type of code that sits between high-level programming languages and machine code. It's typically written in assembly language or another low-level language. It bridges the gap between the high-level language and the machine code that the computer understands. Low-level intermediate code is often used to develop operating systems, device drivers, and other software. It's closer to machine code than high-level languages, making reading and writing more efficient and challenging.

Need for Intermediate Code Generation

If a source code can be translated directly into its target machine code, why do we need to translate it into an intermediate code that is then translated to its target code? Let's look at why we require an intermediate code.

  • Suppose a compiler does not have the option of generating intermediate code when translating a source language to its target machine language. In that case, a full native compiler is required for each new machine.
     
  • By keeping the analysis portion of all compilers, the same intermediate code eliminates the need for a new full compiler for each unique machine.
     
  • The synthesis part of the compiler is altered depending on the target machine.
     
  • By using code optimization techniques on intermediate code, it becomes easier to apply source code modifications to improve code performance.

Advantages of Intermediate Code Generation

There are various advantages to intermediate code generation in compiler design. Offering a standard representation for code changes made before the final generation makes code optimisation simpler. Additionally, it offers portability across several programming languages and architectures, making debugging and maintenance simpler. 

 

The front-end and back-end of the compiler's concerns are separated, which improves modularity and makes it easier to spot optimisation potential. Additionally, intermediate code promotes platform independence and makes it possible for high-level language constructs to be efficiently represented during compilation.

Disadvantages of Intermediate Code Generation

There are some disadvantages of intermediate code generation in compiler design. It adds complexity, overhead, and perhaps optimization-related limits. It can be difficult to convert it to target code for various architectures, and debugging intermediate code might be more difficult than debugging source code.

Frequently Asked Questions

Why do we need intermediate code generation?

In software development, intermediate code creation is essential for portability and optimisation. It links high-level programming languages and machine code, allowing code to be swiftly translated and optimised for various computer architectures.

What role does intermediate code play?

Intermediate code is the link between high-level programming languages and machine code. It aids software portability and optimisation by providing a readily translated and optimised standard language. It also expedites code compilation and comprehension.

In which phase does the intermediate code is generated?

Intermediary code is produced during the compilation process, following the front-end phase's parsing and processing of the source code, but before the back-end phase translates the source code into machine code.

What type of intermediate code is used in compiler?

The type of intermediate code used in compilers varies, but some commonly used types include Three-Address Code (TAC), Static Single Assignment (SSA) Code, and Virtual Machine Code (VM Code). Each class has advantages and disadvantages regarding efficiency, simplicity, and portability.

Conclusion

In this article, we have extensively discussed 'Intermediate Code' and 'Procedure Calls.' We have also covered intermediate code for procedures. We have also discussed the need for intermediate code generation. We hope that this blog has helped you enhance your knowledge regarding 'Intermediate code for procedures'. 

Recommended Reading: 

Do check out The Interview guide for Product Based Companies as well as some of the Popular Interview Problems from Top companies like Amazon, Adobe, Google, Uber, Microsoft, etc. on Coding Ninjas Studio.

Also check out some of the Guided Paths on topics such as Data Structure and Algorithms, Competitive Programming, Operating Systems, Computer Networks, DBMS, System Design, etc. as well as some Contests, Test Series, Interview Bundles, and some Interview Experiences curated by top Industry Experts only on Coding Ninjas Studio.

Do upvote our blog to help other ninjas grow.

Happy Learning!

Live masterclass