The LabVIEW Compiler - Under the Hood

Publish Date: Aug 03, 2010 | 3 Ratings | 4.67 out of 5 | Print | Submit your review

Table of Contents

  1. LabVIEW Compile Process
  2. DFIR Provides a High-Level Intermediate Representation
  3. DFIR Decompositions and Optimizations
  4. DFIR Back-End Transforms
  5. LLVM Provides a Low-Level Intermediate Representation
  6. DFIR and LLVM Work in Tandem

Compiler design is a complex topic, considered specialized knowledge even among professional software engineers.

NI LabVIEW software is a multiparadigmatic graphical programming environment that incorporates a wide variety of concepts including data flow, object orientation, and event-driven programming. LabVIEW is also exceedingly cross-platform, targeting multiple OSs, chipsets, embedded devices, and field-programmable gate arrays (FPGAs). The LabVIEW compiler is a sophisticated system that has significantly evolved throughout the past 20 years. Explore the LabVIEW compile process and recent compiler innovations by National Instruments.

1. LabVIEW Compile Process

First in the compilation of a VI is type propagation, which is responsible for resolving implied types for terminals that can adapt to type and detect syntax errors. After type propagation, the VI is converted from the editor model into the dataflow intermediate representation (DFIR) graph used by the compiler. The compiler executes several transforms, such as dead code elimination, on the DFIR graph to decompose, optimize, and prepare it for code generation. The DFIR graph is then translated into a Low-Level Virtual Machine (LLVM) intermediate representation (IR), and a series of passes is run over the IR to further optimize and lower it – eventually – to machine code.

Back to Top

2. DFIR Provides a High-Level Intermediate Representation

DFIR is a hierarchical, graph-based IR of block diagram code. Similar to G code, DFIR is composed of various nodes with terminals that can be connected to other terminals. Some nodes, such as loops, contain diagrams, which may in turn contain other nodes.

Figure 1 shows an initial DFIR of a simple VI. When LabVIEW first creates a DFIR for a VI, it is a direct translation of the G code, and nodes in the DFIR graph have a one-to-one correspondence to nodes in the G code. As the compile progresses, DFIR nodes may be moved, split apart, or injected, but the compiler will still preserve characteristics, such as parallelism, inherent in the developer’s G code.

Figure 1. View the initial DFIR graph of a simple VI.

DFIR offers two significant advantages to the LabVIEW compiler:

1. DFIR decouples the editor from the compiler representation – Before the advent of DFIR, LabVIEW had a single representation of the VI that was shared by both the editor and the compiler. This prohibited the compiler from modifying the representation during the compile process, which in turn made it difficult to introduce compiler optimizations. DFIR introduces a series of optimizations and decompositions that can drastically improve the performance of LabVIEW code but require block diagram nodes and wires to be disconnected and moved.

2. DFIR serves as a common hub for the multiple compiler front ends and back ends – Today, LabVIEW works with many dramatically different targets. Similarly, LabVIEW presents multiple models of computation to the user, such as LabVIEW MathScript, C integration, simulation diagrams, and statecharts. DFIR provides a common IR, which the front ends produce and the back ends consume, facilitating reuse between the various combinations.

Back to Top

3. DFIR Decompositions and Optimizations

Once in DFIR, the VI runs through a series of decomposition transforms that reduce or normalize the DFIR graph. After the DFIR graph is thoroughly decomposed, the DFIR optimization passes begin. There are more than 30 decompositions and optimizations that can improve the performance of LabVIEW code. Examine the simple VI, shown in figures 2 and 3, which calls the Trim Whitespace VI (Trim Whitespace.vi) from vi.lib.

Figure 2. This is the VI prior to any DFIR decompositions.

Figure 3. Trim Whitespace.vi Block Diagram is defined above.

First, Trim Whitespace.vi is inlined into the caller VI, shown in Figure 4. Now the unreachable code and dead code elimination algorithms can simplify the code. The first case structure will always execute the same case because the input is a constant. Therefore, the remaining cases can be removed with the entire second case structure because they never execute. Next, loop-invariant code motion will move the Match Pattern primitive out of the loop to ensure it executes only once, as shown in Figure 5.

Figure 4. The subVI is inlined into the caller, resulting in a DFIR graph equivalent to this G code.

Figure 5. The one execution produces the optimized DFIR graph.

Back to Top

4. DFIR Back-End Transforms

After the DFIR graph is decomposed and optimized, the back-end transforms execute. These transforms evaluate and annotate the DFIR graph in preparation for ultimately lowering the DFIR graph to a LLVM IR. The clumper is responsible for grouping nodes into clumps, which can run in parallel. The inplacer identifies when allocations can be reused and when a copy must be made. After the inplacer runs, the allocator reserves the memory that the VI needs to execute. Finally, the code generator is responsible for converting the DFIR graph into executable machine instructions for the target processor.

Back to Top

5. LLVM Provides a Low-Level Intermediate Representation

LLVM is a versatile, high-performance open source compiler framework originally invented as a research project at the University of Illinois. The LLVM is now widely used both in academia and industry due to its flexible, clean API and nonrestrictive licensing. In LabVIEW 2010, the LabVIEW code generator uses LLVM to generate target machine code. After creating the code stream from the DFIR graph, LabVIEW visits each instruction and creates an equivalent LLVM representation. The software invokes various optimization passes, and, finally, the LLVM just-intime (JIT) framework creates the executable machine instructions in memory. LabVIEW now uses LLVM to perform instruction combining, jump threading, scalar replacement of aggregates, conditional propagation, tail call elimination, loop invariant code motion, dead code elimination, and loop unrolling. 

Back to Top

6. DFIR and LLVM Work in Tandem

While DFIR is a high-level IR that preserves parallelism and LLVM is a low-level IR with knowledge of target machine characteristics, the pair works in tandem to optimize the LabVIEW code developers write for the processor architecture on which the code executes.

Chris Wood

Chris Wood is a senior software engineer for LabVIEW at National Instruments. He holds a bachelor’s degree in computer engineering from Texas A&M University.

 Craig Smith

Craig Smith is a principal software engineer at National Instruments. He holds bachelor’s and master’s degrees in computer science from Texas A&M University.

See a historical view on the evolution of the LabVIEW compiler and an in-depth discussion of the entire compile process

This article first appeared in the Q3 2010 issue of Instrumentation Newsletter.

Back to Top

Bookmark & Share


Ratings

Rate this document

Answered Your Question?
Yes No

Submit