Host Initialization

Host initialization defines the configuration and initial state of the application.

Host initialization is one of the more novel features of Pollen. Understanding it is easier in the context of practical examples, usage cases, and code. We will first define it and then discuss its usage and value.

This section assumes a basic understanding of Pollen modules, classes, protocols, and compositions.

Host initialization defines the configuration and initial state of the application.

A Pollen program is translated to C on a host computer and it will load and run on a target microcontroller. The build flow on the host is pictured below. Note that the Pollen translator produces initialization scripts as well as C files.

img:pollen build flow

The initialization scripts are executed by the Pollen translator during the translation and the results determine the initial state of Pollen variables and objects when the application runs on target hardware.

The term host phase refers to the execution of these initialization scripts on the host computer. To make the program more efficient and robust, the host phase can handle complex initialization and allocation that in other programming languages must be done at runtime. Pollen off loads this work to the host computer via the initialization scripts. This is what is meant by the term host initialization.

Embedded applications often have a high degree of similarity in their code functionality, to do the basic work of controlling the hardware, within a context of high variability and complexity in initial configuration. Pollen facilitates code reuse by supporting sophisticated configuration capabilities during the host phase that allows the same code to operate under a variety of configurations. Note that host initialization can also make code easier to reuse. Configuration and initialization can be handled via host initialization in ways that minimize source code changes for different applications and contexts. We will see examples of this below.

A function is a host function if it is defined with the host attribute:

 host foo() { print "executed at host time" }

A function without the host attribute is a target function.

 bar() { print "executed at target time" }

Host functions do not exist in the application as it runs on the target hardware. They are present and executed during the host phase only. Host functions cannot call target functions and target functions cannot call host functions.

The host attribute can appear on function definitions including module and class constructors. Host constructors are called by the translator during the host phase (and host functions are callable by host constructors).

The declaration below will result in the invocation of the MyClass host constructor. Note that the new keyword used with the attribute host invokes the host class constructor. The result will be an object that will be statically initialized during target application start up.

host MyClass ref = new MyClass(1)

Data items can be declared host. This includes simple data items of primitive type, objects of class type, arrays, and function references.

host bool isMaster = true

A host variable of primitive type which is assigned during the host phase is initialized as a constant. This has particular value in the embedded context. On many embedded systems there is much more program Flash (or ROM) than RAM. Therefore, if variables do not change during execution, it reduces pressure on RAM to allocate them as constants in Flash.

There are a number of use cases for host data.

Use Case: Simple Host Variables of Primitive Type

The first use case we’ll examine shows host variables of primitive type. It is from the example used in the section on modules, I2C, which is found here. This module declared four host data items of primitive type:

host bool isMaster = true
host uint8 busSpeed = BusSpeed.STANDARD
host uint8 prescaler = 0
host uint8 bitrate = 0

This module can be either master or slave, and that is configured by clients of the module through two host functions. A client will either call the first or second routine below, depending on configuation choices:

  public host setMasterMode(uint32 speed) {
    isMaster = true
  }

  public host setSlaveMode(uint32 speed) {
    isMaster = false
  }

These functions each initialize another set of host variables whose value depends on the setting of the isMaster flag: busSpeed, prescaler, and bitrate. Thus the I2C module can be configured flexibly, as master or slave, depending on client requirements and without changing I2C source code.

Also all of these host variables will be allocated as constants in Flash. These data items are naturally variable during the host phase but constant during application execution because they reflect different use cases for hardware that are inherently constant once execution begins - bus speed, master or slave, etc.

Use Case: Host Variables of Class Type

The test program TimerBlink declares a host Timer object:

host Timer t1 = new Timer(tick1)

This is a host declaration so the host constructor for Timer will be executed during the host phase. When the application begins execution on the target hardware, the Timer (and the host Event object it contains) are statically initialized. This avoids dynamic memory allocation, configuration, and initialization. This is more efficient, which is important for embedded applications.

We also create more robust systems by eliminating dynamic memory allocation. As this is a statically allocated object there will be no memory leaks.

Note that with dynamic memory allocation, the state of the heap can vary. Using dynamic memory allocation makes a program non-deterministic. Many embedded applications are mission critical, where application failure means a risk of loss of life. For such applications, deterministic program behavior is always preferred and often required. Host initialization addresses these issues, as it allows you to avoid dynamic memory allocation in a way that is intuitive and straight forward.

Use Case: Arrays with Host Dimensions

Timer clients should be able to request new timers without changing any code in the Timer module. This issue is handled in other languages by computing the data structure size at runtime and then allocating it dynamically. In Pollen, for the embedded context, we wish to avoid dynamic memory allocation. That means the internal Timer array should have constant size. How can we grow arrays when they are defined as having constant size without changing the internal Timer implementation?

It’s straightforward to do this in Pollen. We use a host variable to hold the array size. Then it will be computed during the host phase. At load time, the array size is a constant. Then the array can be allocated and initialized statically to that constant size. (This is how it is implemented in the timer modules in the pollen-core cloud bundle.)

Here are the relevant declarations in TimerManager:

host uint8 numMsTimers = 1
Timer msTimers[numMsTimers] = {null}

The array msTimers should be allocated to be the right size to hold all timers. During the host phase, the host constructor for Timer calls registerTimerOnHost to increment that size for each new Timer. Here is the host constructor for Timer:

  public host Timer(HP.handler h) {
      TimerManager.registerTimerOnHost(@)     // Notify the TimerManager of this Timer
  }

The function registerTimerOnHost will increment the host variable numMsTimers:

public host registerTimerOnHost(Timer t) {
    numMsTimers += 1
}

At the end of the host phase numMsTimers will have been incremented for every timer created. The array msTimers can be statically allocated and initialized. If the client adds two new timers:

host Timer t1 = new Timer(tick1)
host Timer t2 = new Timer(tick2)
host Timer t3 = new Timer(tick3)

Then at program startup the internal Timer array msTimers will have the correct (static) size of 3.

The preset initializer is a type of host initialization. It is a special routine that is supported only for compositions. Like all code defined in compositions, it executes during the host phase. Recall that the purpose of compositions is to assemble, configure, and initialize sets of modules during the host phase. The preset initializer provides an additional mechanism for compositions to fullfill this purpose.

A unique feature of the preset initializer is that it can initialize module private data in any of its modules. It provides access from outside the module to private data. This allows modules to be configured according to the requirements of their clients. It also enables modules to adapt to hardware variability without changes to their source code.

Here the private variable clk_rate declared in MyApp is being configured in a composition Config.

from Mcu import MyApp
composition Config {

   preset Config() {

      MyApp.clk_rate = CLOCK_RATE

   }
}

In preset initializers protocol members can be bound and variables which have been declared either in module scope or class scope can be initialized. More specifically these variable initializations are supported:

  • Target variables declared in module scope can be initialized to constants.
  • Host variables declared in module scope can be initialized to expressions or (host) function returns.
  • All variables declared in class scope (host and target) can be initialized to constants.

The section on compositions contains another example of the preset initializer usage. That example shows how protocol members can be bound and host functions can be called in preset initializers.

This is the order of host initialization phases:

  1. Host variables are initialized to the values on their declarations.
  2. Preset initializers are executed.
  3. Host constructors are executed, along with any host functions they call, in an undefined order.

Note that the host constructor for the top level module will always run last.