Runtime Storage
Management
Runtime Storage Management
• In compiler design, runtime storage management is essential for
handling memory allocation and deallocation during program
execution.
• It focuses on organizing memory effectively so that programs can
store and access data dynamically while executing.
• Runtime storage management encompasses managing different
memory regions such as the stack, heap, global/static data segment,
and code segment.
Key Components of Runtime
Storage Management
• Memory Segments in Runtime Storage Management
• Compilers divide the runtime memory into segments to manage different
types of data. These segments are:
• Code Segment: Stores the compiled code (machine instructions). This area is
typically read-only to prevent accidental modification.
• Static/Global Data Segment: Holds global variables and static data. This
segment is initialized once and remains throughout the program's lifetime.
• Stack Segment: Used for managing function calls, local variables, parameters,
and control data like return addresses.
• Heap Segment: Supports dynamic memory allocation, such as data created
with malloc in C or new in C++.
Stack Management
• The stack segment is crucial for supporting function calls, recursive functions, and
managing local variables. Each function call creates a new stack frame, also known as
an activation record.
• Structure of a Stack Frame:
• Return Address: Stores the address to return to after a function completes.
• Parameters: Holds parameters passed to the function.
• Local Variables: Space for variables defined within the function.
• Saved Registers: Contains values of registers that need to be restored after the function call.
• Challenges:
• Recursion: Recursive calls can lead to stack growth, requiring careful management to avoid
stack overflow.
• Memory Management Efficiency: Stack frames must be efficiently created and destroyed as
functions are called and return.
Heap Management
• The heap segment is used for dynamic memory allocation, where
memory can be allocated and deallocated at runtime, as required by
the program.
• Languages like C and C++ use malloc, calloc, and free (C), or new and
delete (C++) for heap management.
• Languages with garbage collection (e.g., Java, Python) handle
deallocation automatically to prevent memory leaks.
• Challenges:
• Fragmentation: Frequent allocation and deallocation can lead to memory
fragmentation, where free memory blocks are scattered across the heap.
• Garbage Collection: In managed languages, garbage collection is used
to free memory that’s no longer referenced. Implementing efficient
garbage collection algorithms (e.g., mark-and-sweep, generational
collection) is crucial for runtime performance.
• Overhead: Memory allocation and deallocation add runtime
overhead, so it’s essential to optimize these operations to avoid
performance bottlenecks.
Global and Static Data Management
• Description: Global variables and static variables have a fixed memory
location in the global data segment, allocated once when the program starts
and deallocated upon termination.
• BSS Segment: Uninitialized static and global variables are stored here. This
segment does not take up actual space in the binary; instead, it’s zeroed out
at runtime.
• Data Segment: Initialized global and static variables reside here.
• Challenges:
• Data Persistence: These variables remain in memory throughout the program’s
execution, so excessive use can lead to higher memory consumption.
• Concurrency Issues: Global variables can create data races in multithreaded
environments, so care is needed to synchronize access to shared data.
Activation Records
• An activation record is a contiguous block of storage that manages information
required by a single execution of a procedure.
• When you enter a procedure, you allocate an activation record, and when you exit
that procedure, you de-allocate it.
• Basically, it stores the status of the current activation function. So, whenever a
function call occurs, then a new activation record is created and it will be pushed
onto the top of the stack.
• It will remain in stack till the execution of that function. So, once the procedure is
completed and it is returned to the calling function, this activation function will be
popped out of the stack.
• If a procedure is called, an activation record is pushed into the stack, and it is
popped when the control returns to the calling function.
• Temporaries:
• The temporary values, such as those arising in the evaluation of expressions,
are stored in the field for temporaries.
• Local data:
• The field for local data holds data that is local to an execution of a procedure.
• Saved Machine States:
• The field for Saved Machine Status holds information about the state of the
machine just before the procedure is called. This information includes the
value of the program counter and machine registers that have to be restored
when control returns from the procedure.
• Access Link :
• It refers to information stored in other activation records that is non-local. The access
link is a static link and the main purpose of the access link is to access the data which
is not present in the local scope of the activation record. It is a static link.
• Control Links :
In this case, it refers to an activation record of the caller. They are
generally used for links and saved status. It is a dynamic link in nature. When
a function calls another function, then the control link points to the activation
record of the caller.
Record A contains a control link pointing to the previous record on the
stack. Dynamically executed programs are traced by the chain of control links.
• Parameter List:
• The field for parameters list is used by the calling procedure to supply
parameters to the called procedure. We show space for parameters in the
activation record, but in practice, parameters are often passed in machine
registers for greater efficiency.
• Return value:
• The field for the return value is used by the called procedure to return a value
to the calling procedure. Again in practice, this value is often returned in a
register for greater efficiency.
Challenges in Runtime Storage
Management
• Memory Leaks: In languages without automatic garbage collection, memory
that is no longer in use but not deallocated can accumulate, causing
memory leaks.
• Fragmentation: Frequent allocation and deallocation can lead to
fragmented memory, reducing the availability of contiguous memory blocks.
• Concurrency: Managing runtime storage in a multithreaded environment is
challenging due to data races and synchronization requirements, especially
in the heap.
• Efficiency: Runtime storage management introduces overhead, so efficient
algorithms are essential for allocation, deallocation, and garbage collection
to avoid impacting performance.
Storage Allocations strategies
• Storage allocation is a critical phase that determines how the memory for
variables and data structures is allocated and managed during program
execution.
• This involves deciding where in memory each variable, constant, or data
structure will reside and whether that memory allocation should be static,
stack-based, or dynamic (heap-based).
• Proper storage allocation is essential for efficient memory management and is
closely tied to runtime organization.
• Types of Storage Allocations
1. Static Allocation
2. Stack Allocation
3. Heap Allocation
Static Allocation
• In static allocation, memory for all variables and data structures is
reserved at compile-time. This means the compiler assigns memory
locations to all variables before the program runs, and these locations
remain fixed throughout execution.
• Characteristics:
• Suitable for global variables, static variables, and constants.
• Fast access due to fixed memory addresses.
• Memory cannot be reused or resized dynamically.
Stack Allocation
• In stack allocation, memory is managed using a stack data structure.
It’s used primarily for local variables within functions.
• Each time a function is called, a stack frame (activation record) is
created for that function’s local variables and parameters, and it’s
removed when the function returns.
• Characteristics:
• Suitable for local variables, function parameters, and return addresses.
• Fast allocation and deallocation due to LIFO (Last In, First Out) structure.
• Memory is automatically freed when a function returns.
Heap Allocation
• Heap allocation is used for dynamic memory allocation, where memory
is allocated and freed at runtime based on the program’s needs.
• This memory is managed manually by the programmer in languages like
C/C++ (using malloc, free, new, delete) or automatically in languages
with garbage collection like Java and Python.
• Characteristics:
• Suitable for data structures that need to persist beyond function calls or have a
variable size.
• Flexible but slower than stack allocation due to fragmentation and the need for
explicit allocation and deallocation.
• Memory can be reused, and sizes can vary.
Feature Static Allocation Stack Allocation Heap Allocation
Allocation Time Compile-time Runtime Runtime
Memory Management Fixed, cannot be resized Automatically managed Programmer-managed
Lifetime Entire program duration Limited to function scope Can persist beyond scope
Efficiency Fast Very fast (LIFO structure) Slower (fragmentation)
Use Case Global/static variables, Local variables, function Dynamic data structures
constants calls
Code optimization
• Code optimization in compiler design refers to techniques that improve the
performance and efficiency of the generated code without altering the
program's functionality.
• Code optimization techniques can either reduce the program's execution
time, memory usage, or both.
• Types of Code Optimization
• Code optimization techniques can broadly be classified into two categories:
• Machine-Independent Optimizations: These are high-level optimizations that are independent
of the target machine architecture. They improve code performance at the intermediate code
level.
• Machine-Dependent Optimizations: These are low-level optimizations that focus on the
hardware characteristics of the target machine. They improve the final machine code's
performance.
Machine-Independent Optimization
Techniques
• Constant Folding
• Constant folding involves evaluating constant expressions at compile-time
rather than runtime, reducing the computation needed during execution.
• int x = 3 * 4; // 3 * 4 is folded to 12 at compile-time
• Constant Propagation
• Constant propagation replaces variables with known constant values,
simplifying expressions and reducing runtime calculations.
• int x = 10;
• int y = x + 5; // x is replaced with 10
• Common Subexpression Elimination
• Common Subexpression Elimination identifies and eliminates expressions that are
repeated with the same operands and results, reducing redundant calculations.
• int x = (a + b) * (a + b);
• After optimization
int temp = a + b;
int x = temp * temp;
• Dead Code Elimination
• Dead code elimination removes code that does not affect the program's output, such
as unreachable code or computations that produce unused results.
• int x = 5;
• x = 10; // 5 is never used, so `x = 5;` is removed
• Loop-Invariant Code Motion
• This technique moves code that does not change within a loop outside of the loop to avoid
redundant computation.
• for (int i = 0; i < n; i++)
{ int temp = a + b; // a + b does not depend on i
array[i] = temp * i; }
• Strength Reduction
• Strength reduction replaces expensive operations (such as multiplication or division) with
equivalent but less costly operations (like addition or bit shifting).
• for (int i = 0; i < n; i++)
{
result = i * 2; // Multiply operation
}
• Inlining Function Calls
• Inlining replaces a function call with the body of the function to reduce the
overhead of function calls. This is especially useful for small functions that are
called frequently.
• int square(int x)
{ return x * x; }
int y = square(4); // Function call is replaced with 4 * 4