Listings

A listing is a list of instructions in the intermediate language that will eventually be compiled into a function that can be executed by the CPU. The listing also stores metadata that describes the context in which the code operates. This metadata includes:

To allow the code in the listing to refer to variables and labels in the listing, the listing assigns each label and variable a unique name. Since the Intermediate Language is designed as a compilation target for other languages, the names are simply integer indexes. However, because the listing is in charge of assigning the names for variables and labels, the listing can be thought of as representing a namespace for these names, since the created names are only meaningful in the context of the listing that created them.

Listings are represented by the class core.asm.Listing. In Basic Storm, it is possible to create a listing as follows:

use lang:bs:macro; // For named{}
use core:asm;

void main() {
    // Is the function going to be a member function?
    Bool isMember = false;

    // What is the result type of the function?
    TypeDesc resultType = named{Int}.typeDesc();

    // Create the listing:
    Listing listing(isMember, resultType);

    // Add code to the listing:
    listing << prolog();
    // ...

    print(listing.toS());
}

As we can see from the code above, creating a listing requires knowing the type returned from the listing, and if it is a member function of a type or not. The reason for this is that these two pieces of information affect the calling convention on some systems.

Labels

As previously mentioned, a listing acts as a namespace for labels and variables. Therefore, new and unique names for labels are generated by the listing itself using the label member:

Label loopStart = listing.label();

Labels can then be inserted into the listing using the << operator. This makes the label refer to the next instruction that is inserted:

listing << loopStart;
listing << instr;

Blocks

Variables in a listing are ordered in a tree of blocks. These blocks work similarly to blocks in Basic Storm. Each block is active for some subset of the instructions in the listing, and any variables contained in the block are only accessible to those instructions. Similarly to labels, blocks are given names in the form of integers.

The listing initially contains a root block for variables that are visible directly from after the prolog, to the epilog. This block is accessible by calling the member root() in the Listing object. It is possible to create new blocks by calling createBlock and specifying which block the new block should be a child of. The Listing also contains members to traverse the tree of blocks, and the variables in them.

Blocks are lexically scoped in the output. A block begins at a begin <block> instruction, and ends at the corresponding end <block> instruction. The root block is considered to begin at the prolog instruction and end at the epilog instruction.

To illustrate how blocks are created and activated, consider the example below:

use core:asm;

void main() {
     Listing l(false, voidDesc);

     // Create a block inside the root:
     Block child = l.createBlock(l.root());

     // Activate the root block.
     l << prolog();

     // Start the child block.
     l << begin(child);

     // ...

     l << end(child);

     // Function epilog and return.
     l << fnRet();
}

Variables and Parameters

Each block contains a list of variables. Some of the variables in the root block may be parameters to the function. Each variable records its data type. In this context, the data type is simply the size of the variable, and a reference to a destructor to call whenever the variable should be destroyed. For parameters, the system also records the type description in order to follow the system's calling convention properly. Each variable may also be associated with additional metadata in the form of a core.asm.Listing.VarInfo object. This object stores higher-level data that can be used by debuggers (for example, Progvis).

When a block begins, all contained variables are initialized to zero. Similarly, all variables are automatically destroyed whenever the block ends, or when an exception occurs. The destruction behavior depends on whether a destructor was specified, and the options in the core.asm.FreeOpt parameter the variable was created. The following options determine when the destructor is called, assuming one is specified:

There are also two options that can be used to modify the behavior of the cleanup. These can be bitwise or:ed with the options above:

The following example illustrates how variables and parameters can be created and used:

use core:asm;

void main() {
    Listing l(false, voidDesc);

    // Create an integer parameter. Passing a type description (here, for an integer)
    // generally does the right thing regarding cleanup.
    Var param1 = l.createParam(intDesc);

    // Create an integer variable in the root block. Again, passing
    // a type description generally does the right thing regarding cleanup.
    Var local1 = l.createVar(l.root, intDesc);


    // Prolog, initializes the root block.
    l << prolog();

    // Copy the parameter to the local variable.
    l << mov(local1, param1);

    // End.
    l << fnRet();
}