.\"  format with troff or nroff using the -ms macro package
.so common.ms
.Nm PORT
.TL
Porting the SR Programming Language
.AU
Gregg Townsend
Dave Bakken
.AI
Department of Computer Science
The University of Arizona
.sp 1
October 5, 1994
.\"	.Pt title -- major part
.nr Pt 0 1
.de Pt
.ne 10
.sp 3
.ps +1
.ce 1
\fBPart \\n+(Pt \(em \\$1\fP
.br
.ps -1
..
.\"	.Np -- numbered paragraph
.nr Np 0 1
.de Np
.IP \\n+(Np.
..
.LP
This document outlines the steps necessary to port the SR system
to a new system architecture.
The reader should first review the companion document
.I "Installing the SR Programming Language,"
which gives an overview of the system and its organization.
.PP
The first stage of any port of SR is the construction and verification
of a single-threaded system.
That is described first.
With additional effort, SR can be configured to utilize true multiprocessing
on architectures that provide this.
This optional second stage is described later.
.PP
If you decide to attempt a port,
please let us know by electronic mail and keep us informed of your progress.
We'll try to assist by answering questions and offering suggestions.
If you succeed with the port, please send us a copy of your changes
for possible inclusion in future versions of SR.
Several of the existing configurations are based on
such contributed code.
.Pt "Porting Single Threaded SR"
.SH
System Requirements
.LP
Most modern Unix systems provide a good base for implementing SR.
SR assumes the following:
.DS
Memory is addressable at the byte level; characters are 8 bits, ASCII coded.
The C type \fIint\fP is at least 32 bits wide.
All pointer types are the same size as a C \fIlong\fP.
User stacks can be switched by assembly code.
A Berkeley-style \fIsocket\fP(2) interface is available.
.DE
.PP
The first two assumptions pervade the system.
The third is used by runtime routines that use
\fIlong\fP as a universal argument type.
Context switching is localized to
.B rts/process.c
and the assembly code described below.
Dependencies on Berkeley networking are concentrated mostly in
.B rts/socket.c
and
.B rts/srx.c .
.SH
Porting the C Code
.LP
The present SR system has been built in several different environments.
It is intended to compile without warning messages under
compilers designed for either traditional or ANSI C.
.PP
To configure a new architecture, edit the file
.B arch.h
in the main directory and add a section similar to the others.
Use conditional code triggered by a symbol that is predefined by
the C compiler.
.PP
Define
.B ARCH
to be a string describing the target architecture.
Define
.B SFILE
as an appropriately named assembly language file (to be described later).
If
.B "<float.h>"
is not available as a standard
.B "#include"
file, define
.B LOW_REAL
and
.B HIGH_REAL
to be the smallest and largest positive
.B double
values representable on the architecture.
(Be sure to use values that
.B printf
can handle;
do not specify something that rounds up and prints as
.B Infinity .)
If you later find that a special compiler option is needed to accommodate
the complex code generated by SR,
come back and define
.B BIGCC
using the existing examples as a model.
.PP
To check out the C code, first create an empty file in the
.B csw
directory matching the name given for
.B SFILE .
Configure the system according to the installation guide,
and run
.B "make \-k"
to compile.
.PP
If there are errors,
you will need to modify the source code.
Our preference is to try first for a portable solution;
or, failing that, to isolate the code in a machine-specific
.B #ifdef .
Pervasive problems can sometimes be handled by modifying
.B gen.h
in the main directory.
.SH
Writing the Assembly Language Code
.LP
Each SR virtual machine is implemented as a single Unix process,
with the machines communicating via sockets.
Within a virtual machine, SR implements a lightweight process facility.
Assembly code to switch contexts is required for each new machine;
scheduling decisions are handled in the existing C code.
.PP
Three entry points must be supplied for creating, switching,
and checking process contexts.
A ``context'' is just a block of memory containing stack space,
saved registers, and whatever else is necessary.
The C code never looks inside a context array
and doesn't care how it's laid out.
The existing
.B .s
files give some examples for different architectures.
.br
.ne  8
.LP
The needed entry points are:
.RS
.LP
.nf
.ta 20n
\fBsr_build_context\fP (entry, context, size, arg1, arg2, arg3, arg4)
void (*entry)();	/* \fIentry point of function to be called\fP */
char *context;	/* \fIcontext array\fP */
int size;	/* \fIsize of context array, in bytes\fP */
long arg1,... arg4;	/* \fIarguments to be passed to the entry point\fP */
.fi
.IP
Initialize a context array so that, when activated by
.B sr_chg_context ,
it will call the function specified by
.I entry
with the four supplied arguments.
(We assume that no problems arise from possibly calling a C function
with too many arguments.)
The called function is never expected to return;
if it does, a stack underflow abort should occur (see below).
.IP
The context need not be set up so that
.B sr_chg_context
invokes
.I entry
directly;
for example, in the
.B mips.s
code, it proved easiest to plant the address of some additional assembly code
to invoke the desired function when triggered.
.IP
The context array will be aligned on an address that is a multiple of 8.
.LP
.nf
\fBsr_chg_context\fP (newctx, oldctx)
char *newctx, *oldctx;	/* \fIcontext arrays\fP */
.fi
.IP
Suspend execution of the current lightweight process
.I oldctx
in favor of the one identified by
.I newctx .
Generally, this means saving one set of registers
(including stack and frame pointers)
and restoring another.
It is only necessary to save those registers that the C compiler
expects to be saved across function calls.
.B sr_chg_context
is always called explicitly by the SR runtime system,
never by random events such as interrupts.
.IP
The first time
.B sr_chg_context
is called, the program is using its original C stack;
.I oldctx
is zero, and no registers need be saved.
On subsequent calls, 
.I oldctx
is a context created earlier by
.B sr_build_context .
.IP
Some older ports of SR ignore the
.I oldctx
parameter and instead use a saved value.
These versions continue to work for single-threaded SR, but will need
modification if they are to be used to implement MultiSR.
.LP
.nf
\fBsr_check_stk\fP (context)
.fi
.IP
Check that the current stack in
.I context
has not overflowed its bounds.
This routine is called by the runtime system as a sanity check.
.IP
.B sr_check_stk
is called only when executing in an
.B sr_build_context
context.
.RE
.LP
For all three functions, error conditions should be handled by calling
one of the C functions
.B sr_stk_overflow ,
.B sr_stk_underflow ,
or
.B sr_stk_corrupted
as appropriate.
It is a good idea to check the integrity of a new context,
if possible,
before switching to it.
.SH
Integration and Testing
.LP
Go to the
.B csw
subdirectory and place the new assembly language code in the
file named by
.B SFILE
in
.B ../arch.h .
Add that file name to the 
.B SRC
definition in the Makefile.
The Makefile uses
.I cc (1)
to select and copy the
.B .s
file to
.B asm.s ,
which is then
assembled by
.I as (1).
.PP
The
.B cstest
program is provided for testing assembly code in a simpler environment
than that of SR.
Type
.B "make cstest"
to build this test program.
When run,
.B cstest
should produce output that is identical with the file
.B cstest.stdout .
.PP
When the
.B cstest
output is correct, incorporate it into the library by moving
back to the top directory and again running
.B make .
This now builds a complete SR system that includes the new code.
.PP
Initial system testing can be performed using the
.B quick
and
.B examples
subdirectories of the verification suite
(\fBvsuite\fP)
provided with the SR source code.
To run these tests, enter
``\fBsrv/srv quick examples\fP''.
For a port to a new architecture, further testing is in order.
Be sure to get and run the full verification suite as described in the
installation guide.
When the full suite runs successfully you can be
reasonably certain of having a solid implementation of SR.
.Pt "Porting MultiSR"
.SH
Introduction
.LP
MultiSR is a configuration of the SR programming language
that utilizes true multiprocessing on systems having more
than one processor.
In MultiSR,
SR's lightweight threads package multiplexes SR threads
on top of concurrent processes provided by the system.
.SH 
Requirements
.LP
To be able to host MultiSR, a multiprocessor system must provide:
.RS
.IP \(bu
concurrent processes with access to shared variables
.IP \(bu
facilities for dynamically allocating additional shared memory
.IP \(bu
a fast locking facility, such as spin locks
.IP \(bu
a way for each process to determine its index (0..n)
.RE
.PP
MultiSR is ported by defining macros and functions that interface the
SR runtime system to the multiprocessing system.
Some editing of Makefiles and other configuration files is also
likely to be required.
.PP
For MultiSR to work, it is important that the assembly language code be
reentrant.
Most older implementations of
.B sr_chg_context
and
.B sr_check_stk
ignore the 
.I oldctx
parameter and use a variable saved in static memory,
which only works correctly on a uniprocessor.
(However, the
.B i386.s
code works correctly on a Sequent because the value it saves
is private to a particular process.)
.SH
Configuring MultiSR
.LP
Select a name for the new configuration of MultiSR that suggests
the platforms to which it applies.
For example, the Sequent port is named
.B dynix
because it is applicable to Sequents running the Dynix operating system.
Choose a name and substitute it where this document uses
.B xxxxx.
.PP
Begin by editing files in the
.B multi
directory.
Copy
.B irix.h
and
.B irix.c
to make new files
.B xxxxx.h
and
.B xxxxx.c
respectively.
Edit these files as described below,
using the Dynix and Irix files as models.
.SH
File multi/xxxxx.h
.LP
This file casts the system's multiprocessing facilities in terms
expected by SR.
It is included by many components of the SR system
and by the generated C code.
.Np
Define
.B MULTI_SR
with no value.
This enables conditional compilation of MultiSR code throughout the SR system.
.Np
Add any
.B #include
directives and/or function declarations needed to
use the multiprocessing facilities.
.Np
Define
.B MALLOC(n)
and
.B UNMALLOC(a)
macros that function like
.B malloc(n)
and
.B free(a)
but allocate shared memory.
These will probably map directly to system functions.
.B MALLOC
must return a 
.B "char *"
or
.B "void *"
pointer just like
.B malloc.
.Np
If the C code generated by SR needs to be compiled with a special
.B cc
option, such as the ``\-Y'' needed for shared variables on the Sequent,
define the option as a text string with the name
.B MULTI_CC_OPT .
.Np
Several macros are required for declaring and manipulating locks.
Define
.B multi_mutex_t
as the datatype to be used for lock variables.
If the locks themselves must be allocated dynamically, use a pointer type,
and define
.B multi_alloc_lock(a)
to allocate a lock and
.B multi_free_lock(a)
to free one.
If locks are declared statically, define these as 
.B 0 .
Define
.B multi_reset_lock(a)
to initialize or reinitialize a lock,
.B multi_lock(a)
to reserve a lock,
and
.B multi_unlock(a)
to release a lock.
.Np
Define
.B SHARED_FILE_OBJS
if the system allows a file opened by one process to be used by another, and
.B UNSHARED_FILE_OBJS
if not.
If you are uncertain,
.B SHARED_FILE_OBJS
is a good guess and will be validated later by the test package.
.Np
Define
.B FIRST_SHARED_FD
and
.B LAST_SHARED_FD
to give a range of file descriptors that are always shared
regardless of the general rule.
For example, standard output and standard error
are always safely shared because they are created
by an ancestor of all the processes in the program and because SR
flushes output buffers after every write.
If these are the only files,
.B FIRST_SHARED_FD
and
.B LAST_SHARED_FD
should be
\fB1\fP and \fB2\fP respectively.
If you defined
.B SHARED_FILE_OBJS ,
define
.B FIRST_SHARED_FD
and
.B LAST_SHARED_FD
to be
.B 0
and
.B 255 ;
there is little penalty if
.B LAST_SHARED_FD
is too large.
.Np
Define
.B MAX_JOBSERVERS
to specify an upper limit on the number of concurrent processes.
This limit will include the implicit I/O process that is created if
.B UNSHARED_FILE_OBJS
is defined.
For maximum benefit, the limit should allow at least as many
user processes as the number of available processors.
.Np
Define
.B MY_JS_ID
as a macro that returns a process's index
(ranging from 0 to the number of concurrent processes).
If this is not easily available from the system, make
it a function call and add a routine for obtaining it to the
.B .c
file below.
.SH
File multi/xxxxx.c
.LP
This file is included by
.B ../rts/process.c ,
causing it to be incorporated in the SR runtime system.
It contains three C functions needed for porting MultiSR.
All functions must be provided.
.nr Np 0 1
.Np
.B "sr_init_multiSR()"
is called before any locks are allocated or initialized
and before any other function in this file is called.
It need not do anything, but may be used for any necessary
global initialization.
.Np
.B "sr_create_jobservers(code,n)"
creates
.B n
concurrent processes, each executing the
function
.B code(arg) ,
which never returns.
.B sr_create_jobservers
is itself assumed to disappear (not return), leaving only the
spawned
.B code
functions running.
The
.B "void *"
parameter
.B arg
can be any value;
it is uninterpreted by
.B code
but can be used to communicate a value with
.B sr_jobserver_first
(below).
.Np
.B "sr_jobserver_first(arg)"
need not do anything,
but may be used for error checking or per-process initialization.
It is called immediately by each instance of
.B code
spawned by
.B sr_create_jobservers .
.B arg
is the argument passed to that code.
.LP
Any other variables or functions needed by the port may be added to this file.
Initialization, if needed, can be performed in
.B sr_init_multiSR .
New external and function names should begin with
.B sr_ .
The runtime system function
.B sr_missing_children
may be used if the requested number of processes cannot be obtained;
the Dynix code provides an example of its use.
.SH
Testing the MultiSR Porting Primitives
.LP
The
.B multi
directory includes a program for testing the machine-dependent
primitives needed by MultiSR.
The
.B Makefile
is currently set up to build and run the test on the existing platforms;
it will need editing to work elsewhere.
The test program checks several things:
.DS
.IP \(bu 3n
It verifies that
.B SHARED_FILE_OBJS
work, if configured
.IP \(bu 3n
It exercises the locking primitives
.IP \(bu 3n
It allocates shared memory to pass data among processes
.IP \(bu 3n
It ensures that process indices can be obtained
.DE
.PP
The correct output of the test program is dependent on the number of
processes.
The program
.B mexpect.c
synthesizes a copy of what is expected, given a process count.
This synthesized output can be compared with the test program's
actual output to verify its correctness.
.SH
Building and Testing MultiSR
.LP
Return to the main SR directory and note that
.B srmulti.h
and
.B srmulti.c
are symbolic links.
Enable MultiSR by redirecting these to point to the two new files
.B multi/xxxxx.h
and
.B multi/xxxxx.c .
Also make any necessary
.B Makefile
or
.B Configuration
changes, such as to CFLAGS or LIBR definitions.
.PP
Type
.B "make sclean"
and then
.B "make"
to rebuild the system with MultiSR enabled.
Try building and running a few SR programs with the environment
variable SR_PARALLEL set to enable multiprocessing.
Finally, use
.B srv
to run the SR verification suite as described in the SR porting guide,
and be sure to run it at least twice (with SR_PARALLEL both set and unset).
.PP
The issue of debugging MultiSR is beyond the scope of this document,
but a couple of hints are worth mentioning.
The environment variable SR_DEBUG can be set to enable runtime tracing
of an SR program; details are contained in the file
.B rts/debug.h .
An annotated list of the locks used by MultiSR appears in the appendix of
.I "The SR Run-Time System Interface".
