Dependency Graph in Compiler Design
A dependency graph is used to represent the flow of information among the attributes in a parse tree. In a parse tree, a dependency graph basically helps to determine the evaluation order for the attributes. The main aim of the dependency graphs is to help the compiler to check for various types of dependencies between statements in order to prevent them from being executed in the incorrect sequence, i.e. in a way that affects the program’s meaning. This is the main aspect that helps in identifying the program’s numerous parallelizable components.
It assists us in determining the impact of a change and the objects that are affected by it. Drawing edges to connect dependent actions can be used to create a dependency graph. These arcs result in partial ordering among operations and also result in preventing a program from running in parallel. Although use-definition chaining is a type of dependency analysis, it results in unduly cautious data reliance estimations. On a shared control route, there may be four types of dependencies between statements I and j.
Dependency graphs, like other-directed networks, have nodes or vertices depicted as boxes or circles with names, as well as arrows linking them in their obligatory traversal direction. Dependency graphs are commonly used in scientific literature to describe semantic links, temporal and causal dependencies between events, and the flow of electric current in electronic circuits. Drawing dependency graphs is so common in computer science that we’ll want to employ tools that automate the process based on some basic textual instructions from us.
Types of dependencies:
Dependencies are broadly classified into the following categories:
1. Data Dependencies:
When a statement computes data that is later utilized by another statement. A state in which instruction must wait for a result from a preceding instruction before it can complete its execution. A data dependence will trigger a stoppage in the flowing services of a processor pipeline or block the parallel issuing of instructions in a superscalar processor in high-performance processors using pipeline or superscalar approaches.
2. Control Dependencies:
Control Dependencies are those that come from a program’s well-ordered control flow. A scenario in which a program instruction executes if the previous instruction evaluates in a fashion that permits it to execute is known as control dependence.
3. Flow Dependency:
In computer science, a flow dependence occurs when a program statement refers to the data of a previous statement.
When an instruction needs a value that is later modified, this is known as anti-dependency, or write-after-read (WAR). Instruction 2 anti-depends on instruction 3 in the following example; the order of these instructions cannot be modified, nor can they be performed in parallel (potentially changing the instruction ordering), because this would modify the final value of A.
An output dependence, also known as write-after-write (WAW), happens when the sequence in which instructions are executed has an impact on the variable’s ultimate output value. There is an output dependence between instructions 3 and 1 in the example below; altering the order of instructions would affect the final value of A, hence these instructions cannot be run in parallel.
If the outcome of A determines whether B should be performed or not, an instruction B has a control dependence on a previous instruction A. The display style S 2S 2 instruction has a control reliance on the display style S 1S 1 instruction in the following example. However, display style S 3S 3 is not dependent on display style S 1S 1, because display style S 3S 3 is always done regardless of the result of display style S 1S 1.
Example of Dependency Graph:
Design dependency graph for the following grammar:
E -> E1 + E2 E -> E1 * E2
E -> E1 + E2
E -> E1 * E2
E.val -> E1.val + E2.val
E.val -> E1.val * E2.val
Required dependency graph for the above grammar is represented as –
- Synthesized attributes are represented by .val.
- Hence, E.val, E1.val, and E2.val have synthesized attributes.
- Dependencies are shown by black arrows.
- Arrows from E1 and E2 show that the value of E depends upon E1 and E2.
Dependency resolution is a two-phase procedure that is performed until the dependency graph is complete.
- Perform conflict resolution when a new dependence is introduced to the graph to decide which version should be added.
- In a specific dependence, for example, a versioned module, which is identified as part of the graph, it helps to extract its information so that it helps in adding its dependencies one by one.
When doing dependency resolution, Gradle(automation tool) handles two types of conflicts:
A version conflict is a conflict that happens when two components depend on the same module but the versions are different.
Let us say that project depends on react library of Facebook i.e. “com.google.react: react:18.7.0”. here the version is 18.7.0. Now we can clearly see that It is also depending on some other library which itself depends on react but the version is 19.0.2 is a different one altogether.
Gradle resolves this by selecting the highest version. In that case, 19.0.2 will be chosen.
But that’s not the end of the store. Gradle has a notion of rich version declaration, and there are numerous ways to choose a version from a variety of options.
Implementation conflicts are the situations when the dependency graph contains multiple modules that provide the same implementation, or capability in Gradle terminology. Gradle determines what a module offers using variations and capabilities. This is a one-of-a-kind feature that demands its own chapter to fully comprehend what it entails and allows. A conflict occurs the moment two modules either:
- Attempt to select incompatible variants,
- Declare the same capability
Uses of Dependency Graph:
- The primary idea behind dependency graphs is for the compiler to check for various types of dependencies between statements in order to prevent them from being executed in the incorrect sequence, i.e. in a way that affects the program’s meaning.
- This aids it in identifying the program’s numerous parallelizable components.
- Automated software installers: They go around the graph seeking software packages that are needed but haven’t been installed yet. The coupling of the packages determines the reliance.
- Instructions scheduling uses dependency in a wider way.
- Dependency graphs are widely used in Dead code elimination.