lval = rval
lval = lval = rval
lval = rval op rval
There is one DFL statement per line, and the parts of a statement
are separated by one or more spaces. DFL has a limited set of
operators, variables and constants
op --> [+-*/=~?]
lval --> var | out | halt
rval --> var | in | num
var consist of a letter
followed by a sequence of zero or more letters or numbers, and are
simply names for queues of integers. A variable reference on the
left-hand side of an assignment indicates writing to the associated
queue, and a variable reference on the right-hand side of an
assignment indicates reading from the associated queue. Variables
can appear at most once on the right-hand side of an assignment
statement; thus we need an explicit "copy" operator for sharing a
value. The variables "in", "out", and "halt" have special meanings. "in" is used only on the right-hand side of a statement, and reads integers from stdin or from the specified file. "out" and "halt" are used only on the left-hand side of a statement. "out" writes integers to stdout, and "halt", when assigned any value, ends the computation--any statements on the ready queue are executed, but no further statements are added to the ready queue, any remaining values in the "out" queue are written to stdout, and the interpreter terminates.
num are integers, and can
appear only on the right-hand side of a statement. A constant
reference in a statement has the effect of writing the value to the
associated buffer exactly once.
bi = bj op bk
has the effect of reading values from queues bj and bk, performing
op on those values, and writing the result
to queue bi. DFL defines the following binary operators
bi = bj = bk
is a double assignment or `copy' command, and has the effect
of reading a value from buffer bk and writing it to buffers bi and
bj
bi = bj ~
bk writes a 1 to buffer bi if bj and bk are equal, and a 0
otherwise
bi = bj ? bk reads buffers bj and bk. If bj is zero the
values bj and bk are discarded; if bj is non-zero, the value from bk
is written to buffer bi
a1 = 1 a5 = a1 = a1 a2 = 0 a6 = a2 + a5 a2 = a7 = a6 a3 = 2 a8 = a3 = a3 c5 = a7 * a8 a4 = 1 a9 = a4 = a4 c3 = c5 + a9 b5 = b6 = c3 b1 = in b2 = b1 = b1 b9 = b3 = b2 b8 = b7 = b6 b4 = b3 / b5 c6 = b4 * b7 c1 = b9 ~ c6 c2 = c1 ? b8 halt = out = c2The associated dataflow graph of the program may make its function more obvious.
bi = bj op bk a record is
created consisting of
bi = bj a
record is created consisting of
A ready statement-record is executed by removing values from input queues, performing the specified operation, and writing the resulting value to the specified output queue. Ready statement-records can be queued for execution, and can be executed in any order, or in parallel by a set of threads. After execution, a statement-record may or may not still be ready for execution again, depending on the state of the associated queues.
These execution rules give "static dataflow," as discussed by your text. For greater concurrency, if both buffers of a statement-record have several values, we would like to process these values in parallel, while keeping the output values in order. (You don't have to do that for this project, though!)
dfl program [datafile]
"program" is the name of the DFL program file, and "datafile" is an
optional file of integers, to be read by the "in" variable. ("in"
reads from stdin, if no file is specified.)
If you can get the project to work without threads, as a single process, it shouldn't be too difficult to add the code for threads. There are a number of ways to manage the threads. For example, you can have a single "dispatcher" handing off tasks (ready statements) to a set of threads, or you can simply divide up the statements among the threads. The latter approach may not give the greatest theoretical parallelism, but it eliminates the need for a separate dispatcher, which can be a major bottleneck. Be careful to restrict i/o to a single thread.