Data the applications operates with usually stored in memory explicitly 'as-is'. In pursuit of reducing memory footprint many approaches are developed, such as storing data in packed format and unpacking on-the-fly when needed. Extreme example of packing is procedural generation when only most basic parameters are stored in memory and actual data generated by predefined algorithms. In Xreate **context** is any data(implicitly stored) which could be generated based on parameters reflecting place in the program code or, to be more precise, place in the [[Analysis/cfa|Control Flow Graph]] of program code, thereby context is data atatched to a code scope. ===Static context=== Static context is part of context fully known at the compile-time and thus use no memory for storing context data. import raw ("core/control-context.lp") //needed to activate context reasoning case context::zero { calculate = function::int {0} //return 0 } case context::one { calculate = function::int {1} //return 1 } test = function:: int; entry { context:: one. //set up context, consisting of expression 'one' calculate() // return 1 } In this example at the moment of executing function //calculate// context (//one//) is fully defined statically(at compile time). It represents case when information for decision of what specialization of //calculate// to choose requires no memory at all. In example below we have only one function specialization: import raw ("core/control-context.lp")" case context:: safe { funSensitive = function:: int {0} } test = function:: int; entry { context:: safe. funcSensitive() } There is only //safe// specialization of funSensitive and code compiles only if function called in expected context(//safe// in this example). NOTE:: The example shows that function specialization guards could express requirements(or //contract//)to a context it works within. ===Context propagation=== Simplest case is a **nested scope propagation**. The term denotes context propagation from a parent scope to a nested one, or in other words, nested scope inherits parent scope's context. import raw ("core/control-context.lp") case context::safe { //requires 'safe' context square = function(x:: int):: int { x * x }} test = function:: int; entry { // scopeA context:: safe; test. range = [1..10]:: [int]. loop fold(range->x::int, 0->acc):: int { //scopeB acc + square(x) // In the nested scope 'safe' context still accessable } } The example demonstrates availability of a `safe` annotation in the nested context of code scope `scopeB` despite being declared in other scope. More complicated case is **interfunction context propagation**. This stands for a context propagating through "caller/callee" relation: callee inherits caller context. The example below demonstates this: import raw ("core/control-context.lp") case context::toMillimeters { convertConcrete = function(source:: num)::num { 10 * source } } case context::toInches { convertConcrete = function(source:: num)::num { 2 * source } } convert= function(source:: num):: num { convertConcrete(source) } test = function(source:: num):: num; entry { context:: toMillimeters. convert(1) } To sort out what's going on here the control flow presented below: {Fres/diagram-context-propagation-interfunction-static.dia} There is only one path to reach `convertConcrete` and consequenlty context could be determind statically(on compile-time). Since root function `test` sets up context `toMillimeters` only one relevant specialization of `convertConcrete` is vaild. And now let's consider even more compilcated case with several different context pathes in CFG: {Fres/diagram-context-propagation-interfunction-mixed, layout=right, float} import raw ("core/control-context.lp") compute = function::int { 0 } computeFast = function:: int { context:: computation(fast). compute() } computePrecisely = function:: int { context:: computation(precise). compute() } test = function(cmnd:: int):: int; entry { context:: arithmetic(iee754). if (cmnd > 0)::int {computePrecisely()} else {computeFast()} } Here several(that is two) pathes exists to reach function `compute` and moreover each path inroduces its own context. Statically could be determined only path invariant context annotations or annotations that are hold for any possible path to a given code scope. !!In other words, statically determined context is a conjuction of all possible contexts of a given code scope.!! Thus, in the given example only `arithmetic(iee754)` annotattion is statically determined for function `compute` since it defined in the `test` function which is a root in the CFG(more generally, `test` //dominates// 'compute'). ===Context rules=== Full utility of annotations is exposed if provided that we able to infer new annotations or decisions by employing rules of inference. Given specific and somewhat compilcated nature of context annotations propagation there should be specific rules that take in account these considerations. To put it simple, context rules are rules to extend context by deriving new context annotations from already exisiting ones. import raw ("core/control-context.lp") case context:: toMilli { convert = function(lengthInCm::int)::int{ 10 * length } } main=function::int; entry { context:: output(milli). //here we inroduce context rule(aliasing) rule context::toMilli case output(milli) {true} convert(1) } See [[Syntax/Annotations#annotation_rules| syntax of annotation rules]] for details. In this example obvious rule is inroduced having form like `AnnotationOld -> AnnotationNew` meaning //aliasing// to inroduce `AnnotationNew` as new context annotation whenever `AnnotationNew` holds. Host function `main` inroduces context `output(milli)` but callee requires slightly different context for execution(`toMilli`). Thus, in order to adapt context the rule for introducing //alias// could be applied. As a consequence, context of both functions consists of `output(milli)` and `toMilli` as well. It is obvious, that rule is applicable to an annotations if it defined in the same scope(as in example above) or below, in terms of CFG. ===Context rules propagation=== Slightly more complex situation is for rules defined above annotations it applicable to. import raw ("core/control-context.lp") case context:: toMilli { convert = function(lengthInCm::int)::int{ 10 * length } } funcIntermediate = function(lengthInCm::int)::int{ context:: output(milli). convert(1) } main=function::int; entry { //here we inroduce context rule(aliasing) rule context::toMilli case output(milli) {true} funcIntermediate(1) } Here rule is defined in root function `main` but appropriate annotations for rule are defined in another function(`funcIntermediate`). In other words, scope's context where rule is defined do not hold relevant annotations and therefore rule produces nothing new for the context. Reasonable behaviour would be to //inherit// rules as it done for context annotations in order to apply rule whenever possible for nested scopes or for scopes located below in CFG. Thus, unlike other rules a context rules implicilty propagated in the same way as it done for annotations. ===Late context=== Static scope context reasoning and consequent decisions is too //weak// to be usefull for real world applications, because it could deduce only context annotattions that are guarantied to be always true. However, for any cases it's cruicial to consider //possible contexts// that is, contexts valid only under certain conditions. import raw ("core/control-context.lp") case context:: computation(fast) { compute = function:: num { 0 } } case context:: computation(precise) { compute = function:: num { 0 } } executeComputation= function:: num { compute() } test = function(cmnd:: num):: num; entry { if (cmnd > 0)::num { context:: computation(fast). executeComputation() } else { context:: computation(precise). executeComputation() } } In this example, static(compile-time) reasoning is fruitless and infer nothing for `compute` scope because there are two different pathes to reach `compute` from top function(`test`) and moreover each path conducts its own context: `computation(fast)` through true block of `IF` statement and `computation(precise)` through false block respectively. Such uncertainty could be resolved at runtime when one path or another is choosen. It leads to a necessity of having //late context// - context data gathered on relevant occasion at runtime to determing right decisions. To sum up, context consists of two complements parts: on the one hand //static(early) context// denotes compile time inferences, and on the other hand, //late(dynamic) context// denotes annotations decided upon at runtime. ===Remarks on late context implementation=== To return to a last example, in order to correcly determine `compute`'s context it's necessary: * to infer at compile-time every possible context for given scope * to store in memory key context data and fully restore particular context whenever needed As of now, to convey late context data //a hidden parameter injected to a function signature//. After such transformation signature of `executeComputation` looks like `executeComputation(__hidden_context_data__)`, where `hidden_context_data` holds data enough to determine within `executeComputation` which one of possible contexts it encountered with. Consequently, `executeComputation` decides which specialization of `compute` should be called based on `hidden_context_data` value. Only at run-time there is enough information for `executeComputation` to decide what specialization of `compute` to call. NOTE: Since it is possible to determine number of possible contexts with diffent outcome decisions, it is possible to determine least size for late context data enough to identify each possible variant. (In example above, since there are only two specializons of `compute`, 1 bit is enough to convey late context data) ===Path dependent late context=== TODO: fill in this section ===Context loops=== TODO: fill in this section See also: * [[Articles/gestalts#static_dynamic_antagonism]] * [[Articles/gestalts#static_dynamic_antagonism#polymorphism]]