+ All Categories
Home > Documents > Reactive Imperative Programming with Dataflow Constraints

Reactive Imperative Programming with Dataflow Constraints

Date post: 05-Feb-2023
Category:
Upload: independent
View: 0 times
Download: 0 times
Share this document with a friend
23
arXiv:1104.2293v1 [cs.PL] 12 Apr 2011 Reactive Imperative Programming with Dataflow Constraints Camil Demetrescu Dept. of Computer and System Sciences Sapienza University of Rome [email protected] Irene Finocchi Dept. of Computer Science Sapienza University of Rome [email protected] Andrea Ribichini Dept. of Computer and System Sciences Sapienza University of Rome [email protected] Abstract Dataflow languages provide natural support for specifying con- straints between objects in dynamic applications, where programs need to react efficiently to changes of their environment. Re- searchers have long investigated how to take advantage of dataflow constraints by embedding them into procedural languages. Previ- ous mixed imperative/dataflow systems, however, require syntactic extensions or libraries of ad hoc data types for binding the imper- ative program to the dataflow solver. In this paper we propose a novel approach that smoothly combines the two paradigms without placing undue burden on the programmer. In our framework, programmers can define ordinary commands of the host imperative language that enforce constraints between objects stored in special memory locations designated as “reac- tive”. Differently from previous approaches, reactive objects can be of any legal type in the host language, including primitive data types, pointers, arrays, and structures. Commands defining con- straints are automatically re-executed every time their input mem- ory locations change, letting a program behave like a spreadsheet where the values of some variables depend upon the values of other variables. The constraint solving mechanism is handled transpar- ently by altering the semantics of elementary operations of the host language for reading and modifying objects. We provide a formal semantics and describe a concrete embodiment of our technique into C/C++, showing how to implement it efficiently in conven- tional platforms using off-the-shelf compilers. We discuss common coding idioms and relevant applications to reactive scenarios, in- cluding incremental computation, observer design pattern, and data structure repair. The performance of our implementation is com- pared to ad hoc problem-specific change propagation algorithms, as well as to language-centric approaches such as self-adjusting com- putation and subject/observer communication mechanisms, show- ing that the proposed approach is efficient in practice. Categories and Subject Descriptors D.3.3 [Programming Languages]: Language Constructs and Features—Constraints General Terms Algorithms, design, experimentation, languages. Keywords Reactive programming, dataflow programming, im- perative programming, constraint solving, incremental computa- tion, observer design pattern, data structure repair. 1. Introduction A one-way, dataflow constraint is an equation of the form y = f (x 1 ,...,x n ) in which the formula on the right side is automatically re-evaluated and assigned to the variable y whenever any variable x i changes. If y is modified from outside the constraint, the equation is left temporarily unsat- isfied, hence the attribute “one-way”. Dataflow constraints are recognized as a powerful programming methodology in a variety of contexts because of their versatility and sim- plicity [38]. The most widespread application of dataflow constraints is perhaps embodied by spreadsheets [2, 28]. In a spreadsheet, the user can specify a cell formula that de- pends on other cells: when any of those cells is updated, the value of the first cell is automatically recalculated. Rules in a makefile are another example of dataflow constraints: a rule sets up a dependency between a target file and a list of input files, and provides shell commands for rebuilding the target from the input files. When the makefile is run, if any input file in a rule is discovered to be newer than the tar- get, then the target is rebuilt. The dataflow principle can be also applied to software development and execution, where the role of a cell/file is replaced by a program variable. This approach has been widely explored in the context of interac- tive applications, multimedia animation, and real-time sys- tems [13, 24, 34, 39]. Since the values of program variables are automatically recalculated upon changes of other values, the dataflow com- putational model is very different from the standard imper- ative model, in which the memory store is changed explic- itly by the program via memory assignments. The execution flow of applications running on top of a dataflow environ- ment is indeed data-driven, rather than control-driven, pro- viding a natural ground for automatic change propagation in all scenarios where programs need to react to modifications of their environment. Implementations of the dataflow prin- ciple share some common issues with self-adjusting compu- 1 2011/4/13
Transcript

arX

iv:1

104.

2293

v1 [

cs.P

L] 1

2 A

pr 2

011

Reactive Imperative Programming with Dataflow Constraints

Camil DemetrescuDept. of Computer and System Sciences

Sapienza University of Rome

[email protected]

Irene FinocchiDept. of Computer Science

Sapienza University of Rome

[email protected]

Andrea RibichiniDept. of Computer and System Sciences

Sapienza University of Rome

[email protected]

AbstractDataflow languages provide natural support for specifying con-straints between objects in dynamic applications, where programsneed to react efficiently to changes of their environment. Re-searchers have long investigated how to take advantage of dataflowconstraints by embedding them into procedural languages. Previ-ous mixed imperative/dataflow systems, however, require syntacticextensions or libraries ofad hocdata types for binding the imper-ative program to the dataflow solver. In this paper we proposeanovel approach that smoothly combines the two paradigms withoutplacing undue burden on the programmer.

In our framework, programmers can define ordinary commandsof the host imperative language that enforce constraints betweenobjects stored in special memory locations designated as “reac-tive”. Differently from previous approaches, reactive objects canbe of any legal type in the host language, including primitive datatypes, pointers, arrays, and structures. Commands definingcon-straints are automatically re-executed every time their input mem-ory locations change, letting a program behave like a spreadsheetwhere the values of some variables depend upon the values of othervariables. The constraint solving mechanism is handled transpar-ently by altering the semantics of elementary operations ofthe hostlanguage for reading and modifying objects. We provide a formalsemantics and describe a concrete embodiment of our techniqueinto C/C++, showing how to implement it efficiently in conven-tional platforms using off-the-shelf compilers. We discuss commoncoding idioms and relevant applications to reactive scenarios, in-cluding incremental computation, observer design pattern, and datastructure repair. The performance of our implementation iscom-pared toad hocproblem-specific change propagation algorithms, aswell as to language-centric approaches such as self-adjusting com-putation and subject/observer communication mechanisms,show-ing that the proposed approach is efficient in practice.

Categories and Subject Descriptors D.3.3 [ProgrammingLanguages]: Language Constructs and Features—Constraints

General Terms Algorithms, design, experimentation, languages.

Keywords Reactive programming, dataflow programming, im-perative programming, constraint solving, incremental computa-tion, observer design pattern, data structure repair.

1. IntroductionA one-way, dataflow constraint is an equation of the formy = f(x1, . . . , xn) in which the formula on the right sideis automatically re-evaluated and assigned to the variabley whenever any variablexi changes. Ify is modified fromoutside the constraint, the equation is left temporarily unsat-isfied, hence the attribute “one-way”. Dataflow constraintsare recognized as a powerful programming methodology ina variety of contexts because of their versatility and sim-plicity [38]. The most widespread application of dataflowconstraints is perhaps embodied by spreadsheets [2, 28]. Ina spreadsheet, the user can specify a cell formula that de-pends on other cells: when any of those cells is updated, thevalue of the first cell is automatically recalculated. Rulesina makefile are another example of dataflow constraints: arule sets up a dependency between a target file and a list ofinput files, and provides shell commands for rebuilding thetarget from the input files. When the makefile is run, if anyinput file in a rule is discovered to be newer than the tar-get, then the target is rebuilt. The dataflow principle can bealso applied to software development and execution, wherethe role of a cell/file is replaced by a program variable. Thisapproach has been widely explored in the context of interac-tive applications, multimedia animation, and real-time sys-tems [13, 24, 34, 39].

Since the values of program variables are automaticallyrecalculated upon changes of other values, the dataflow com-putational model is very different from the standard imper-ative model, in which the memory store is changed explic-itly by the program via memory assignments. The executionflow of applications running on top of a dataflow environ-ment is indeed data-driven, rather than control-driven, pro-viding a natural ground for automatic change propagation inall scenarios where programs need to react to modificationsof their environment. Implementations of the dataflow prin-ciple share some common issues with self-adjusting compu-

1 2011/4/13

tation, in which programs respond to input changes by up-dating automatically their output [3, 4, 25].

Differently from purely declarative constraints [7], data-flow constraints are expressed by means of (imperative)methods whose execution makes a relation satisfied. Thisprogramming style is intuitive and readily accessible to abroad range of developers [38], since the ability to smoothlycombine different paradigms in a unified framework makesit possible to take advantage of different programming stylesin the context of the same application. The problem of in-tegrating imperative and dataflow programming has alreadybeen the focus of previous work in the context of specificapplication domains [11, 32–34, 38]. Previous mixed imper-ative/dataflow systems are based on libraries ofad hocdatatypes and functions for representing constraint variablesandfor binding the imperative program to the constraint solver.One drawback of these approaches is that constraint vari-ables can only be of special data types provided by the run-time library, causing loss of flexibility and placing undueburden on the programmer. A natural question is whether thedataflow model can be made to work with general-purpose,imperative languages, such as C, without adding syntacticextensions andad hocdata types. In this paper we affirma-tively answer this question.

Our Contributions. We present a general-purpose frame-work where programmers can specify generic one-way con-straints between objects of arbitrary types stored inreactivememory locations. Constraints are written as ordinary com-mands of the host imperative language and can be addedand removed dynamically at run time. Since they can changemultiple objects within the same execution, they are multi-output. The main feature of a constraint is its sensitivity tomodifications of reactive objects: a constraint is automati-cally re-evaluated whenever any of the reactive locations itdepends on is changed, either by the imperative program, orby another constraint. A distinguishing feature of our ap-proach is that the whole constraint solving mechanism ishandled transparently by altering the semantics of elemen-tary operations of the host imperative language for readingand modifying objects. No syntax extensions are requiredand no new primitives are needed except for adding/remov-ing constraints, allocating/deallocating reactive memory lo-cations, and controlling the granularity of solver activations.Differently from previous approaches, programmers are notforced to use any special data types provided by the languageextension, and can resort to the full range of conventionalconstructs for accessing and manipulating objects offeredbythe host language. In addition, our framework supports allthe other features that have been recognized to be importantin the design of dataflow constraint systems [38], including:

Arbitrary code: constraints consist of arbitrary code that islegal in the underlying imperative language, thus includ-ing loops, conditionals, function calls, and recursion.

Address dereferencing: constraints are able to referencevariables indirectly via pointers.

Automatic dependency detection: constraints automaticallydetect the reactive memory locations they depend on dur-ing their evaluation, so there is no need for program-mers to explicitly declare dependencies, which are alsoallowed to vary over time.

We embodied these principles into an extension of C/C++that we called DC. Our extension has exactly the same syn-tax as C/C++, but a different semantics. Our main contribu-tions are reflected in the organization of the paper and canbe summarized as follows:

• In Section 2 we abstract our mechanism showing howto extend an elementary imperative language to supportone-way dataflow constraints using reactive memory. Wedistinguish between three main execution modes: nor-mal, constraint, and scheduling. We formally describeour mixed imperative/dataflow computational model bydefining the interactions between these modes and pro-viding a formal semantics of our mechanism.

• In Section 3 we discuss convergence of the dataflow con-straint solver by modeling the computation as an iterativeprocess that aims at finding a common fixpoint for thecurrent set of constraints. We identify general constraintproperties that let the solver terminate and converge to acommon fixpoint independently of the scheduling strat-egy. This provides a sound unifying framework for solv-ing both acyclic and cyclic constraint systems.

• In Section 4 we describe the concrete embodiment of ourtechnique into C/C++, introducing the main features ofDC. DC has exactly the same syntax as C/C++, but oper-ations that read or modify objects have a different seman-tics. All other primitives, including creating and deletingconstraints and allocating and deallocating reactive mem-ory blocks, are provided as runtime library functions.

• In Section 5 we give a variety of elementary and ad-vanced programming examples and discuss how DC canimprove C/C++ programmability in three relevant appli-cation scenarios: incremental computation, implementa-tion of the observer software design pattern, and datastructure checking and repair. To the best of our knowl-edge, these applications have not been explored before inthe context of dataflow programming.

• In Section 6 we describe how DC can be implementedusing off-the-shelf compilers on conventional platformsvia a combination of runtime libraries, hardware/operat-ing system support, and dynamic code patching, withoutrequiring any source code preprocessing.

• In Section 7 we perform an extensive experimental anal-ysis of DC in a variety of settings, showing that our im-plementation is effective in practice. We consider both

2 2011/4/13

interactive applications and computationally demandingbenchmarks that manipulate lists, grids, trees, matrices,and graphs. We assess the performances of DC againstconventional C-based implementations as well as againstcompetitors that can quickly react to input changes, i.e.,ad hocdynamic algorithms, incremental solutions real-ized in CEAL [25] (a state-of-the-art C-based frame-work for self-adjusting computation), andQt’s signal-slot implementation of the subject/observer communica-tion mechanism [22].

Related work is discussed in Section 8 and directions forfuture research are sketched in Section 9.

2. Abstract ModelTo describe our approach, we consider an elementary im-perative language and we show how to extend it to supportone-way dataflow constraints. We start from WHILE [35], anextremely simple language of commands including a sub-language of expressions. Although WHILE does not sup-port many fundamental features of concrete imperative lan-guages (including declarations, procedures, dynamic mem-ory allocation, type checking, etc.), it provides all the build-ing blocks for a formal description of our mechanism, ab-stracting away details irrelevant for our purposes. We dis-cuss how to modify the semantics of WHILE to integrate adataflow constraint solver. We call the extended languageDWHILE. DWHILE is identical to WHILE except for a dif-ferent semantics and additional primitives for adding/delet-ing constraints dynamically and for controlling the granular-ity of solver activations. As we will see in Section 4, theseprimitives can be supported in procedural languages as run-time library functions.

2.1 TheDWHILE Language

The abstract syntax of DWHILE is shown in Figure 1. The

e ∈ Exp ::= ℓ | v | (e) | . . .

c ∈ Comm ::=skip |ℓ := e |c1 ; c2 |if e then c1 elsec2 |while e do c |newcons c |delcons c |begin at c end at

Figure 1. Abstract syntaxof DWHILE.

language distinguishes be-tween commands and ex-pressions. We usec, c1,c2 as meta-variables rang-ing over the set of com-mandsComm, ande, e1,e2 as meta-variables rang-ing over the set of ex-pressionsExp. Canonicalforms of expressions areeither storage locationsℓ ∈Loc, or storable valuesvover some arbitrary do-mainV al. Expressions canbe also obtained by applying to sub-expressions any primi-tive operations defined over domainV al (e.g., plus, minus,etc.). Commands include:

normalmode

⇒nc ⇒e

S=Ø S ≠Ø

⇒s

schedulingmode

⇒cc ⇒ce

constraintmode

write to reactivememory location

constrainttermination

Figure 2. Transitions between different execution modes.

• Assignments of values to storage locations (ℓ := e). Thesecommands are the basic state transformers.

• Constructs for sequencing, conditional execution, and it-eration, with the usual meaning.

• Two new primitives,newcons anddelcons, for addingand deleting constraints dynamically. Notice that a con-straint in DWHILE is just an ordinary command.

• An atomic block construct,begin at c end at, thatexecutes a commandc atomically so that any constraintevaluation is deferred until the end of the block. Thisoffers fine-grained control over solver activations.

In Section 4 we will show a direct application of the con-cepts developed in this section to the C/C++ programminglanguages.

2.2 Memory Model and Execution Modes

Our approach hinges upon two key notions:reactive memorylocationsandconstraints. Reactive memory can be read andwritten just like ordinary memory. However, differently fromordinary memory:

1. If a constraintc reads a reactive memory locationℓ duringits execution, a dependency(ℓ, c) of c from ℓ is logged ina setD of dependencies.

2. If the value stored in a reactive memory locationℓ ischanged, all constraints depending onℓ (i.e., all con-straintsc such that(ℓ, c) ∈ D) are automatically re-executed.

Point 2 states that constraints are sensitive to modificationsof the contents of the reactive memory. Point 1 shows how tomaintain dynamically the setD of dependencies needed totrigger the appropriate constraints upon changes of reactivememory locations. We remark that re-evaluating a constraintc may completely change the set of its dependencies: priorto re-execution, all the old dependencies(−, c) ∈ D arediscarded, and new dependencies are logged inD during there-evaluation ofc.

As shown in Figure 2, at any point in time the executioncan be in one of three modes:normal execution, constraintexecution, or scheduling. As we will see more formally laterin this section, different instructions (such as reading a re-active memory location or assigning it with a value) mayhave different semantics depending on the current executionmode.

3 2011/4/13

We assumeeagerconstraint evaluation, i.e., out-of-dateconstraints are brought up-to-date as soon as possible. Thischoice is better suited to our framework and, as previous ex-perience has shown, lazy and eager evaluators typically de-liver comparable performance in practice [38]. Eager eval-uation is achieved as follows. A scheduler maintains a datastructureS containing constraints to be first executed or re-evaluated. As an invariant property,S is guaranteed to beempty during normal execution. As soon as a reactive mem-ory locationℓ is written, the scheduler queries the setD ofdependencies and adds toS all the constraints depending onℓ. These constraints are then run one-by-one in constraintexecution mode, and new constraints may be added toSthroughout this process. WheneverS becomes empty, nor-mal execution is resumed.

An exception to eager evaluation is related to atomicblocks. The execution of an atomic blockc is regarded as anuninterruptible operation: new constraints created during theevaluation ofc are just added toS. Whenc terminates, foreach reactive memory locationℓ whose value has changed,all the constraints depending onℓ are also added toS, andthe solver is eventually activated. Constraint executionsareuninterruptible as well.

We remark that any scheduling mechanism may be usedfor selecting fromS the next constraint to be evaluated: inthis abstract model we rely on a functionpick that imple-ments any appropriate scheduling strategy.

2.3 Configurations

A configuration of our system is a six-tuple

(ρ, a, σ,D, S, cself ) ∈ R×Bool×Σ×Dep×2Cons×Cons

where:

• R = {ρ : Loc → { normal, reactive}} is a set ofstore attributes, i.e., Boolean functions specifying whichmemory locations are reactive.

• Bool = {true, false} is the set of Boolean values.

• Σ = {σ : Loc → V al} is a set of stores mapping storagelocations to storable values.

• Cons is the set of constraints and2Cons denotes itspower set. A constraint can be any command in DWHILE,i.e., Cons = Comm. We use different names for thesake of clarity.

• Dep = 2Loc×Cons is the set of all subsets of dependen-cies of constraints from reactive locations.

Besides a storeσ and its attributeρ, a configuration includes:

• a Boolean flaga that istrue inside atomic blocks and isused for deferring solver activations;

• the setD of dependencies,D ⊆ Loc× Cons;

• the scheduling data structureS ⊆ Cons discussed above;

• a meta-variablecself that denotes thecurrentconstraint(i.e., the constraint that is being evaluated) in constraint

execution mode, and is undefined otherwise. If the sched-uler were deterministic,cself may be omitted from theconfiguration, but we do not make this assumption in thispaper.

2.4 Operational Semantics

Most of the operational semantics of the DWHILE lan-guage can be directly derived from the standard semanticsof WHILE. The most interesting aspects of our extension in-clude reading and writing the reactive memory, adding anddeleting constraints, excuting commands atomically, anddefining the behavior of the scheduler and its interactionswith the other execution modes. Rules for these aspects aregiven in Figure 4 and are discussed below.

Let ⇒e⊆ (Σ×Exp)×V al and⇒c ⊆ (Σ×Comm)×Σbe the standard big-step transition relations used in the oper-ational semantics of the WHILE language [35]. Besides⇒e

and⇒c, we use additional transition relations for expressionevaluation in constraint mode (⇒ce), command executionin normal mode (⇒nc), command execution in constraintmode (⇒cc), and constraint solver execution in schedulingmode (⇒s), as defined in Figure 3. Notice that expressionevaluation in normal mode can be carried on directly bymeans of transition relation⇒e of WHILE. As discussedbelow, relation⇒ce is obtained by appropriately modifying⇒e. Similarly, relations⇒nc and ⇒cc are obtained by ap-propriately modifying⇒c. All the rules not reported in Fig-ure 4 can be derived in a straightforward way from the cor-responding rules in the standard semantics of WHILE [35].

The evaluation of a DWHILE program is started by ruleEVAL , which initializes the atomic flaga to false and boththe scheduling queueS and the setD of dependencies to theempty set.

Writing Memory. Assigning an ordinary memory loca-tion in normal execution mode (rule ASGN-N1) just changesthe store as in the usual semantics of WHILE. This is alsothe case when the new value of the location to be assignedequals its old value or inside an atomic block. Otherwise, ifthe locationℓ to be assigned is reactive, the new value dif-fers from the old one, and execution is outside atomic blocks(rule ASGN-N2), constraints depending onℓ are scheduledin S and are evaluated one-by-one. As we will see, the tran-sition relation⇒s guaranteesS to be empty at the end of theconstraint solving phase. In conformity with the atomic ex-ecution of constraints, assignment in constraint mode (ruleASGN-C) just resorts to ordinary assignment in WHILE forboth normal and reactive locations. We will see in ruleSOLVER-2, however, that constraints can be neverthelessscheduled by other constraints if their execution changes thecontents of reactive memory locations.

Reading Memory. Reading an ordinary memory locationin constraint execution mode (rule DEREF-C1) just evaluatesthe location to its value in the current store: this is achievedby using transition relation⇒e of the WHILE semantics.

4 2011/4/13

⇒⊆ (R× Σ× Comm)× Σ 〈ρ, σ, c〉 ⇒ 〈σ′〉

⇒ce ⊆ (R× Σ× Cons ×Dep× Exp)× (Dep× V al) 〈ρ, σ, cself , D, e〉 ⇒ce 〈D′, v〉

⇒nc ⊆ (R×Bool × Σ×Dep× 2Cons × Comm)× (Σ×Dep× 2Cons) 〈ρ, a, σ,D, S, c〉 ⇒nc 〈σ′, D′, S′〉

⇒cc ⊆ (R×Σ×Dep× 2Cons × Cons ×Comm)× (Σ×Dep× 2Cons) 〈ρ, σ,D, S, cself , c〉 ⇒cc 〈σ′, D′, S′〉

⇒s ⊆ (R× Σ×Dep× 2Cons)× (Σ×Dep) 〈ρ, σ,D, S〉 ⇒s 〈σ′, D′〉

Figure 3. Transition relations for DWHILE program evaluation (⇒), expression evaluation in constraint mode (⇒ce),command execution in normal mode (⇒nc), command execution in constraint mode (⇒cc), and constraint solver execution inscheduling mode (⇒s).

ρ, a, S ⊢ 〈σ,D, c〉 ⇒nc 〈σ′, D

′〉

ρ ⊢ 〈σ, c〉 ⇒ σ′

where:

a = false

D = ∅S = ∅

ρ, σ, cself ⊢ 〈D, e〉 ⇒ce 〈D′

, v〉 σ′ = σ|ℓ 7→v

ρ, S, cself ⊢ 〈σ,D, ℓ := e〉 ⇒cc 〈σ′, D

′〉

(EVAL ) (ASGN-C)

σ ⊢ e ⇒e v σ′ = σ|ℓ 7→v

ρ, a,D, S ⊢ 〈σ, ℓ := e〉 ⇒nc σ′

S = ∅ S′ = {c | (ℓ, c) ∈ D}

σ ⊢ e ⇒e v σ′ = σ|ℓ 7→v ρ ⊢ 〈σ′

, D, S′〉 ⇒s 〈σ′′

, D′〉

ρ, a, S ⊢ 〈σ,D, ℓ := e〉 ⇒nc 〈σ′′

, D′〉

if ρ(ℓ) = normal or σ′(ℓ) = σ(ℓ) or a = true if ρ(ℓ) = reactive andσ′(ℓ) 6= σ(ℓ) anda = false

(ASGN-N1) (ASGN-N2)

σ ⊢ ℓ ⇒e v

ρ, σ, cself , D ⊢ ℓ ⇒ce vif ρ(ℓ) = normal

σ ⊢ ℓ ⇒e v D′ = D ∪ {(ℓ, cself )}

ρ, σ, cself ⊢ 〈D, ℓ〉 ⇒ce 〈D′

, v〉if ρ(ℓ) = reactive

(DEREF-C1) (DEREF-C2)

ρ, cself ⊢ 〈σ,D, S, c〉 ⇒cc 〈σ′

, D′

, S′〉

ρ, cself ⊢ 〈σ,D, S, begin at c end at〉 ⇒cc 〈σ′

, D′

, S′〉

ρ, a,D ⊢ 〈σ, S, c〉 ⇒nc 〈σ′

, S′〉

ρ, a,D ⊢ 〈σ, S, begin at c end at〉 ⇒nc 〈σ′

, S′〉

if a = true

(BEGINEND-C) (BEGINEND-N1)

S = ∅ a′ = true ρ,D ⊢ 〈a′

, σ, S, c〉 ⇒nc 〈σ′

, S′〉

S′′ = S

′ ∪ { c | (ℓ, c) ∈ D ∧ σ(ℓ) 6= σ′(ℓ) ∧ ρ(ℓ) = reactive } ρ ⊢ 〈σ′

, D, S′′〉 ⇒s 〈σ′′

, D′〉

ρ, a, S ⊢ 〈σ,D, begin at c end at〉 ⇒nc 〈σ′′, D

′〉if a = false

(BEGINEND-N2)

S = ∅ S′ = {c} ρ ⊢ 〈σ,D, S

′〉 ⇒s 〈σ′, D

′〉

ρ, a, S ⊢ 〈σ,D, newcons c〉 ⇒nc 〈σ′

, D′〉

if a = falseS

′ = S ∪ {c}

ρ, a, σ,D ⊢ 〈S, newcons c〉 ⇒nc S′

if a = true

(NEWCONS-N1) (NEWCONS-N2)

D′ = D \ {(−, c)} S

′ = S \ {c}

ρ, a, σ ⊢ 〈D, S, delcons c〉 ⇒nc 〈D′, S

′〉

S′ = S ∪ {c}

ρ, σ,D, cself ⊢ 〈S, newcons c〉 ⇒cc S′

(DELCONS-N) (NEWCONS-C)

D′ = D \ {(−, c)} S

′ = S \ {c}

ρ, σ, cself ⊢ 〈D,S, delcons c〉 ⇒cc 〈D′

, S′〉 ρ ⊢ 〈σ,D, S〉 ⇒s 〈σ,D〉

if S = ∅

(DELCONS-C) (SOLVER-1)

ρ ⊢ 〈σ,D′

, S \ {cself}, cself , cself 〉 ⇒cc 〈σ′

, D′′

, S′〉

ρ ⊢ 〈σ′

, D′′

, S′′〉 ⇒s 〈σ′′

, D′′′〉

ρ ⊢ 〈σ,D, S〉 ⇒s 〈σ′′

, D′′′〉

where:

cself = pick(S)D′ = D \ {(− , cself )}S′′ = S′ ∪ {c | (ℓ, c) ∈ D′′ ∧

σ(ℓ) 6= σ′(ℓ) ∧ ρ(ℓ) = reactive}

if S 6= ∅

(SOLVER-2)

Figure 4. DWHILE program evaluation.

5 2011/4/13

If the locationℓ to be read is reactive (rule DEREF-C2), anew dependency of the active constraintcself from ℓ is alsoadded to the setD of dependencies.

Executing Atomic Blocks. To execute an atomic blockin normal mode (rule BEGINEND-N2), the uninterruptiblecommandc is first evaluated according to the rules definedby transition ⇒nc. If the content of some reactive loca-tion changes due to the execution ofc, the solver is thenactivated at the end of the block. Thebegin at / end at

command has instead no effect when execution is alreadyatomic, i.e., in constraint mode (rule BEGINEND-C) and in-side atomic blocks (rule BEGINEND-N1), except for execut-ing commandc.

Creating and Deleting Constraints. In non-atomic nor-mal execution mode, rule NEWCONS-N1 creates a new con-straint and triggers its first execution by resorting to⇒s.In atomic normal execution and in constraint mode, rulesNEWCONS-N2 and NEWCONS-C simply add the constraintto the scheduling queue. Similarly, rules DELCONS-N andDELCONS-C remove the constraint from the schedulingqueue and clean up its dependencies fromD.

Activating the Solver. Rules SOLVER-1 and SOLVER-2specify the behavior of the scheduler, which is started byrules ASGN-N2 and BEGINEND-N2. Rule SOLVER-1 de-fines the termination of the constraint solving phase: thisphase ends only when there are no more constraints to beevaluated (i.e.,S = ∅). Rule SOLVER-2 has an inductivedefinition. IfS is not empty, functionpick selects fromS anew active constraintcself , which is evaluated in constraintmode after removing fromD its old dependencies. The fi-nal state (σ′′) and dependencies (D′′′) are those obtained byapplying the scheduler on the storeσ′ obtained after the ex-ecution ofcself and on a new setS′′ of constraints.S′′ isderived fromS by adding any new constraints (S′) result-ing from the execution ofcself along with the constraintsdepending on reactive memory locations whose content hasbeen changed bycself . The definition ofS′′ guarantees thatconstraints can trigger other constraints (even themselves),even if each constraint execution is regarded as an atomicoperation and is never interrupted by the scheduler.

3. Convergence PropertiesIn this section, we discuss some general properties of theconstraint solving mechanism we adopt in DC, includingtermination, correctness, and running times. The computa-tion of one-way dataflow constraints (similarly to spread-sheet formulas, circuits, etc.) is traditionally described inthe literature in terms of a bipartite directed graph calleddataflow graph. In a dataflow graph, a node can model ei-ther an execution unit (e.g., gate [6], process [23], one-wayconstraint [38], or spreadsheet formula [28]) or an input/out-put port of one or more units (e.g., gate port, variable, orcell). There is an arc from a port to an execution unit if the

unit uses that port as a parameter, and from an executionunit to a port if the unit assigns a value to that port. Pathsin a dataflow graph, which is usually acyclic, describe howdata flows through the system, and the result of a compu-tation can be characterized algorithmically in terms of anappropriate traversal of the graph (e.g., in a topological or-der). This model is very effective in describing scenarioswhere data dependencies are either specified explicitly, orcan be derived statically from the program. However, in gen-eral the dataflow graph might be not known in advance ormay evolve over time in a manner that may be difficult tocharacterize. In all such cases, proving general propertiesof programs based on the evaluation of the dataflow graphmay not be easy. A more general approach, which we fol-low in our work, consists of modeling dataflow constraintsolving as an iterative process that aims at finding a com-mon fixpoint for the current set of constraints. In our con-text, a fixpoint is a store that satisfies simultaneously all therelations between reactive memory locations specified by theconstraints. This provides a unifying framework for solvingdataflow constraint systems with both acyclic and cyclic de-pendencies.

3.1 Independence of the Scheduling Order

In Section 2, we have assumed that the scheduling order ofconstraint executions is specified by a functionpick givenas a parameter of the solver. A natural question is whetherthere are any general properties of a set of constraints thatlet our solver terminate and converge to a common fix-point independently of the scheduling strategy used by func-tion pick. Using results from the theory of function itera-tions [15], we show that any arbitrary collection of inflation-ary one-way constraints has the desired property. This classof constraints includes, for instance, any program that canbedescribed in terms of an acyclic dataflow graph such as com-putational circuits [6], non-circular attribute grammars[29],and spreadsheets [28] (see Section 3.2). We remark, how-ever, that it is more general as it allows it to address prob-lems that would not be solvable without cyclic dependencies(an example is given in Section 3.3).

We first provide some preliminary definitions in accor-dance with the terminology used in [7]. We model constraintexecution as the application of functions on stores:

DEFINITION 1. We denote byfc : Σ → Σ the functioncomputed by a constraintc ∈ Cons, wherefc(σ) = σ′ if〈σ, c〉 ⇒c σ′. We say that storeσ ∈ Σ is a FIXPOINT for fcif fc(σ) = σ.

To simplify the discussion, throughout this section we as-sume that constraints only operate on reactive cells and fo-cus our attention on stores where all locations are reactive.The definition of inflationary functions assumes that a partialordering is defined on the set of storesΣ:

6 2011/4/13

DEFINITION 2 (INFLATIONARY FUNCTIONS). Let (Σ,�) beany partial ordering over the set of storesΣ and let f :Σ → Σ be a function onΣ. We say thatf is inflationaryif σ � f(σ) for all σ ∈ Σ.

Examples of partial orderings onΣ will be given in Sec-tion 3.2 and in Section 3.3. A relevant property of partialorderings in our context is thefinite chain property, basedon the notion ofsequence stabilization:

DEFINITION 3 (FINITE CHAIN PROPERTY). A partial order-ing (Σ,�) overΣ satisfies theFINITE CHAIN PROPERTYif ev-ery non-decreasing sequence of elementsσ0 � σ1 � σ2 �. . . fromΣ eventually stabilizes at some elementσ in Σ, i.e.,if there existsj ≥ 0 such thatσi = σ for all i ≥ j.

To describe the store modifications due to the execution ofthe solver, we use the notion ofiteration of functionsonstores. LetF = {f1, . . . , fn}, 〈a1, . . . , ak〉, andσ ∈ Σ be afinite set of functions onΣ, a sequence of indices in[1, n],and an initial store, respectively. An iteration of functions ofF starting atσ is a sequence of stores〈σ0, σ1, σ2, . . .〉 whereσ0 = σ andσi = fai

(σi−1) for i > 0. We say that functionfai

is activatedat stepi. Iterations of functions that lead toa fixed point are calledregular:

DEFINITION 4 (REGULAR FUNCTION ITERATION). A func-tion iteration 〈σ0, σ1, σ2, . . .〉 is REGULAR if it satisfies thefollowing property: for allf ∈ F and i ≥ 0, if σi is not afixpoint forf , thenf is activated at some stepj > i.

Using arguments from Chapter 7 of [7], it can be provedthat any regular iteration of inflationary functions starting atsome initial store stabilizes in a finite number of steps to acommon fixpoint:

LEMMA 1 (FIXPOINT). Let (Σ,�) be any partial orderingover Σ satisfying the finite chain property and letF be afinite set of inflationary functions onΣ. Then any regulariteration ofF starting atσ eventually stabilizes at a commonfixpointσ′ of the functions inF such thatσ � σ′.

We can now discuss convergence properties of our solver:

THEOREM 1. Let C = {c1, . . . , ch} be any set of con-straints, letF = {fc1, . . . , fch} be the functions computedby constraints inC, and let(Σ,�) be any partial orderingoverΣ satisfying the finite chain property. If functions inFare inflationary onΣ and{f ∈ F | f(σ) 6= σ} ⊆ S ⊆ F ,then〈ρ, σ,D, S〉 ⇒s 〈σ′, D′〉 andσ′ is a common fixpointof the functions inF such thatσ � σ′.

PROOF (SKETCH). Consider the sequence〈S0, S1, . . .〉 ofscheduling sets resulting from a recursive application of ruleSOLVER-2 terminated by rule SOLVER-1 (see Figure 4),with S0 = S. Let ci = pick(Si) the constraint executedat stepi, and letq = 〈σ0, σ1, σ2, . . .〉 be the function itera-tion such thatσ0 = σ andσi+1 = fci(σi). We prove that

q is regular. Notice thatS0 = S contains initially all func-tions for whichσ0 is not a fixpoint. Furthermore,Si+1 isobtained fromSi by removingci and adding at least all con-straints for whichσi+1 is not a fixpoint. It remains to showthat all constraints are activated at some step, i.e., they areeventually removed fromS. This can be proved by observ-ing that an inflationary functionfci either leaves the storeunchanged, and therefore|S| decreases by one, or producesa storeσi+1 = fci(σi) strictly larger thanσi, i.e.,σi � σi+1

andσi 6= σi+1. By the finite chain property, this cannot hap-pen indefinitely, soS eventually gets empty. Sinceq is regu-lar, the proof follows from Lemma 1. ✷

Assuming that functions in Lemma 1 and Theorem 1 are alsomonotonic, it is possible to prove that the solver always con-verges to theleastcommon fixpoint, yielding deterministicresults independently of the scheduling order. We recall thata functionf is monotonic ifσ � σ′ impliesf(σ) � f(σ′)for all σ, σ′ ∈ Σ.

3.2 Acyclic Systems of Constraints

In this section we show that, if a system of constraints isacyclic, then our solver always converges deterministicallyto the correct result, without the need for programmers toprove any stabilization properties of their constraints. Wenotice that this is the most common case in many appli-cations, and several efficient techniques can be adopted byconstraint solvers to automatically detect cycles introducedby programming errors [38]. In particular, we prove termi-nation of our solver on any system of constraints that modelsa computational circuit subject to incremental changes of itsinput. For the sake of simplicity, we focus on single-outputconstraints, to which any multi-output constraint can be re-duced.

A circuit is a directed acyclic graphG = (V,E) withvalues computed at the nodes, referred to asgates[6]. Eachnodeu is associated with an output functiongu that com-putes a valueval(u) = gu(val(v1), ..., val(vdu

)), wheredu is the indegree of nodeu and, for eachi ∈ [1, du], arc(vi, u) ∈ E. Arcs enteringu are ordered and, ifdu = 0,u is called an input gate (in this case,gu is constant). Thegate values and functions may have any data types. For sim-plicity, we will assume that there is only one gate in thegraph with outdegree0: the value computed at this gate isthe output of the circuit. The circuit value problem is to up-date the output of the circuit whenever the value of an inputgate is changed. This problem is equivalent to the scenariowhere the gate valuesval(u) are reactive memory cells, andeach non-input gateu is computed by a constraintcu thatassignsval(u) with gu(val(v1), ..., val(vdu

)). A circuit up-date operation changes the valueval(u) of any input gateuto a new constant. Any such update triggers the solver withS = {cu′ | (u, u′) ∈ E}. We now show that the solver up-dates correctly the circuit output.

7 2011/4/13

Letn be the number of circuit nodes, and letu1, u2, ..., un

be any topological ordering of the nodes, whereun is theoutput gate of the circuit. Letσ ∈ Σ be a store withdom(σ) = {val(ui) | i ∈ [1, n]}. We say that a valueval(ui) is incorrect inσ if σ is not a fixpoint forcui

. Letb(σ) =

∑n

i=1 2i ·χσ(ui), whereχσ(ui) = 1 if valueval(ui)

is incorrect inσ, and0 otherwise. We define a partial order-ing (Σ,�) as follows:

DEFINITION 5. For any two storesσ and σ′ in Σ, we saythatσ � σ′ if b(σ) ≥ b(σ′).

Relation� is clearly reflexive, antisymmetric, and transitive.Moreover, the constraintscu compute inflationary functionsonΣ. Let σ′ = fcu(σ) be obtained by evaluating contraintcu in storeσ. If val(u) is correct inσ (i.e.,χσ(u) = 0), thenσ′ = σ. Otherwise,χσ(u) = 1, χσ′(u) = 0, andχσ(u) =χσ′(u) for all gatesu that precedeu in the topologicalordering. This implies thatb(σ) ≥ b(fcu(σ)), and thusσ � fcu(σ). Sinceb(·) can assume only2n possible values,(Σ,�) also satisfies the finite chain property. After updatingan input gateu, all constraints for whichσ is no longer afixpoint are included in the setS = {cu′ | (u, u′) ∈ E}on which the solver is started. Hence, all the hypotheses ofTheorem 1 hold and the solver converges to storeσ′ ∈ Σthat is a common fixpoint of allfcu, i.e., a storeσ′ inwhich all gate values are correct. This implies that the circuitoutput is also correct. It is not difficult to prove that, if welet functionpick(S) return constraints in topological order,then all gates that may be affected by the input change areevaluated exactly once during the update.

3.3 Cyclic Systems of Constraints: an Example

Differently from previous approaches to solving one-waydataflow constraints [6, 17, 26, 27], which were targeted toacyclic dependencies, our abstract machine can handle themost general case of cyclic constraints embedded within animperative program. This opens up the possibility to addressproblems that would not be solvable using acyclic dataflowgraphs, backed up with a formal machinery to help designersprove their convergence properties (Section 3.1). We exem-plify this concept by considering the well known problemof maintaining distances in a graph subject to local changesto its nodes or arcs. In the remainder of this section weshow how to specify an incremental variant of the classicalBellman-Ford’s single-source shortest path algorithm [8]interms of a (possibly cyclic) system of one-way constraints.Compared to purely imperative specifications [18], the for-mulation of the incremental algorithm in our mixed impera-tive/dataflow framework is surprisingly simple and requiresjust a few lines of code. By suitably defining a partial orderon Σ and an appropriatepick function, we show that oursolver finds a correct solution within the best known worst-case time bounds for the problem.

insert(u, v, w):E := E ∪ {(u, v)}w(u, v) := w

newcons( if d[u] +w(u, v) < d[v] then d[v] := d[u] + w(u, v)︸ ︷︷ ︸

cuv

)

decrease(u, v, δ):w(u, v) := w(u, v) − δ

Figure 5. Incremental shortest path updates in a mixed im-perative/dataflow style.

Incremental Shortest Paths. Let G = (V,E,w) be a di-rected graph with real edge weightsw(u, v), and lets be asource node inV . We consider the incremental shortest pathproblem that consists of updating the distancesd[u] of allnodesu ∈ V from the sources after inserting any new edgein the graph, or decreasing the weight of any existing edge.For the sake of simplicity we assume that no negative-weightcycles are introduced by the update and that, if a nodeu isunreachable from the source, its distanced[u] is+∞.

Update Algorithm. The incremental shortest path problemcan be solved in our framework as follows. We keep edgeweights and distances in reactive memory. Assuming to startfrom a graph with no edges, we initialized[s] := 0 andd[u] := +∞ for all u 6= s. The pseudocode of updateoperations that insert a new edge and decrease the weightof an existing edge by a positive amountδ are shown inFigure 5. Operationinsert(u, v, w) adds edge(u, v) to thegraph with weightw and creates a new constraintcuv for theedge:cuv simply relaxesthe edge if Bellman’s inequalityd[u] + w(u, v) ≥ d[v] is violated [8]. The constraint isimmediately executed after creation (see rule NEWCONS-N1 in Figure 4) and the three pairs(d[u], cuv), (d[v], cuv),and(w(u, v), cuv) are added to the set of dependenciesD.Any later change tod[u], d[v] orw(u, v), which may violatethe inequalityd[u] + w(u, v) ≥ d[v], will cause the re-execution ofcuv. Decreasing the weight of an existing edge(u, v) by any positive constantδ with decrease(u, v, δ) canbe done by just updatingw(u, v). In view of rule ASGN-N2of Figure 4, the system reacts to the change and re-executesautomaticallycuv and any other affected constraints.

Using the machinery developed in Section 3.1 and suit-ably defining a partial order onΣ and an appropriatepickfunction, we now show that our solver finds a correct so-lution within the best known worst-case time bounds for theproblem, i.e., it updates distances correctly after anyinsert

or decrease operation.

Termination and Correctness. For the sake of conve-nience, we denote bydσ[u] = σ(d[u]) and bywσ(u, v) =σ(w(u, v)) the distance of nodeu and the weight of edge(u, v) in storeσ, respectively. For any two graphsG1 andG2

on the same vertex set, we denote byG1⊎G2 the multigraphwith vertex setV and edge setE1 ⊎ E1, where⊎ indicatesthe join of multisets: the same edge may thus appear twice

8 2011/4/13

in E1⊎E2, possibly with different weights. Let us focus ourattention on a restricted set of stores, which encompasses allpossible configurations of the reactive memory during theexecution of the solver triggered by an update:

DEFINITION 6. Let Gold = (V,Eold, wold) and G =(V,E,w) be the graph before and after inserting a newedge or decreasing the weight of an edge, respectively. Wedenote byΣsp ⊆ Σ the set of all functionsσ : Loc → V alsuch that:

• dom(σ) = {d[u] |u ∈ V } ∪ {w(u, v) | (u, v) ∈ E} ⊆Loc;

• for eachu ∈ V , dσ[u] is the weight of a simple path (i.e.,with no repeated nodes) froms to u in Gold ⊎G;

• for each(u, v) ∈ E, wσ(u, v) is fixed as the weight ofedge(u, v) in G.

Notice that, as simple paths in a graph are finite, the numberof possible values eachdσ[u] can attain is finite, and there-foreΣsp is a finite set. We define a partial ordering(Σsp,�)onΣsp as follows:

DEFINITION 7. Let σ andσ′ be any two stores inΣsp. Wesay thatσ � σ′ if dσ[u] ≥ dσ′ [u] for all u ∈ V .

Relation� is reflexive, antisymmetric, and transitive. More-over, sinceΣsp is finite, (Σsp,�) satisfies the finite chainproperty. We now prove that constraintscuv compute infla-tionary functions onΣsp.

LEMMA 2. Functionsfcuvcomputed by constraintscuv of

Figure 5 are inflationary with respect to the partial ordering(Σsp,�) of Definition 7.

PROOF. Let (u, v) be any edge inE and letσ be any storein Σsp. If σ = fcuv

(σ) then clearlyσ � fcuv(σ). Con-

sider the caseσ 6= fcuv(σ). Notice thatσ and fcuv

(σ)can only differ in the value of memory locationd[v]. Sincethere are no negative-weight cycles anddσ[u] is the weightof a simple path inGold ⊎ G, then so isdfcuv

(σ)[v] =dσ[u] + wσ(u, v). Furthermore, ascuv never increases dis-tances, thendfcuv

(σ)[v] ≤ dσ[v] . Therefore,σ � fcuv(σ).

This shows thatfcuvis inflationary. ✷

It is not difficult to see that, if all distances are correct beforean update, then the solver is started on a storeσ ∈ Σsp. Asall the hypotheses of Theorem 1 hold, the solver convergesto storeσ′ ∈ Σsp that is a common fixpoint of allfcuv

.Therefore: (1)dσ′ [u]’s are weights of simple paths inG,and (2) they satisfy all Bellman’s inequalities. It is wellknown [5] that node labels satisfying both properties (1) and(2) are in fact the correct distances in the graph.

Running Time. If we let functionpick(S) return the con-straintcuv ∈ S with the largest variation ofd[u] due to theupdate, then we can adapt results from [18] and show thateachcuv is executed by the solver at most once per update.If pick uses a standard priority queue withO(log n) time

typedef void (*cons_t)(void*);

int newcons(cons_t cons, void* param);

void delcons(int cons_id);

void* rmalloc(size_t size);

void rfree(void* ptr);

void begin_at();

void end_at();

void arm_final(int cons_id, cons_t final);

void set_comp(int (*comp)(void*, void*));

Figure 6. Main functions of the DC language extension.

per operation, then the solver updates distances incremen-tally in O(m log n) worst-case time, even in the presence ofnegative edge weights (but no negative cycles). This can bereduced toO(m + n logn) by just creating one constraintper node, rather that one constraint per edge, and letting itrelax all outgoing edges. This matches the best known al-gorithmic bounds for the problem [18]. We remark that, re-computing distances from scratch with the best known staticalgorithm would requireO(mn) time in the worst case ifthere are negative edge weights, andO(m + n logn) timeotherwise. In Section 7.3 we will analyze experimentally theperformances of our constraint-based approach showing thatin practice it can be orders of magnitude faster than recom-puting from scratch, even when all weights are non-negative.

4. Embodiment into C/C++In this section we show how to apply the concepts devel-oped in Section 2 to the C and C++ languages, deriving anextension that we call DC. DC has exactly the same syntaxas C/C++, but operations that read or modify objects havea different semantics. All other primitives, including creat-ing/deleting constraints, allocating/deallocating reactive ob-jects, and opening/closing atomic blocks, are provided asruntime library functions1 (see Figure 6).

Reactive Memory Allocation. Similarly to other automaticchange propagation approaches (e.g., [4, 34]), in DC all ob-jects allocated statically or dynamically are non-reactive bydefault. Reactive locations are allocated dynamically usinglibrary functionsrmalloc andrfree, which work just likemalloc andfree, but on a separate heap.

Opening and Closing Atomic Blocks. Atomic blocks aresupported in DC using two library functionsbegin at andend at. Calling begin at opens an atomic block, whichshould be closed with a matching call toend at. Nestedatomic blocks are allowed, and are handled using a counterof nesting levels so that the solver is only resumed at the endof the outer block, processing any pending constraints thatneed to be first executed or brought up to date as a result ofthe block’s execution.

1 A detailed documentation of the DC application programmingin-terface, including stricter library naming conventions and several ad-ditional features not covered in this paper, is available atthe URL:http://www.dis.uniroma1.it/~ demetres/dc/

9 2011/4/13

Creating and Deleting Constraints. For the sake of sim-plicity, in Section 2 constraints have been modeled as ordi-nary commands. DC takes a more flexible approach: con-straints are specified as closures formed by a function thatcarries out the computation and a user-defined parame-ter to be passed to the function. Different constraints maytherefore share the same function code, but have differ-ent user-defined parameters. New constraint instances canbe created by callingnewcons, which takes as parametersa pointercons to a function and a user-defined parame-ter param. When invoked in non-atomic normal executionmode,newcons executes immediately functioncons withparameterparam, and logs all dependencies between thecreated constraint and the reactive locations read during theexecution. If a constraint is created inside an atomic block(or inside another constraint), its first evaluation is deferreduntil the end of the execution of the current block (or con-straint). All subsequent re-executions of the constraint trig-gered by modifications of the reactive cells it depends onwill be performed with the same value ofparam specified atthe creation time.newcons returns a unique id for the cre-ated constraint, which can be passed todelcons to disposeof it.

Reading and Modifying Objects. Reading and modifyingobjects in reactive memory can be done in DC by evaluatingordinary C/C++ expressions. We remark that no syntax ex-tensions or explicit macro/function invocations are required.

Customizing the Scheduler. Differently from other ap-proaches [34], DC allows programmers to customize theexecution order of scheduled constraints. While the defaultpick function of DC (which gives higher priority to least re-cently executed constraints) works just fine in practice foralarge class of problems (see Section 7), the ability to replaceit can play an important role for some specific problems, aswe have seen in the incremental shortest paths example ofSection 3.3. DC provides a functionset comp that installsa user-defined comparator to determine the relative priorityof two constraints. The comparator receives as arguments theuser-defined parameters associated with the constraints tobecompared.

Final Handlers. An additional feature of DC, built on topof the core constraint handling mechanisms described inSection 4, is the ability to perform some finalization oper-ations only when the results of constraint evaluations arestable, i.e., when the solver has found a common fixpoint.For instance, a constraint computing the attribute of a wid-get in a graphic user interface may also update the screen bycalling drawing primitives of the GUI toolkit: if a redrawingoccurs at each constraint execution, this may cause unnec-essary screen updates and flickering effects. Another usageexample of this feature will be given in Section 5.3.

DC allows users to specify portions of code for a con-straint to be executed as final actions just before resuming

struct robject {void* operator new(size_t size) { return rmalloc(size); }void operator delete(void* ptr) { rfree(ptr); }

};

static void con_h(void*), fin_h(void*);class rcons {

int id;public:

virtual void cons() = 0;

virtual void final() {}rcons() { id = -1; }

~rcons() { disable(); }void enable() { if (id == -1) id = newcons(con_h, this); }void disable() { if (id != -1) { delcons(id); id = -1; } }

void arm_final() { if (id != -1) arm_final(id, fin_h); }void unarm_final() { if (id != -1) arm_final(id, NULL); }

};

void con_h(void* p) { ((rcons*)p)->cons(); }void fin_h(void* p) { ((rcons*)p)->final(); }

Figure 7. C++ wrapping of DC primitives.

the underlying imperative program interrupted by the solveractivation. This can be done by calling functionarm final

during constraint solving: the operation schedules afinalhandlerto be executed at the end of the current solving ses-sion. The function takes as parameters a constraint id and apointer to a final handler, orNULL to cancel a previous re-quest. A final handler receives the same parameter as theconstraint it is associated to, but no dependencies from reac-tive locations are logged during its execution. All final han-dlers are executed in normal execution mode as a whole in-side an atomic block.

C++ Wrapping of DC Primitives. The examples in the re-mainder of this paper are based on a simple C++ wrappingof the DC primitives, shown in Figure 7. We abstract theconcepts of reactive object and constraint using two classes:robject andrcons. The former is a base class for objectsstored in reactive memory. This is achieved by overloadingthe new anddelete operators in terms of the correspond-ing DC primitivesrmalloc andrfree, so that all membervariables of the object are reactive. Classrcons is a virtualbase class for objects representing dataflow constraints. Theclass provides a pure virtual function calledcons, to be de-fined in subclasses, which provides the user code for a con-straint. An additional emptyfinal function can be option-ally overridden in subclasses to define the finalization codefor a constraint. The class also provides functionsenable

anddisable to activate/deactivate the constraint associatedwith the object, and functionsarm final andunarm final

to schedule/unschedule the execution of final handlers.

5. Applications and Programming ExamplesIn this section, we discuss how DC can improve C/C++programmability in three relevant application scenarios.Tothe best of our knowledge, these applications have not beenexplored before in the context of dataflow programming. Allthe code we show is real.

10 2011/4/13

template<typename T> struct node : robject, rcons {enum op_t { SUM, PROD };T val;

op_t op;node *left, *right;

node(T v): val(v), left(NULL), right(NULL) { enable(); }node(op_t o): op(o), left(NULL), right(NULL) { enable(); }

void cons() {if (left == NULL || right == NULL) return;switch (op) {

case SUM: val = left->val + right->val; break;case PROD: val = left->val * right->val; break;

}}

};

Figure 8. Incremental evaluation of expression trees.

5.1 Incremental Computation

In many applications, the input data is subject to continuousupdates that need to be processed efficiently. For instance,ina networking scenario, routers must react quickly to link fail-ures by updating routing tables in order to minimize commu-nication delays. When the input is subject to small changes,a program may fix incrementally only the portion of the out-put affected by the update, without having to recompute theentire solution from scratch. For many problems, efficientadhocalgorithms are known that can update the output asymp-totically faster that recomputing from scratch, delivering inpractice speedups of several orders of magnitude [17, 19].Such dynamic algorithms, however, are typically difficult todesign and implement, even for problems that are easy tobe solved from-scratch. A language-centric approach, whichwas extensively explored in both functional and imperativeprogramming languages, consists of automatically turningaconventional static algorithm into an incremental one, by se-lectively recomputing the portions of a computation affectedby an update of the input. This powerful technique, knownas self-adjusting computation [4], provides a principled wayof deriving efficient incremental code for several problems.We now show that dataflow constraints can provide an effec-tive alternative for specifying incremental programs. Laterin this section we discuss differences and similarities of thetwo approaches.

Example. To put our approach into the perspective of pre-vious work on self-adjusting computation, we revisit theproblem of incremental re-evaluation of binary expressiontrees discussed in [25]. This problem is a special case ofthe circuit evaluation described in Section 3.2: input val-ues are stored at the leaves and the value of each internalnode is determined by applying a binary operator (e.g., sumor product) on the values of its children. The final result ofthe evaluation is stored at the root. We start from the con-ventional node structure that a programmer would use for abinary expression tree, containing the type of the operationcomputed at the node (only relevant for internal nodes), thenode’s value, and the pointers to the subtrees. Our DC-basedsolution (see Figure 8) simply extends the node declarationby letting it inherit from classesrobject andrcons, and by

providing the code of a constraint that computes the value ofthe node in terms of the values stored at its children. Ev-erything else is exactly what the programmer would havedone anyway to build the input data structure. An expressiontree can be constructed by just creating nodes and connect-ing them in the usual way:

node<int> *root = new node<int>(node<int>::SUM);

root->left = new node<int>(10);

root->right = new node<int>(node<int>::PROD);

root->right->left = new node<int>(2);

root->right->right = new node<int>(6);

The example above creates the tree shown in Figure 9 (left).Since all fields of the node are reactive and each nodeis equipped with a constraint that computes its value, atany time during the tree construction,root->value con-tains the correct result of the expression evaluation. Weremark that this value not only is given for free withoutthe need to compute it explicitly by traversing the tree,but is also updated automatically after any change of thetree. For instance, changing the value of the rightmost leafwith root->right->right->val = 3 triggers the propaga-tion chain shown in Figure 9 (right). Other possible updatesthat would be automatically propagated include changingthe operation type of a node or even adding/removing entiresubtrees. Notice that a single change to a node may triggerthe re-execution of the constraints attached to all its ances-tors, so the total worst-case time per update isO(h), whereh is the height of the tree. For a balanced expression tree,this is exponentially faster than recomputing from scratch.If a batch of changes are to be performed and only the finalvalue of the tree is of interest, performance can be improvedby grouping updates withbegin at() andend at() so thatthe re-execution of constraints is deferred until the end ofthe batch, e.g.:

begin_at(); // put the solver to sleep

root->op = node<int>::SUM; // change node operation type

delete root->right->left // delete leaf

... // etc...

end_at(); // wake up the solver

Discussion. DC and imperative self-adjusting computa-tion languages such as CEAL [4] share the basic idea ofchange propagation, and reactive memory is very similar toCEAL’s modifiables. However, the two approaches differ ina number of important aspects. In CEAL, the solution is ini-tially computed by a core component and later updated by amutator, which performs changes to the input. In DC there isno explicit distinction between an initial run and a sequenceof updates, and in particular there is no static algorithm thatis automatically dynamized. Instead, programmers explicitlybreak down the solution to a complex problem into a col-lection of reactive code fragments that locally update smallportions of the program state as a function of other portions.This implies a paradigm shift that may be less straightfor-ward for the average programmer than writing static algo-rithms, but it can make it easier to exploit specific properties

11 2011/4/13

node: robject, rconsval

op

22

left

+

right

constra

int

val

op

10

NULL

left

n/a

NULL

right

constra

int

val

op

12

left

*

right

constra

int

val

op

2

NULL

left

n/a

NULL

right

constra

int

val

op

6

NULL

left

n/a

NULL

right

constra

int

cons dependson reactive cell

cons writesreactive cell

root

val

op

16

left

+

right

constraint

val

op

10

NULL

left

n/a

NULL

right

constra

int

val

op

6

left

*

right

constraint

val

op

2

NULL

left

n/a

NULL

right

constra

int

val

op

3

NULL

left

n/a

NULL

right

constra

int

root

root->right->right->val = 3;

tree update operation:

Figure 9. Reactive expression tree (left) and change propagation chain after a leaf value update (right).

of the problem at hand, which in some cases can be crucialfor coding algorithms provably faster than recomputing fromscratch.

Traceable data types [4] have been recently introduced toextend the class of static algorithms that can be handled ef-ficiently by self-adjusting computation: in a traceable datatype, dependencies are tracked at the level of data structureoperations, rather than individual memory locations, com-bining in a hybrid approach the benefits of automatic changepropagation and those ofad hocdynamic data structures.This can yield large asymptotic gains in dynamizing staticalgorithms that use basic abstract data types, such as dictio-naries or priority queues. However, it is not clear that everyconventional static algorithm can be effectively dynamizedin this way: for some complex problems, it may be neces-sary to implement anad hoctraceable data structure that per-forms all the incremental updates, thus missing the advan-tages of automatic change propagation and thwarting the au-tomatic incrementalization nature of self-adjusting computa-tion. In contrast, dataflow constraints can explicitly dealwithchanges to the state of the program (when this is necessary toobtain asymptotic benefits), incorporating change-awarenessdirectly within the code controlled by the change propaga-tion algorithm without requiring traceable data structures.

5.2 Implementing the Observer Design Pattern

As a second example, we show how the reactive nature ofour framework can be naturally exploited to implement theobserver software design pattern. A common issue arisingfrom partitioning a system into a collection of cooperatingsoftware modules is the need to maintain consistency be-tween related objects. In general, a tight coupling of theinvolved software components is not desirable, as it wouldreduce their reusability. For example, graphical user inter-face toolkits almost invariably separate presentational as-pects from the underlying application data management, al-lowing data processing and data presentation modules tobe reused independently. Theobserver software design pat-

tern [14] answers the above concerns by defining one-to-many dependencies between objects so that when one ob-ject (thesubject) changes state, all its dependents (theob-servers) are automatically notified. A key aspect is that sub-jects send out notifications of their change of state, withouthaving to know who their observers are, while any numberof observers can be subscribed to receive these notifications(subjects and observers are therefore not tightly coupled). Awidely deployed embodiment of this pattern is provided bytheQt application development framework [22].

Qt is based on a signal-slot communication mechanism:a signal is emitted when a particular event occurs, whereas aslot is a function that is called in response to a particular sig-nal. An object acting as a subject emits signals in responseto changes of its state by explicitly calling a special mem-ber function designated as a signal. Observers and subjectscan be explicitly connected so that any signal emitted by asubject triggers the invocation of one or more observer slots.Programmers can connect as many signals as they want to asingle slot, and a signal can be connected to as many slotsas they need. Since the connection is set up externally af-ter creating the objects, this approach allows objects to beunaware of the existence of each other, enhancing informa-tion encapsulation and reuse of software components. Sub-jects and observers can be created inQt as instances of theQObject base class.Qt’s signal-slot infrastructure hingesupon an extension of the C++ language with three new key-words:signal andslot, to designate functions as signalsor slots, andemit, to generate signals.

A Minimal Example: Qt vs. DC. To illustrate the conceptsdiscussed above and compareQt and DC as tools for imple-menting the observer pattern, we consider a minimal exam-ple excerpted from theQt 4.6 reference documentation. Thegoal is to set up a program in which two counter variablesa

andb are connected together so that the value ofb is auto-matically kept consistent with the value ofa. The examplestarts with the simple declaration shown in Figure 10(a) (allexcept the framed box), which encapsulates the counter into

12 2011/4/13

an object with member functionsvalue/setValue for ac-cessing/modifying it. Figure 10(b) shows how theCounterclass can be modified inQt so that counter modificationscan be automatically propagated to other objects as pre-scribed by the observer pattern. First of all, the class inheritsfrom Qt’s QObject base class and starts with theQ OBJECT

macro. FunctionsetValue is declared as a slot and it isaugmented by calling explicitly thevalueChanged signalwith theemit keyword every time an actual change occurs.SinceQt Counter objects contain both signal and slot func-tions they can act both as subjects and as observers. The fol-lowing code snippet shows how two counters can be createdand connected so that each change to the former triggers achange of the latter:

Counter *a = new Counter, *b = new Counter;

QObject::connect(a, SIGNAL(valueChanged(int)),

b, SLOT(setValue(int)));

a->setValue(12); // a->value() == 12, b->value() == 12

b->setValue(48); // a->value() == 12, b->value() == 48

TheQObject::connect call installs a connection betweencountersa andb: every timeemit valueChanged(value)

is issued bya with a given actual parameter,setValue(intvalue) is automatically invoked onb with the same param-eter. Therefore, the calla->setValue(12) has as a side-effect that the value ofb is also set to12. Conversely, thecall b->setValue(48) entails no change ofa as no con-nection exists fromb to a.

The same result can be achieved in DC by just lettingtheCounter class of Figure 10(a) inherit from therobjectbase class of Figure 7. As a result, them valuemember vari-able is stored in reactive memory. The prescribed connectionbetween reactive counters can be enforced with a one-waydataflow constraint that simply assigns the value ofb equalto the value ofa:

Counter *a = new Counter, *b = new Counter;

struct C : rcons {

Counter *a, *b;

C(Counter *a, Counter *b) : a(a), b(b) { enable(); }

void cons() { b->setValue(a->value()); }

} c(a,b);

a->setValue(12); // a->value() == 12, b->value() == 12

b->setValue(48); // a->value() == 12, b->value() == 48

We notice that the role of theQObject::connect of theQt implementation is now played by a dataflow constraint,yielding exactly the same program behavior.

Discussion. The example above shows that DC’s run-time system handles automatically a number of aspects thatwould have to be set up explicitly by the programmers usingQt’s mechanism:

• there is no need to define slots and signals, relievingprogrammers from the burden of extending the definition

class Counter : public robject {

public:

Counter() { m_value = 0; }

int value() const { return m_value; }

void setValue(int value) { m_value = value; }

private:

int m_value;

};

(a) A counter class and its DC observer pattern version (framed box).

class Counter : public QObject {

Q_OBJECT

public:

Counter() { m_value = 0; }

int value() const { return m_value; }

public slots:

void setValue(int value);

signals:

void valueChanged(int newValue);

private:

int m_value;

};

void Counter::setValue(int value) {

if (value != m_value) {

m_value = value;

emit valueChanged(value);

}

}(b) Qt observer pattern version of the counter class.

Figure 10. Observer pattern example excerpted from theQt

4.6 reference documentation: DC vs.Qt implementation.

of subject and observer classes with extra machinery (seeFigure 10);

• only actual changes of an object’s state trigger propaga-tion events, so programmers do not have to make explicitchecks such as inCounter::setValue’s definition toprevent infinite looping in the case of cyclic connections(see Figure 10(b));

• DC does not require extensions of the language, and thusthe code does not have to be preprocessed before beingcompiled.

We sketch below further points that make dataflow con-straints a flexible framework for supporting some aspectsof component programming, putting it into the perspectiveof mainstream embodiments of the observer pattern such asQt:

• in DC, only subjects need to be reactive, while observerscan be of any C++ class, even of third-party libraries dis-tributed in binary code form. InQt, third-party observersmust be wrapped using classes equipped with slots thatact as stubs;

• relations betweenQt objects are specified by creating ex-plicitly one-to-one signal-slot connections one at a time;a single DC constraint can enforce simultaneously anyarbitrary set of many-to-many relations. Furthermore,as the input variables of a dataflow constraint are de-

13 2011/4/13

0 template<class T, class N> class snode : public rcons {1 map<N**, snode<T,N>*> *m;2 N *head, **tail;

3 snode *next;4 int refc;

5 public:6 snode(N *h, N **t, map<N**, snode<T,N>*> *m) :

7 m(m), head(h), tail(t), next(NULL), refc(0) {8 (*m)[tail] = this;9 enable();

10 }11 ~snode() {

12 m->erase(tail);13 if (next != NULL && --next->refc == 0) delete next;14 }

15 void cons() {16 snode<T,N>* cur_next;

1718 if (*tail != NULL) {

19 typename map<N**, snode<T,N>*>::iterator it =20 m->find( &(*tail)->next );21 if (it != m->end())

22 cur_next = it->second;23 else cur_next = new snode<T,N>(*tail,

24 &(*tail)->next, m);25 } else cur_next = NULL;26

27 if (next != cur_next) {28 if (next != NULL && --next->refc == 0)

29 next->arm_final();30 if (cur_next != NULL && cur_next->refc++ == 0)

31 cur_next->unarm_final();32 next = cur_next;33 }

34 if (head != NULL) T::watch(head);35 }

36 void final() { delete this; }37 };

38 template<class T, class N> class watcher {39 snode<T,N> *gen;

40 map<N**, snode<T,N>*> m;41 public:

42 watcher(N** h) { gen = new snode<T,N>(NULL, h, &m); }43 ~watcher() { delete gen; }44 };

Figure 11. Data structure checking and repair:list watcher.

tected automatically, relations may change dynamicallydepending on the state of some objects;

• Qt signal-slot connections let subjects communicate val-ues to their observers; DC constraints can compute thevalues received by the observers as an arbitrary functionof the state of multiple subjects, encapsulating complexupdate semantics;

• in Qt, an object’s state change notification can be de-ferred by emitting a signal until after a series of statechanges has been made, thereby avoiding needless inter-mediate updates; DC programmers can control the gran-ularity of change propagations by temporarily disablingconstraints and/or by usingbegin at/end at primitives.

5.3 Data Structure Checking and Repair

Long-living applications inevitably experience various formsof damage, often due to bugs in the program, which couldlead to system crashes or wrong computational results.The ability of a program to perform automatic consistencychecks and self-healing operations can greatly improve re-

liability in software development. One of the most commoncauses of faults is connected with different kinds of datastructure corruptions, which can be mitigated using datastructure repair techniques [21].

In this section, we show how dataflow constraints can beused to check and repair reactive data structures. We ex-emplify this concept by considering the simple problem ofrepairing a corrupt doubly-linked list [30]. We first showhow to build a genericlist watcher, which is able to de-tect any changes to a list and perform actions when modi-fications occur. This provides an advanced example of DCprogramming, where constraints are created and destroyedby other constraints. Differently from the expression treesof Section 5.1, where constraints are attributes of nodes, themain challenge here is how to let the watched list be com-pletely unaware of the watcher, while still maintaining auto-matically a constraint for each node. The complete code ofthe watcher is shown in Figure 11. The only assumpion ourwatcher makes on list nodes to be monitored (of generic typeN) is that they are reactive and contain anext field pointing tothe successor. The main idea is to maintain ashadow listofconstraints that mirrors the watched list (Figure 12). Shadownodes aresnode objects containing pointers to the monitorednodes (head) and to their next fields (tail). A special gener-ator shadow node (gen) is associated to the reactive variable(list) holding the pointer to the first node of the input list.A lookup table (m) maintains a mapping from list nodes tothe corresponding shadow nodes. The heart of the watcher isthe constraint associated with shadow nodes (lines 15–35).It first checks if the successor of the monitored node, if any,is already mapped to a shadow node (lines 18–21). If not, itcreates a new shadow node (line 23). Lines 27–33 handle thecase where the successor of the shadow node has changedand itsnext field has to be updated. Line 34 calls a user-definedwatch function (provided by template parameterT),which performs any desired checks and repairs for an inputlist node. To dispose of shadow nodes when the correspond-ing nodes are disconnected from the list, we use a simplereference counting technique, deferring to a final handler thetask of deallocating dead shadow nodes (line 36).

The following code snippet shows how to create a simplerepairer for a doubly-linked list based on the watcher ofFigure 11:

struct node : robject { int val; node *next, *prev; };

struct myrepairer {

static void watch(node* x) {

// check

if (x->next != NULL && x != x->next->prev)

// repair

x->next->prev = x;

}

};

// create reactive list head and repairer

node** list = ...;

watcher<myrepairer,node> rep(list);

14 2011/4/13

node: ro

bje

ct

list

val 7

next

prev

val 11

next

prev

val 4

next

prevNULL

NULL

head tail

next

m refc1

head tail

next

m refc0

head tail

next

m refc1

head tail

next

m refc1

NULL

NULL

watched list

m m m

gen

m

map

watcher

snode: rc

ons

m

map lookup

Figure 12. A reactive doubly-linked list, monitored by awatcher.

ap

plicati

on

cod

e memory access logger

constraintscheduler

binary image analyzer

constraint solver

SIGSEGV

cons dispatcher

code

cache

shadow

mem

ory

newcons/delcons

reactive memory manager

DC runtime libraryrmalloc/

rfree

S

reactive mem.read/write

process a

dd

ress s

pace

D

W

ELF

binary code patcher

patc

hed c

ode

reactive memory allocator

trampoline insert.

constraint factory C

Figure 13. DC’s software architecture.

// manipulate the list

...

The repairer objectrep checks if the invariant propertyx== x->next->prev is satisfied for all nodes in the list, andrecovers it to a consistent state if any violation is detectedduring the execution of the program. We notice that severaldifferent watchers may be created to monitor the same list.

6. ImplementationIn this section we discuss how DC can be implemented viaa combination of runtime libraries, hardware/operating sys-tem support, and dynamic code patching, without requiringany source code preprocessing. The overall architecture ofour DC implementation, which was developed on a LinuxIA-32 platform, is shown in Figure 13. At a very high level,the DC runtime library is stratified into two modules: 1) areactive memory manager, which defines thermalloc andrfree primitives and provides support for tracing accessesto reactive memory locations; 2) aconstraint solver, whichschedules and dispatches the execution of constraints, keep-ing track of dependencies between reactive memory loca-tions and constraints. We start our description by discussinghow to support reactive memory, which is the backbone ofthe whole architecture.

6.1 Reactive Memory

Taking inspiration from transactional memories [1], we im-plemented reactive memory using off-the-shelf memory pro-

tection hardware. Our key technique uses access violations(AV) combined with dynamic binary code patching as abasic mechanism to trace read/write operations to reactivememory locations.

Access Violations and Dynamic Code Patching. Reactivememory is kept in aprotectedregion of the address space sothat any read/write access to a reactive object raises an AV.Since access violation handling is very inefficient, we use itjust to detect incrementally instructions that access reactivememory. When an instructionx first tries to access a reac-tive location, a segmentation fault with offending instructionx is raised. In the SIGSEGV handler, we patch the tracetcontainingx by overwriting its initial 5 bytes with a jump toa dynamically recompiled tracet′ derived fromt, which isplaced in a code cache. In tracet′, x is instrumented with ad-ditional inline code that accesses reactive locations withoutgenerating AVs, and possibly activates the constraint solver.Tracet′ ends with a jump that leads back to the end oft sothat control flow can continue normally in the original code.Sincet′ may contain several memory access instructions, itis re-generated every time a new instruction that accessesreactive memory is discovered. To identify traces in thecode, we analyze statically the binary code when it is loaded

Text

Data

BSS

Stack

mmaps

Shadowmemory

Reactivememory

info

Heap

Reactivememory

312

312

302

+

322

kern

el space

brk

0

off

set

30

fixed 2

and we construct a lookup table that maps theaddress of each memory access instruction tothe trace containing it. To handle the caseswhere a trace in a functionf is smaller than 5bytes and thus cannot be patched, we overwritethe beginning off with a jump to a new versionf ′ of f where traces are padded with trailingnop instructions so that the smallest trace is atleast 5-bytes long.

Shadow Memory and Address Redirecting.To avoid expensive un-protect and re-protectpage operations at each access to reactive mem-ory, we mirror reactive memory pages with un-protectedshadow pagesthat contain the actualdata. The shadow memory region is kept un-der the control of our reactive memory alloca-tor, which maps it onto physical frames withthemmap system call. Any access to a reactiveobject is transparently redirected to the corre-sponding object in the shadow memory. As aresult, memory locations at addresses withinthe reactive memory region are never actuallyread or written by the program. To avoid wast-ing memory without actually accessing it, re-active memory can be placed within the Kernelspace, located in the upper 1GB of the addressspace on 32-bit Linux machines with the clas-sical 3/1 virtual address split. Kernel space is flagged in thepage tables as exclusive to privileged code (ring 2 or lower),thus an AV is triggered if a user-mode instruction tries totouch it. More recent 64-bit platforms offer even more flexi-

15 2011/4/13

bility to accomodate reactive memory in protected regionsof the address space. We let the reactive memory regionstart at address230 + 231 = 0xC000000 and grow upwardas more space is needed (see the figure on the right). Theshadow memory region starts at address231 = 0x8000000and grows upward, eventually hitting the memory mappingsegment used by Linux to keep dynamic libraries, anony-mous mappings, etc. Any reactive object at addressx is mir-rored by a shadow object at addressx− δ, whereδ = 230 =0x4000000 is a fixed offset. This makes address redirectingvery efficient.

6.2 Constraint Solver

Our implementation aggregates reactive locations in 4-bytewordsaligned at 32 bit boundaries. The solver is activatedevery time such a word is read in constraint execution mode,or its value is modified by a write operation. The maininvolved units are (see Figure 13):

1. A dispatcher that executes constraints, maintaining aglobal timestampthat grows by one at each constraintexecution. For each constraint, we keep the timestamp ofits latest execution.

2. A memory access loggerthat maintains the set of depen-denciesD and a listW of all reactive memory wordswritten by the execution of the current constraintcself ,along with their initial values before the execution. Toavoid logging information about the same word multi-ple times during the execution of a constraint, the loggerstamps each word with the time of the latest constraintexecution that accessed it. Information is logged only ifthe accessed word has a timestamp older than the currentglobal timestamp, which can only happen once for anyconstraint execution. To representD, the logger keeps foreach wordv the address of the head node of a linked listcontaining the id’s of constraints depending uponv.

3. A constraint schedulerthat maintains the set of sched-uled constraintsS. By defaultS is a priority queue, wherethe priority of a constraint is given by the timestamp ofits latest execution: the scheduler repeatedly picks andlets the dispatcher execute the constraint with the highestpriority, until S gets empty. Upon completion of a con-straint’s execution, words are scanned and removed fromW : for eachv ∈ W whose value has changed since thebeginning of the execution, the constraint id’s in the listof nodes associated withv are added toS, if not alreadythere.

Nodes of the linked lists that representD and data struc-turesS andW are kept in contiguous chunks allocated withmalloc. To support direct lookup, timestamps and depen-dency list heads for reactive memory words are stored in acontiguousreactive memory inforegion that starts at address231 = 0x8000000 and grows downward, eventually hittingthe heap’sbrk.

A critical aspect is how to clean up old dependencies inD when a constraint is re-evaluated. To solve the problemefficiently in constant amortized time per list operation, wekeep for each node its insertion time into the linked list.We say that a node isstale if its timestamp is older thanthe timestamp of the constraint it refers to, andup to dateotherwise. Our solver uses a lazy approach and disposes ofstale nodes only when the word they refer to is modified andthe linked list is traversed to add constraints toS. To preventthe number of stale nodes from growing too large, we use anincremental garbage collection technique.

7. Experimental EvaluationIn this section we present an experimental analysis of theperformances of DC in a variety of different settings, show-ing that our implementation is effective in practice.

7.1 Benchmark Suite

We have evaluated DC on a set of benchmarks that includesa variety of problems on lists, grids, trees, matrices, andgraphs, as well as full and event-intensive interactive appli-cations.

• Linked Lists.We considered several fundamental prim-itives on linear linked data structures, which provide avariety of data manipulation patterns. Our benchmarksinclude data structures for: computing the sum of the ele-ments in a list (adder), filtering the items of a list accord-ing to a given function (filter), randomly assigningeach element of a list to one of two output lists (halver),mapping the items of a list onto new values according toa given mapping function (mapper), merging two sortedlists into a single sorted output list (merger), produc-ing a sorted version of an input list (msorter), produc-ing a reversed version of an input list (reverser); split-ting a list into two output lists, each containing only ele-ments smaller or, respectively, greater than a given pivot(splitter). All benchmarks are subject to operationsthat add or remove nodes from the input lists.

• Graphs and Trees.Benchmarks in this class include clas-sical algorithmic problems for routing in networks andtree computations:

sp: given a weighted directed graph and a source nodes, computes the distances of all graph nodes froms.Graph edges are subject to edge weight decreases (seeSection 3.3).

exptrees: computes the value of an expression treesubject to operations that change leaf values or opera-tors computed by internal nodes (see Section 5.1).

• Linear Algebra.We considered number-crunching prob-lems on vectors and matrices, including the product of avector and a matrix (vecmat) and matrix multiplication(matmat), subject to different kinds of updates of singlecells as well as of entire rows or columns.

16 2011/4/13

From-scratch time Propagation time Mem peak usage DC statistics(secs) (msecs) (Mbytes)

Benchmark conv dc ceal dcconv

cealconv

cealdc dc ceal ceal

dc dc ceal cealdc

avg consper update

instrtime

patchedinstr

adder 0.10 1.44 1.40 14.40 14.00 0.97 0.68 85.80 126.17 211.54 232.87 1.10 1.5 0.030 26exptrees 0.14 1.02 1.07 7.28 7.64 1.04 4.11 5.46 1.32 143.30 225.32 1.57 15.6 0.028 72filter 0.19 2.08 1.11 10.94 5.84 0.53 0.63 2.49 3.95 265.78 189.47 0.71 0.5 0.032 39halver 0.20 2.08 1.33 10.40 6.65 0.63 0.61 3.95 6.47 269.10 218.22 0.81 0.5 0.030 38mapper 0.19 2.04 1.30 10.73 6.84 0.63 0.61 2.63 4.31 261.53 214.34 0.81 0.5 0.032 39merger 0.19 2.12 1.37 11.15 7.21 0.64 0.66 4.43 6.71 284.41 218.21 0.81 0.5 0.031 57msorter 0.91 5.18 3.91 5.69 4.29 0.75 5.55 15.91 2.86 689.59 820.14 1.18 37.6 0.031 75reverser 0.18 2.04 1.30 11.33 7.22 0.63 0.62 2.63 4.24 267.45 214.34 0.80 0.5 0.030 37splitter 0.18 2.27 1.31 12.61 7.27 0.57 1.54 3.92 2.54 344.60 222.34 0.64 1.5 0.031 56

Table 1. Performance evaluation of DC versus CEAL, for a common set ofbenchmarks. Input size isn = 1, 000, 000 for alltests exceptmsorter, for whichn = 100, 000.

0.0001

0.001

0.01

0.1

1

10

100

1000

0.0001 0.001 0.01 0.1 1 10 100

Mill

isec

onds

Percentage of input changed

Update times - Mapper benchmark

DCCEALconv

0 20 40 60 80

100 120 140 160 180

1 2 3 4 5 6 7 8 9 10

Sec

onds

Number of nodes x 100000

Total update times - Adder benchmark

CEALDC

0 10 20 30 40 50 60 70 80 90

1 2 3 4 5 6 7 8 9 10

Rat

io

Number of nodes x 100000

Performance ratio CEAL/DC - Adder benchmark

CEAL/DC

(a) (b) (c)

Figure 14. (a) Change propagation times on themapper benchmark for complex updates with input sizen = 100, 000; (b-c)performance comparison of the change propagation times of DC and CEAL on theadder benchmark.

• Interactive Applications.We considered both full real ap-plications and synthetic worst-case scenarios, including:

othello: full application that implements the well-known board game in which two players in turn placecolored pieces on a square board, with the goal of re-versing as many of their opponent’s pieces as possi-ble;

buttongrid: event-intensive graphic user interfaceapplication with a window containingn × n pushbuttons embedded in a grid layout. This is an extremeartificial scenario in which many events are generated,since a quadratic number of buttons need to be resizedand repositioned to maintain the prescribed layout ateach interactive resize event.

Some benchmarks, such asmatmat andsp, are very com-putationally demanding. For all these benchmarks we haveconsidered an implementation based on DC, obtained bymaking the base data structures (e.g., the input list) reac-tive, and a conventional implementation in C based on non-reactive data structures. Interactive applications (othello

andbuttongrid) are written in theQt-4 framework: changepropagation throughout the GUI is implemented either us-ing constraints (DC versions), or using the standard signal-

slot mechanism provided byQt (conventional versions). Toassess the performances of DC against competitors thatcan quickly respond to input changes, we have also con-sidered highly tunedad-hocdynamic algorithms [18, 36]and incremental solutions realized in CEAL [25], a state-of-the-art C-based language for self-adjusting computation.Benchmarks in common with CEAL areadder, exptrees,filter, halver, mapper, merger, msorter, reverser,and splitter. For these benchmarks, we have used theoptimized implementations provided by Hammeret al.[25].

7.2 Performance Metrics and Experimental Setup

We tested our benchmarks both on synthetic and on real testsets, considering a variety of performance metrics:

• Running times:we measured the time required to initial-ize the data structures with the input data (from-scratchexecution), the time required by change propagation, andbinary code instrumentation time. All reported times arewall-clock times, averaged over three independent trials.Times were measured withgettimeofday(), turningoff any other processes running in the background.

• Memory usage:we computed the memory peak usage aswell as a detailed breakdown to assess which componentsof our implementation take up most memory (constraints,

17 2011/4/13

0.01

0.1

1

10

100

1000

NY BAY COL FLA NW NE

Ave

rage

tim

e pe

r up

date

(m

sec)

Road network

Incremental routing - comparison

rrsp (custom pick)sp (default pick)

sq

Figure 15. Analysis of differentpick function definitionson the incremental routing problem.

shadow memory, reactive memory, stale and non-staledependencies, etc.).

• DC-related statistics:we collected detailed profiling in-formation including counts of patched instructions, staledependencies cleanups, allocated/deallocated reactiveblocks, created/deleted constraints, constraints executedper update, and distinct constraints executed per update.

All DC programs considered in this section, except forsp

that will be discussed separetely, use the default timestamp-based comparator for constraint scheduling.

Experimental Platform. The experiments were performedon a PC equipped with a 2.10 GHz Intel Core 2 Duo with 3GB of RAM, running Linux Mandriva 2010.1 with Qt 4.6.All programs were compiled withgcc 4.4.3 and optimiza-tion flag-O3.

7.3 Incremental Computation

As observed in Section 3.3, the reactive nature of our mixedimperative/dataflow framework makes it a natural groundfor incremental computation. In this section, we present ex-perimental evidence that a constraint-based solution in ourframework can respond to input updates very efficiently. Wefirst show that the propagation times are comparable to stateof the art automatic change propagation frameworks, such asCEAL [25], and for some problems can be orders of magni-tude faster than recomputing from scratch. We then considera routing problem on real road networks, and compare ourDC-based solution both to a conventional implementationand to a highly optimizedad hocdynamic algorithm sup-porting specific update operations.

Comparison to CEAL. Table 1 summarizes the outcomesof our experimental comparison with the conventional ver-sion and with CEAL for all common benchmarks. Inputsize isn = 1, 000, 000 for all tests (with the exception of

msorter, for which n = 100, 000), wheren is the lengthof the input list for the list-based benchmarks, and the num-ber of nodes in the (balanced) input tree forexptrees. Ta-ble 1 reports from-scratch execution times of both DC andCEAL (compared to the corresponding conventional imple-mentations), average propagation times in response to smallchanges of the input, memory usage and some DC stats (av-erage number of executed constraints per update, executableinstrumentation time, and total number of patched instruc-tions). The experiments show that our DC implementationperforms remarkably well. From-scratch times are on aver-age a factor of1.4 higher than those of CEAL, while prop-agation times are smaller by a factor of 4 on average forall tests considered except theadder, yielding large speed-ups over complete recalculation. In the case of theadder

benchmark, DC leads by a huge margin in terms of propa-gation time (see Figure 14a and Figure 14b), which can beattributed to the different asymptotic performance of the al-gorithms handling the change propagation (constant for DC,and logarithmic in the input size for the list reduction ap-proach used by CEAL). We remark that the logarithmicbound of self-adjusting computation could be reduced toconstant by using a traceable accumulator, as observed inSection 5.1 (however, support for traceable data structures isnot yet integrated in CEAL).

We also investigated how DC and CEAL scale in the caseof batches of updates that change multiple input items si-multaneously. The results are reported in Figure 14a for therepresentativemapper benchmark, showing that the selec-tive recalculations performed by DC and CEAL are fasterthan recomputing from scratch for changes up to significantpercentages of the input.

Comparison to ad hoc Incremental Shortest Paths. Wenow consider an application of the shortest path algorithmdiscussed in Section 3.3 to incremental routing in road net-works. We assess the empirical performance of a constraint-based solution implemented in DC (sp) by comparing itwith Goldberg’ssmart queueimplementation of Dijkstra’salgorithm (sq), a highly-optimized C++ code used as thereference benchmark in the 9th DIMACS ImplementationChallenge [20], and with an engineered version of thead hocincremental algorithm by Ramalingam and Reps (rr) [18,36]. Our code supports update operations following the high-level description given in Figure 5, except that we create oneconstraint per node, rather than one constraint per edge. Weused as input data a suite of US road networks of size up to1.5 million nodes and 3.8 million edges derived from the UACensus 2000 TIGER/Line Files [37]. Edge weights are largeand represent integer positive travel times. We performed oneach graph a sequence ofm/10 random edge weight de-creases, obtained by picking edges uniformly at random andreducing their weights by a factor of 2. Updates that did notchange any distances were not counted.

18 2011/4/13

Road network From-scratch Propagation Speedup Mem peak usage Statisticstime (msec) time (msec) (Mbytes)

Graph n · 103 m · 103 sq sp rr sqsp

sqrr sp rr sq sp cons

per updaterr node scansper update

NY 264 733 50.99 0.16 0.07 318.6 728.4 76.75 26.62 26.19 143.9 143.9BAY 321 800 59.99 0.15 0.07 399.9 857.0 84.84 30.21 29.82 170.6 170.5COL 435 1, 057 79.98 0.28 0.17 285.6 470.4 108.61 39.09 38.97 378.3 378.2FLA 1, 070 2, 712 192.97 0.63 0.35 306.3 551.3 251.26 93.42 93.29 687.5 687.3NW 1, 207 2, 840 236.96 0.87 0.54 272.3 438.8 270.66 102.15 101.53 1002.4 1002.3NE 1, 524 3, 897 354.94 0.27 0.16 1314.5 2218.3 350.86 132.85 132.15 320.2 320.1

Table 2. Performance evaluation of DC for incremental routing in US road networks using up to 1.5 million constraints.

(a)

0

20

40

60

80

100

120

8x8 12x12 16x16 20x20 24x24

Ave

rage

tim

e pe

r re

size

(m

sec)

Grid size (n x n)

Total and change propagation times per resize event

buttongrid-DC (total)buttongrid-Qt (total)

buttongrid-DC (change prop.)buttongrid-Qt (change prop.)

(b)

0

0.5

1

1.5

2

2.5

3

8x8 12x12 16x16 20x20

Ave

rage

tim

e pe

r m

ove

(mse

c)

Board size (n x n)

Total and change propagation times per move

othello-DC (total)othello-Qt (total)

othello-DC (change prop.)othello-Qt (change prop.)

Figure 16. Comparison with signal-slot mechanism inQt: (a)buttongrid; (b) othello.

The results of our experiments are shown in Table 2 andFigure 15. Bothsp andrr were initialized with distancescomputed usingsq, hence we report from-scratch time onlyfor this algorithm. Due to the nature of the problem, theaverage number of node distances affected by an update israther small and almost independent of the size of the graph.Analogously to the incremental algorithm of Ramalingamand Reps, the automatic change propagation strategy usedby our solver takes full advantage of this strong locality, re-evaluating only affected constraints and delivering substan-tial speedups over static solutions in typical scenarios. OurDC-based implementation yields propagation times that are,on average, a factor of1.85 higher than the conventionaladhoc incremental algorithm, but it is less complex, requiresfewer lines of code, is fully composable, and is able to re-spond seamlessly to multiple data changes, relieving the pro-grammer from the task of implementing explicitly changepropagation. We also testedsp with different types of sched-ulers. By customizing thepick function of the default prior-ity queue scheduler (giving highest priority to nodes closestto the source), a noticeable performance improvement hasbeen achieved (see Figure 15). We also tried a simple stackscheduler, which, however, incurred a slowdown of a factorof 4 over the default scheduler.

7.4 Comparison toQt’s Signal-slot Mechanism

Maintaining relations between widgets in a graphic user in-terface is one of the most classical applications of dataflowconstraints [38]. We assess the performance of DC in event-

intensive interactive applications by comparing the DC im-plementations ofbuttongrid andothello with the con-ventional versions built atopQt’s signal-slot mechanism.

In buttongrid, each constraint computes the size andposition of a button in terms of the size and position ofadjacent buttons. We considered user interaction sessionswith continuous resizing, which induce intensive schedul-ing activity along several propagation chains in the acyclicdataflow graph. Inothello, constraints are attached to cellsof the game board (stored in reactive memory) and main-tain a mapping between the board and its graphical repre-sentation: in this way, the game logic can be completely un-aware of the GUI backend, as prescribed by the observerpattern (see Section 5.2). For both benchmarks, we experi-mented with different grid/board sizes. Figure 16 plots theaverage time per resize event (buttongrid) and per gamemove (othello), measured over 3 independent runs. Boththe total time and the change propagation time are reported.For all values ofn, the performance differences of the DCandQt conventional implementations are negligible and thecurves are almost overlapped. Furthermore, the time spentin change propagation is only a small fraction of the totaltime, showing that the overhead introduced by access viola-tions handling, instrumentation, and scheduling in DC canbe largely amortized over the general cost of widget man-agement and event propagation inQt and in its underlyinglayers.

19 2011/4/13

(a)

0

50

100

150

200

250

300

1 2 4 8 16 32 64 128 256 512 10242000

Mem

ory

usag

e br

eakd

own

(MB

)

Block size b

Vector-matrix product: memory usage as a function of block size

code + stack at startupmore dc internalsdependenciesreactive memory (user)shadow memory (dc)constraints (user)constraints (dc)

(b)

1

4

16

64

256

1024

1 2 4 8 16 32 64 128 256 512 1024 2048

Avg

upd

ate

time

per

chan

ged

cell

(us)

Block size b

Vector-matrix product: time as a function of the block size (log-log scale)

2000

1000 500 250125 63 32 16 8 4 2 1

Single cell updateBatch of column cell updates

Figure 17. Analysis ofvecmat as a function of the block size: (a) memory usage; (b) update time for two different kinds ofupdates. Point labels indicate the number of constraints executed in each batch (for single cells updates, the number ofexecutedconstraints is always 1, but a constraint does more work asb increases).

0

0.2

0.4

0.6

0.8

1

1.2

1.4

0 0.5 1 1.5 2 2.5

Sec

onds

Executable size (Megabytes)

Time required for dynamic code instrumentation

Figure 18. Time required for dynamic instrumentation.

7.5 Fine-grained vs. Coarse-grained Decompositions

A relevant feature of DC is that designers can flexibly de-cide at which level of granularity a given algorithmic solu-tion can be decomposed into smaller parts, i.e., they mightuse a single constraint that performs the entire computation(coarse-grained decomposition), or many constraints eachcomputing only a small portion of the program’s state (fine-grained decomposition). In reactive scenarios, where con-straints are re-evaluated selectively only on the affectedpor-tions of the input, this design choice can have implicationsboth on memory usage and on running time. To explore thesetradeoffs, we experimented with matrix benchmarksmatmat

andvecmat. For brevity, in this section we focus onvecmat,the results formatmat being similar.

LetV be a vector of sizen and letM be a reactive matrixof sizen×n. Our implementation of the vector-matrix prod-uct algorithm isblocked: constraints are associated to blocksof matrix cells, where a block is a set of consecutive cellson the same column. If the block size is 1, then there is oneconstraint per matrix cell: constraintci,j is responsible ofupdating thej-th entry of the output vector with the productV [i]∗M [i][j]. This can be done inO(1) time by maintaininga local copy of the old product value and updating the resultwith the difference between the new value and the old one.

If the block size isn, then there is a constraint per matrixcolumn: constraintcj associated with columnj computesthe scalar product betweenV andM [·][j] and updates thej-th entry of the output vector with the new value. The ap-proach can be naturally adapted to deal with any block sizeb ∈ [1, n].

In Figure 17 we report the outcome of an experiment withn = 2000 in which we increased the block sizeb from 1 ton.As shown in Figure 17a, the memory usage is inversely pro-portional tob, and thus directly proportional to the numberof constraints: the memory used for maintaining constraintsis about half of the total amount whenb = 1, and negligiblewhenb = n. All the other components (in particular, reactivememory, shadow memory, and dependencies) do not dependon the specific block size and remain constant. Figure 17bshows the effect ofb on the change propagation times fortwo different kinds of updates. For single cells updates, thetime scales linearly withb (axes are on a log-log scale): thisconfirms the intuition that if an update changes only a sin-gle cell, implementations using larger block sizes performalot of unnecessary work. The scenario is completely differ-ent if single updates need to change entire columns (which isa typical operation for instance in incremental graph reacha-bility algorithms [18]): in this case, change propagation timenot only is not penalized by larger block sizes, but it is alsoslightly improved. This is due to the fact that larger valuesof b yield a smaller number of constraints, which inducessmaller scheduling activity. The improvement, however, ismodest, suggesting that DC’s constraint scheduling over-head is modest compared to the overall work required tosolve a given problem even at the finest-grained decompo-sition where there is one constraint per matrix cell.

7.6 Instrumentation Overhead

As a final set of experiments, we have measured how in-strumentation time scales as a function of the executablefile size. We noticed that the performance overheads aredominated by the initial static binary code analysis phase

20 2011/4/13

performed during DC’s initialization, which scans the codeto index memory access instructions as described in Sec-tion 6.1. The times required for access violation handlingand just-in-time binary code patching are negligible com-pared to the overall execution times in all tested applicationsand are not reported. The experiment was conducted by ini-tializing DC on executable files obtained by linking stati-cally object files of increasing total size. The results are re-ported in Figure 18 and indicate that DC scales linearly, withtotal instrumentation times being reasonably small even forlarge executable sizes.

8. Related WorkThe ability of a program to respond to modifications of itsenvironment is a feature that has been widely explored in alarge variety of settings and along rather different researchlines. While this section is far from being exhaustive, wediscuss some previous works that appear to be more closelyrelated to ours.

GUI and Animation Toolkits. Although dataflow pro-gramming is a general paradigm, dataflow constraints havegained popularity in the 90’s especially in the creation ofinteractive user interfaces. Amulet [34] and its predeces-sor Garnet [33] are graphic user interface toolkits based onthe dataflow paradigm. Amulet integrates a constraint solverwith a prototype-instance object model implemented on topof C++, and is closely related to our work. Each object,created by making an instance of a prototype object, con-sists of a set of properties (e.g., appearance or position) thatare stored in reactive variables, called slots. Constraints arecreated by assigning formulas to slots. Values of slots are ac-cessed through aGetmethod that, when invoked from insideof a formula, sets up a dependency between slots. A varietyof approaches have been tested by the developers to solveconstraints [38]. FRAN (Functional Reactive Animation)provides a reactive environment for composing multime-dia animations through temporal modeling [39]: graphicalobjects in FRAN use time-varying, reactive variables to au-tomatically change their properties, achieving an animationthat is function of both events and time.

Reactive Languages. The dataflow model of computationcan be also supported directly by programming languages.Most of them are visual languages, often used in industrialsettings [10], and allow the programmer to directly man-age the dataflow graph by visually putting links betweenthe various entities. Only a few non-visual languages pro-vide a dataflow environment, mostly for specific domains.Among them, Signal [24] and Lustre [13] are dedicated toprogramming real-time systems found in embedded soft-ware, and SystemC [23] is a system-level specification anddesign language based on C++. The data-driven Alpha lan-guage provided by the Leonardo software visualization sys-tem allows programmers to specify declarative mappings

between the state of a C program an a graphical represen-tation of its data structures [16]. Recently, Meyerovichetal. [32] have introduced Flapjax, a reactive extension to theJavaScript language targeted to Web applications. Flapjaxoffersbehaviors(e.g., variables whose value changes are au-tomatically propagated by the language), andevent streams(e.g., potentially infinite streams of discrete events, each ofwhich triggers additional computations). SugarCubes [12]and ReactiveML [31] allow reactive programming (in Javaand OCAML, respectively) by relying not on operating sys-tem and runtime support, as our approach does, but rather oncausality analysis and a custom interpreter/compiler. Thesesystems, however, track dependencies between functionalunits, through the use of specific language constructs, suchasevents, and explicit commands for generating and waitingfor events.

Constraint Satisfaction. Dataflow constraints fit withinthe more general field of constraint programming [7]. Termssuch as “constraint propagation” and “constraint solving”have often been used in papers related to dataflow since theearly developments of the area [11, 34, 38]. However, thetechniques developed so far in dataflow programming arequite distant from those appearing in the constraint program-ming literature [9]. In constraint programming, relationsbe-tween variables can be stated in the form of multi-way con-straints, typically specified over restricted domains suchasreal numbers, integers, or Booleans. Domain-specific solversuse knowledge of the domain in order to forbid explicitlyvalues or combinations of values for some variables [9],while dataflow constraint solvers are domain-independent.Moving from early work on attribute grammars [17, 29],a variety of incremental algorithms for performing effi-cient dataflow constraint satisfaction have been proposedin the literature and integrated in dataflow systems such asAmulet. These algorithms are based either on a mark-sweepapproach [17, 27], or on a topological ordering [6, 26].In contrast, DC uses a priority-based approach, which al-lows users to customize the constraint scheduling order.Mark-sweep algorithms are preferable when the dataflowgraph can change dynamically during constraint evaluation:this may happen if constraints use indirection and condi-tionals, and thus cannot be statically analyzed. With bothapproaches, if there are cyclic dependencies between con-straints, they are arbitrarily broken, paying attention toeval-uate each constraint in a cycle at most once. Compared toour iterative approach, this limits the expressive power ofconstraints.

Self-adjusting Computation. A final related area, that wehave extensively discussed throughout the paper, is that ofself-adjusting computation, in which programs respond toinput changes by updating automatically their output. Thisisachieved by recording data and control dependencies duringthe execution of programs so that a change propagationalgorithm can update the computation as if the program

21 2011/4/13

were run from scratch, but executing only those parts of thecomputation affected by changes. We refer to [3, 4, 25] forrecent progress in this field.

9. Future WorkThe work presented in this paper paves the road to severalfurther developments. Although conventional platforms of-fer limited support for implementing reactive memory effi-ciently, we believe that our approach can greatly benefit fromadvances in the hot field of transactional memories, whichshares with us the same fundamental need for a fine-grained,highly-efficient control over memory accesses. Multi-coreplatforms suggest another interesting direction. Indeed,ex-posing parallelism was one of the motivations for dataflowarchitectures, since the early developments of the area. Weregard it as a challenging goal to design effective models andefficient implementations of one-way dataflow constraints inmulti-core environments.

AcknowledgmentsWe wish to thank Umut Acar and Matthew Hammer formany enlightening discussions and for their support withCEAL. We are also indebted to Alessandro Macchioni forhis contributions to the implementation of reactive memory,and to Pietro Cenciarelli and Ivano Salvo for providing use-ful feedback on the formal aspects of our work.

This work was supported in part by the Italian Ministryof Education, University, and Research (MIUR) under PRIN2008TFBWL4 national research project “AlgoDEEP: Algo-rithmic challenges for data-intensive processing on emerg-ing computing platforms”.

References[1] M. Abadi, T. Harris, and M. Mehrara. Transactional memory

with strong atomicity using off-the-shelf memory protectionhardware. InPPoPP, pages 185–196, 2009.

[2] R. Abraham, M. M. Burnett, and M. Erwig. Spreadsheetprogramming. InWiley Encyclopedia of Computer Scienceand Engineering. John Wiley & Sons, Inc., 2008.

[3] U. A. Acar, G. E. Blelloch, M. Blume, and K. Tangwongsan.An experimental analysis of self-adjusting computation. InPLDI, pages 96–107, 2006.

[4] U. A. Acar, G. E. Blelloch, R. Ley-Wild, K. Tangwongsan,and D. Turkoglu. Traceable data types for self-adjustingcomputation. InPLDI, pages 483–496, 2010.

[5] R. K. Ahuja, T. L. Magnanti, and J. B. Orlin. Networkflows: theory, algorithms, and applications. Prentice-Hall,Inc., 1993.

[6] B. Alpern, R. Hoover, B. K. Rosen, P. F. Sweeney, and F. K.Zadeck. Incremental evaluation of computational circuits. InACM-SIAM Symp. on Discrete Algorithms (SODA), pages 32–42, 1990.

[7] K. R. Apt. Principles of Constraint Programming. CambridgeUniversity Press, 2003.

[8] R. Bellmann. On a routing problem.Quarterly of AppliedMathematics, 16:87–90, 1958.

[9] C. Bessiere. Constraint propagation. In F. Rossi, P. vanBeek,and T. Walsh, editors,Handbook of Constraint Programming.2006.

[10] P. A. Blume.The LabVIEW Style Book. Prentice Hall, 2007.

[11] A. Borning. The Programming Language Aspects ofThingLab, a Constraint-Oriented Simulation Laboratory.ACM Trans. Program. Lang. Syst., 3(4):353–387, 1981.

[12] F. Boussinot and J.-F. Susini. The sugarcubes tool box:areactive java framework.Software: Practice and Experience,28(14):1531–1550, 1998.

[13] P. Caspi, P. Pilaud, N. Halbwachs, and J. Plaice. Lustre, adeclarative language for programming synchronous systems.In POPL, 1987.

[14] C. Chambers, B. Harrison, and J. Vlissides. A debate onlanguage and tool support for design patterns. InPOPL, pages277–289, 2000.

[15] P. Cousot and R. Cousot. Abstract interpretation: A unifiedlattice model for static analysis of programs by construction orapproximation of fixpoints. InPOPL, pages 238–252, 1977.

[16] P. Crescenzi, C. Demetrescu, I. Finocchi, and R. Petreschi.Reversible execution and visualization of programs withLeonardo.J. Vis. Lang. Comput., 11(2):125–150, 2000.

[17] A. J. Demers, T. W. Reps, and T. Teitelbaum. Incrementalevaluation for attribute grammars with application to syntax-directed editors. InPOPL, pages 105–116, 1981.

[18] C. Demetrescu.Fully Dynamic Algorithms for Path Problemson Directed Graphs. PhD thesis, Sapienza University ofRome, 2001.

[19] C. Demetrescu, I. Finocchi, and G. Italiano.Handbookon Data Structures and Applications, chapter 36: DynamicGraphs. D. Mehta and S. Sahni (eds.), CRC Press, 2005.

[20] C. Demetrescu, A. V. Goldberg, and D. S. Johnson, editors.The Shortest Path Problem: Ninth DIMACS ImplementationChallenge. American Mathematical Society, 2009.

[21] B. Demsky and M. Rinard. Automatic detection and repairoferrors in data structures. InOOPSLA ’03, pages 78–95, 2003.

[22] A. Ezust and P. Ezust.An Introduction to Design Patterns inC++ with Qt 4. Prentice Hall, 2006.

[23] T. Groetker, S. Liao, G. Martin, and S. Swan.System Designwith SystemC. Kluwer Academic Publishers, 2002.

[24] P. L. Guernic, A. Benveniste, P. Bournai, and T. Gautier.Signal, a data-flow oriented language for signal processing.IEEE-ASSP, 1986.

[25] M. Hammer, U. A. Acar, and Y. Chen. CEAL: a C-basedlanguage for self-adjusting computation. InPLDI, pages 25–37, 2009.

[26] R. Hoover. Incremental graph evaluation. PhD thesis, Dept.of Computer Science, Cornell Univ., Ithaca, NY, 1987.

[27] S. E. Hudson. Incremental attribute evaluation: A flexiblealgorithm for lazy update.ACM Trans. Prog. Lang. Sys., 13(3):315–341, 1991.

22 2011/4/13

[28] A. Kay. Computer software.Scient. Amer., 251(3):191–207,1984.

[29] D. E. Knuth. Semantics of context-free languages.Theory ofComputing Systems, 2(2):127–145, June 1968.

[30] M. Z. Malik, K. Ghori, B. Elkarablieh, and S. Khurshid. Acase for automated debugging using data structure repair. InASE, pages 620–624, 2009.

[31] L. Mandel and M. Pouzet. ReactiveML, a reactive extension toML. In Proceedings of 7th ACM SIGPLAN International con-ference on Principles and Practice of Declarative Program-ming (PPDP’05), Lisbon, Portugal, 2005.

[32] L. A. Meyerovich, A. Guha, J. Baskin, G. H. Cooper,M. Greenberg, A. Bromfield, and S. Krishnamurthi. Flapjax:a programming language for ajax applications. InOOPSLA,pages 1–20, 2009.

[33] B. A. Myers. Garnet: Comprehensive support for graphical,highly-interactive user interfaces.IEEE Computer, 23, 1990.

[34] B. A. Myers, R. G. McDaniel, R. C. Miller, A. S. Ferrency,A. Faulring, B. D. Kyle, A. Mickish, A. Klimovitski, andP. Doane. The Amulet environment: New models for effectiveuser interface software development.IEEE Trans. Softw. Eng.,23(6):347–365, 1997.

[35] S. Prasad and S. Arun-Kumar. An introduction to operationalsemantics. InCompiler Design Handbook: Optimizationsand Machine Code, pages 841–890. CRC Press, Boca Raton,2002.

[36] G. Ramalingam and T. Reps. An incremental algorithm fora generalization of the shortest-path problem.Journal ofAlgorithms, 21(2):267 – 305, 1996.

[37] U.S. Census Bureau, Washington, DC. UA Census 2000TIGER/Line Files.http://www.census.gov/geo/www/tiger/, 2002.

[38] B. T. Vander Zanden, R. Halterman, B. A. Myers, R. Mc-Daniel, R. Miller, P. Szekely, D. A. Giuse, and D. Kosbie.Lessons learned about one-way, dataflow constraints in theGarnet and Amulet graphical toolkits.ACM Trans. Program.Lang. Syst., 23(6):776–796, 2001.

[39] Z. Wan and P. Hudak. Functional reactive programming fromfirst principles. InPLDI, pages 242–252, 2000.

23 2011/4/13


Recommended