							July, 1989

Here are some of the questions that have been mailed to the SR-project,
with some answers.  Some of these answers differ from the original replies,
because the system has changed since then.
    
-------------------------------------------------------------------------------

   I have the documentation mentioned in the "Revised Report on SR
   Programming Language", written by Andrews,G.R.,et al, except [3].
   I have been running some of the programs given in the paper in
   Toplas,1988.  However, the dining philosopher's solution (pg 64-67),
   presents some problems. The compiler refuses to compile it !
   The problem is with the creation of the abstract resources,
   ServantOps, and PhilosopherOps. The lines
	 si[i] := ServantOps from s[i]
	   ...  create PhilosopherOps(pi[i],i,t)
   cause the trouble.
   The word (keyword ?) "from" is not recognized by the compiler.

You are correct.  The language described in the TOPLAS paper is
different in a few ways from what we finally settled on and what has
actually has been implemented, which is described in the Report.  The
"from" keyword and semantics is one of those.  It no longer is in the
language.  Its effect can be achieved by other means, e.g., explicitly
stating all the assignments that were implied by "from".  You might
also find it easier to change some of the interfaces.  A working example
of the Dining Philosophers program is now part of the distribution,
in the directory examples/dining.
    
-------------------------------------------------------------------------------

   I understand that SR has been used for course work at the graduate
   level at U of Arizona. Would it be possible to get some idea on the kind
   of problems that were attempted ? 

Among other things, SR was used in a graduate level course in principles of
concurrent programming as a small programming project and in a larger project
course or independent studies.  Typical projects were ATM simulations,
airline reservation systems, replicated databases, pieces of distributed
operating systems, and experiments in fault-tolerance.  SR is being used
similarly at UC Davis.

-------------------------------------------------------------------------------

    What kinds of restrictions apply to C functions referenced as "external"
    from SR?  For example, is it possible to make blocking systems calls?
    Would there be any interference with SR to use TCP/IP sockets?

The general rule is, "be careful".  That is, once you get outside the
bounds of the SR language proper, it's possible to mess things up
fairly easily.  SR assumes that it knows what's going on in its process,
and can get awfully confused if things happen behind its back.

I realize that's awfully vague, but that's the general situation, and
we haven't any documentation that's more precise.

Externals work best for accessing quick system calls (getpid, chmod, etc.)
not available directly, and for escaping into C for heavy numerical
calculation.

Watch out for name conflicts with SR internal routines.

To answer your specific questions, if a system call blocks it will block
ALL the SR processes within the virtual machine (Unix process).  That
may or may not be a problem, depending on the program.

I don't see any specific problem with sockets, as long as you stay away
from the ones SR is using.

-------------------------------------------------------------------------------

	Must one use 'stop' to terminate a program with virtual machines?
    I have not been able to do it any other way, and your 'remote.sr' test
    program uses it.  We have version 1.0.0.  Using 'destroy' terminates the
    rsh'd process or the local one that starts the virtual machine, but srx
    is still running.   Is there any graceful way to terminate?  'Stop' seems
    drastic, since it pays no attention to what may be happening elsewhere.

Currently, "stop" is the only "normal" termination method for such programs.
Distributed termination has not had a high priority, and multiple-machine
deadlock is not detected.

-------------------------------------------------------------------------------

    How could I establish a communication between a process running 
    on a machine A and a daemon running on a machine B, with A <> B?
    The only thing that the client knows is the resource name that he
    wants to use and also the hostname where the daemon that contains
    the resource runs.  Could you please send a little piece of code
    that implements a short example?
    
SR doesn't provide a means for two independent programs to establish
communications.  It only handles communications within a single SR "program",
possibly running on multiple machines.

-------------------------------------------------------------------------------

   Do you know if anyone  has rewritten the socket routines to translate
   into standard network byte-ordering?  We have a network of Suns and
   Vaxes and would run SR on all of them instead of just on the Suns if so.
   If no one has done it, are you interested if we make such revisions?

Not to our knowledge;  we'd definitely be interested.

-------------------------------------------------------------------------------

    I was surprised to find that new is a keyword.  It is the only pre-defined
    function (other than int, char, bool, which are already keywords) that is
    a keyword.  Sort of odd; e.g., new is reserved, but free is not. 

This was done to make parsing simpler;  new(x) differs from a "normal"
function in that its argument x is not an expression, but instead a type name.
For precedent consider "sizeof" in C or "new" in C++.

-------------------------------------------------------------------------------

    What does your verification suite do?

It runs about 360 sr programs and verifies that the output is as expected.
Most of the programs are very simpleminded and just test one aspect of
the compiler;  few are "real" SR programs.

The whole process is controlled by a shell script "srv" that walks the
"vsuite" directory tree.  "srv" and a small subset of the "vsuite" tree
are part of the basic SR distribution in order to verify a successful
build and installation.

-------------------------------------------------------------------------------

    Under what shell do your run the Scripts in your examples/* directories?
    I don't recognize the leading '0' in the command lines of those Scripts.
    
They're run under control of the shell script "srv".  Each line contains a
/bin/sh command, preceded by the expected exit status.  "srv" also does
some implicit I/O redirection.

-------------------------------------------------------------------------------

    I'm getting an "RTS abort: stack corrupted" error.  Could this
    error have come up as a result of a bug in my code, or is it a sign of
    an SR bug?

SR performs a few simple sanity checks before switching process stacks;  the
message indicates that one of these failed, and something is wrong.  It could
be due, for example, to storing using a bad subscript.

-------------------------------------------------------------------------------

    How can I add a new built-in procedure to the compiler?

First, add an entry to sr/predefs.h defining the function name and return type.

Then, edit sr/predef.c.  There are two huge switch statements, both of which
will need a new case entry:

--  In predef_args(), add a case to check the function arguments.

--  In gen_predef(), add a case to generate the code corresponding
    to the new builtin.

-------------------------------------------------------------------------------

    I would be grateful for any hints on how to debug compiler problems.

We don't have many special techniques -- we use dbx and/or add print
statements.  Sometimes -Dxxx is useful if there are already print
statements in the right places; see the file sr/Debugs for the meanings
of the xxx flags.

    Do you have any documentation on the compiler, beyond what is
    in the code?

There's a little bit of stuff in the doc/internals subdirectory.
Some of it is a bit old, though.

    Is there any utility to print out nodes, in a readable format? 
    Currently I need to print out the TK-code.

Try pnode(nodeptr), which prints a tree of nodes (& their signatures)
recursively.

-------------------------------------------------------------------------------

    We are having some fundamental problems understanding the basic
    mapping of SR resources/processes and messages to their actual
    Unix implementations.

Each instance of an SR virtual machine is implemented as a Unix process
(possibly but not necessarily on a different host from the first one).

Each instance of an SR resource is a lightweight process within a virtual
machine.  SR implements the lightweight processes itself.

Messages are passed by Berkeley sockets if between different virtual
machines, otherwise via memory (which of course is shared by all the
lightweight processes).

-------------------------------------------------------------------------------

    I still do not understand the purpose of the srx
    process.  I've mapped out the message paths of
    all the messages that pass through the srx process.
    Why aren't these messages sent directly to the
    remote process?  In the case of the REQ_CREVM
    message, why isn't the execl (rsh, ...) executed
    directly from the rts process?
    
srx exists because we thought it would be a bit simpler to have a central
point of focus for distributed programs.  For example, because srx starts
all new virtual machines, it can easily ensure that they have unique
numbers.  Although srx gets involved in starting VMs and putting them in
touch with each other, they mostly talk directly with each other from then
on.

-------------------------------------------------------------------------------

	    I have had trouble using dbx with SR programs that run srx.  If
    I compile sr/examples/remote/remote.sr with the -g switch and then run
    the program under dbx, I get various errors: 'source file busy' 'srx cant
    execute' 'stack overwritten; can't trace'.  Is there a way around these
    problems?

Not really (although I don't understand the one about "source file busy").
The debugging of parallel programs is a hot research topic these days,
and lots of people have lots of ideas, but we haven't done anything for SR.
The old method of sticking in print statements can help.

Also, you can set the environment variable SRXDEBUG to a hexadecimal value;
the different bits enable different sorts of debugging traces, as defined
in rts/Debugs.

-------------------------------------------------------------------------------
    
    In the io.c source code the CHECK_FILE macro accepts
    two parameters: fp and rv.  If the fp is the noop
    file, CHECK_FILE returns rv.  Why does it return rv?
    As far as I can tell, none of the code that calls
    CHECK_FILE uses the return value.


The return values may be used by generated code, for example in
    if read(f,i) = EOF -> ...

-------------------------------------------------------------------------------

    We have been looking at the C-code generated to invoke an operation.
    We observed the use made of the comma operation in C. Neither of us
    has made use of it before, and the cursory discussion in the literature
    did not enlighten us as to why it is used here (at the end of each line
    of code).  The code would appear to make sense with the comma replaced
    by a semicolon.

The value of a comma operator is its right operand; for example,
	z = (1+2, 3+4);
assigns 7 to z.

The reason for the heavy use of the comma operator is that the compiler
can be in the middle of something else, such as  generating an argument
list, when it discovers that it needs to do a sequence of assignments.
C doesn't allow multiple statments in the middle of an expression, i.e.
	foo(a,b,{x=12;y=b-a;c=x*y});
but SR can get what it needs by generating
	foo(a,b,(x=12,y=b-a,c=x*y));
which in the absence of side effects works like
	x = 12;
	y = b - a;
	c = x * y;
	foo(a,b,c);
but is much easier to generate.

The comma is also used sometimes when a semicolon would work,
because the same generation routines are used in different situations.

-------------------------------------------------------------------------------

    Could you give me a brief explanation of the meaning of the main symbol
    table (vs. the aux table)?

In general the main symbol table contains objects that are visible within
the current compilation unit.  Aux tables are used when an object needs
additional information.  An example:

	resource x
		op f(y1,y2:char)
		const A = 3
	body x()
		var z:int
		op g(zz:int)
	end

the main st would like
		type	kind		tdef		
	x	T_SPEC	K_BLOCK(?)	ptr to at1
	z	T_INT	K_VAR		null
	g	T_VOID	K_OP		ptr to at2

at1 contains stuff declared in spec for x, i.e.,
	f	T_VOID	K_OP		ptr to at3
	A	T_INT	K_CONST		null

at2 would contain parameters for g, i.e.,
	zz	T_INT	K_PARAM		null
at3 would be
	y1	T_CHAR	K_PARAM		null
	y2	T_CHAR	K_PARAM		null

[Note: above is from memory and is ignoring some details, e.g., each at
has an empty node at top.]

If x were to import say resource r, then x's main st would contain
	r	T_SPEC	K_IMPORT	ptr to at4
where at4 contains stuff in r's spec.

You might also check out doc/internals/symtab.
A bit out of date, but should give general idea.

-------------------------------------------------------------------------------

    (1) Does the offset in the symbol table correspond to the offset from rv
    in the C-code? If not, what is it?

It's an offset from rv for resource variables, or from lv for procedure
variables, etc.  If it's OFF_UNK, addressing is through a descriptor.
See cprintf.c for examples of how it's used.
    
    (2) Where is the code is each variable (particularly capabilities) assigned
    a memory location (eg rv + 16)?

Here's an overview of "offsize.c", where this occurs.

First a little background.  Each SR process, when it is started, gets
three pointers to blocks of data.  All the data that the process can
access is found directly or indirectly in one of these blocks.  The
blocks are

	* The resource parameter block.  The pointer to this block is
	  named rp in the generated code.
	* The resource variable block.  This contains variables
	  declared inside the resource but not in any proc or process.
	  The pointer to this block is named rv in the generated code.
	* The local variable block.  The pointer to this block is
	  named lv in the generated code.

In addition, the parameters to each input statement live in a single
block of data; a pointer to this block is stored in the local
variable block.  Thus these parameters require an extra level of
indirection to access; and their offsets are calculated relative
their parameter block.  Fields in records are assigned offsets
relative to the beginning of the record.

Statically sized objects --- objects whose size is known at compile
time --- are assigned space in one of these blocks.  Objects whose
size is not known at compile time are given "descriptors"; these
always have static size and are assigned space in the
appropriate block.  (Arrays, whether or not they have static size,
always get a descriptor.)  A descriptor contains slots that will
contain information about array bounds, size, and a pointer to the
actual object.  These descriptor slots are filled in at run time, when
the declaration of the object is executed.

All the offsets for an entire resource are calculated by one call to a
function named assign_offset() that lives in offsize.c.  (The rest of
the functions I will mention also live in offsize.c.)  Assign_offset()
is essentially a depth-first traversal of the symbol table (which is
not a tree; in fact, not even a DAG.)  Some of the the functions in
offsize.c are little more than switch statements that control the
traversal of the symbol table: do_do() and do_type(), for example.  To
avoid redundant computation and infinite loops, the symbol table is
marked as it is traversed; if a marked symbol is encountered, the
recursion bottoms out.  The symbol table field used for marking is
"s_done".

The depth-first traversal is done in the following order:
	* if the current symbol represents a list --- e.g., a record,
	  resource capability, etc. --- iterate through the list of
	  components, assigning increasing offsets to each component.
	  Of course, each component represents potential recursion.
	* if the symbol is not a list, first descend through the tree
	  corresponding to the type, then assign an offset to the
	  symbol itself (if it gets one at all.)

The above is only to the best of my recollection, and there are a lot
of messy details I haven't even mentioned.  I hope it is enough to get
you started.
