A Quick Introduction to Virgil - Initialization

This is part of a basic introduction to programming in Virgil and gives an overview of the language, its syntax, and its structures. This page focuses on the initialization phase, where a Virgil program can execute initialization code and allocate its data structures during compilation.

Overview

Virgil is designed for building software systems for resource-constrained devices such as microcontrollers. As discussed in the philosophy page, most microcontrollers do not have enough RAM space to support a proper memory management system including dynamic memory allocation and a general purpose garbage collector. For that reason, prior to Virgil, many programs for microcontrollers written in such languages as C and nesC were already designed to preallocate all of their required memory statically and reuse it through execution, without performing dynamic memory allocation. However, though memory could be statically allocated, these languages have limited support for initializing the memory.

Virgil extends the common practice of static allocation into a first-class language feature. Virgil provides a phase during compilation by which an application may execute arbitrary initialization code and preallocate all of its data structures. The Virgil compiler includes an interpreter for the complete language. Ask explained earlier, you have been using this same interpreter to experiment with Virgil up until this point in this tutorial.

Component Initializers

component A {
    field f: int;
    constructor() {
        // initialization code.
        f = 100 * 97 / 8;
    }
}

As you saw in the components page, components serve as units of related functionality, including global methods and fields. They also serve a second purpose; they serve as the unit of compile-time initialization. Each component may define an optional constructor that contains code that is executed inside the compiler during initialization. This code can execute arbitrary computation; it can call other methods, allocate objects, call methods on those objects, etc (it can even perform non-terminating computation). The purpose of this initialization code is to allocate the data structures of the program that will be needed at runtime and save them for use.

Initialization Garbage

The initialization phase allows unrestricted computation and allocation. This phase is backed by a general purpose garbage collector that will discard unreachable objects allocated by the initializers. After initialization is complete, the fields of components serve as roots into the graph of objects that represents the entire heap of the program. The compiler traces from the component fields through objects and object fields to discover everything transitively reachable from the roots. Temporary objects allocated during initialization that are not reachable are discarded. Only the live objects are included in the final program binary.

Code Generation and Runtime

After the initialization phase, the Virgil compiler will compile both the code and the heap of the program together into a single binary that can be loaded onto the device or executed in a simulator. When the program begins execution on the device, the entire initialized heap is available in memory and the program can manipulate these objects normally, but the program will not be allowed to allocate new objects. Any attempt to allocate new objects or arrays will result in an AllocationException.

Go back to the tutorial.