Context
Use the information about control flow and code hierachy

Computer program, its internal states and transitions between them can be looked at from two different sides: control flow and data flow. This chapter expands on a specific mechanism called context to reflect control flow as well as a program's structure to facilitate optimizations, safety measures and supporting code. In simpler terms it is a tool to adjust a service to serve the best particular needs of clients that actually use it.

Block level annotations are used to define context and to reason about it. See syntax.

Examples of Context Usage: Suggestions and Requirements

Context can be used to convey various forms of information as shown in a few examples below.

tests/latex.cpp: Latex.Doc_Examples1
//someStringFn = function:: string {...}
            
main = function:: string; entry 
{
    context:: env(utf8).

    someStringFn()
}

In this example the env(utf8) annotation conveys some information about a code block distinguishing it from others, which allows to apply specific compilation rules for this block. Suppose someStringFn has different specializations for different environments. Now it's possible to invoke a specialization tailored for the UTF-8 environment. In this case a context can be viewed as a suggestion to pick up an appropriate specialization.

On the other hand, there is a possibility to define expected properties of a context:

tests/latex.cpp: Latex.Doc_Examples2
guard::                         only_safe_env
{                             
  crucialOperation = function:: bool 
    {true}
}

main = function::               bool; entry 
{
  context::                     env(safe).
  crucialOperation()
}

Function crucialOperation has only one specialization only_safe_env in the example. If the context does not provide the required environment env(safe) the compiler can't find an appropriate specialization and reports a compilation error. This is a way for a function to express requirements or a contract to a context it works within.

Context Propagation

Context propagation means that contexts of different blocks are related reflecting control flow. Context of a particular code block consists of the following:

  • Local context: annotations that are defined within the block.
  • Parent context. Block's context inherits context of its parent block reflecting enclosing - nested code blocks relation.
  • Client context. Block's context inherits a caller's context reflecting clients of a particular function or caller-callee relation. It allows to take into account how functions are used.

An example below:

tests/latex.cpp: Latex.Doc_ContextPropagation1
//requires 'safe' context
guard::                                          only_safe_env 
{
  crucialOperation = function(a:: int, b::int):: int 
    { 0 }
}

test = function:: int; entry {
  //blockA
  context:: env(safe).

  range = [1..10]:: [int].   
  loop fold(range->x::int, 0->acc):: int {  
      //blockB
      crucialOperation(x, acc)
  }
}

demonstrates context propagation. There are no compilation errors for the context of blockB contains inherited property env(safe) defined in blockA.

Another case is when a callee inherits a caller's context. The example below demonstrates this:

tests/latex.cpp: Latex.Doc_ContextPropagation2
guard::                               units(mm)
{
  lengthToSI = function(x:: float)::  int
    { x / 1000 }
}

guard::                               units(min)
{
  timeToSI = function(x:: float)::    int
    { x * 60 }
}

guard::                               units(m, sec)
{
  velocityToEnv = function(x:: float)::  int
    { x }
}

calculateVelocity = function(length:: float, time:: float):: float
{ 
  velocity_si = lengthToSI(length) / timeToSI(time).

  velocityToEnv(velocity_si)
}
            
test = function::               int; entry 
{
  context::                     length(units(mm)); time(units(min)); velocity(units(m, sec)).
  calculate(10, 2)
}

Function calculateVelocity() works with values measured in different units. It normalizes each value by invoking the ..toSI() functions; after the computation is done it converts the result back to an environment's units. One approach is to keep unit information for each variable independently. But if we know that an entire program or a part of it works only with specific units we can register it in a context, such as length(units(mm)) in this example, letting the function calculateVelocity() and its callees to inherit the context, thus allowing the compiler to generate code tailored to specific units only.

Latex (Late Context)

Importance of context as a development tool stems from the fact that if reflects among other thing a code's clients - by whom a given code is used. This allows to tailor a service based on specific clients' needs. Unfortunately this sort of information cannot be gathered fully at compile time. Compile time or static context reasoning is partial in a sense that it is capable of inferring only properties that are true for all possible paths leading in a CFG to a given block. Beyond that are properties that depend on an exact path in a CFG. Such uncertainty is possible to resolve only at runtime once it's known which path exactly is chosen. If a function is called from different locations, with each location having a different context, the context of the function in question is obviously different during each invocation. To solve this problem late context (latex, in short) is introduced - a mechanism of embedding into compiled code certain instructions to gather data on relevant occasions at runtime to determine an exact or strong context.

Latex approach can be described as follows:

  • A cloud of all possible context facts(weak context) for a given block is computed during compile time. Each such fact is associated with code paths it is held for.
  • During execution the information is gathered which facts are hold depending on an actual code path. There are late parameters reserved to store late context. To pass on latex parameters, they are injected into functions as hidden parameters.
  • Late parameters are used internally to match against guards to determine which specialization to invoke.

An example:

tests/latex.cpp: Latex.Doc_Latex1
import raw ("scripts/cfa/context.lp").         //enable context reasoning

guard:: sum
{
  compute = function(arg1:: int, arg2:: int):: int 
  {
      arg1 + arg2
  }
}

guard:: sub
{
  compute = function(arg1:: int, arg2:: int):: int 
  {
      arg1 - arg2
  }
}

executeComputation= function(arg1:: int, arg2:: int):: int
{
  compute(arg1, arg2) //`compute` has several specializations
}

test = function(cmnd:: int, arg1:: int, arg2:: int)::  int; entry
{
  if (cmnd > 0)::int  
  {
    //scopeA
    context::                  sum.
    executeComputation(arg1, arg2)
  
  } else 
  {
    //scopeB
    context::                  sub.
    executeComputation(arg1, arg2)
  } 
}

Function executeComputation is used in different contexts in scopes scopeA, scopeB, thus it is impossible to infer statically which compute 's specialisation it should invoke. In this case executeComputation function's signature is expanded to take in a hidden late parameter with its value conveying an actual context in order to dynamically invoke an appropriate specialization at run time.