[comp.sys.amiga.tech] CBM, Why did you make it so hard?

fullmer@dover.uucp (Glen Fullmer) (03/13/89)

I have a friend that has ported a generic graphics package to the Amiga.
He is uses the Aztec C compiler, and I use the Lattice compiler.  This means
that I cannot link my graphics program to his Aztec C graphics object library. 
So he thought he would fix that and create a graphics Amiga-shared library 
that anyone can call at run time.  That was fine.  After a number of weeks
trying to track down all that has been written about Amiga-shared libraries
(which is precious little) and "ripping off" as much public-domain code that 
he could find, has written a single-user-at-a-time graphics library.  I,
being as helpful as usual, suggested that a single-user-at-a-time graphics
library is not very useful, especially if one wants to write a generic
widowing system with his library (which I do).  I suggested that he rewrite
his graphics library so that it is re-executable and reentrant (being the
helpful person that I am).  Given that history, we have a number of questions:


1.  Why didn't the developers of the C compilers agree on a common linkable
    format?

2.  How does one make a generic package, like his graphics package, both
    reexecutable and reentrant?  Does it require specific register allocation
    via assembler programming?  Or is there an easier way? A different 
    global hook that can be used to hang environments on?

3.  How does one restore the environment, for example, after an interrupt?

4.  Is making a package that must retain context both over the life of a 
    call and the life of the session different than  "pure" residentable
    code?  If so, how are they different, and what are the coding 
    differences?

5.  How did CBM do this for their libraries?

6.  Why isn't it simpler?


Granted, a lot of this is probably very naive for some of you guys, but
where does one learn it?  References?

Thanks, in advance, for your support.  
-- 
  _____     _  "For a successful technology, reality must take precedence" 
 {____/    //  "over public relations, for Nature cannot be fooled."
      \   // _  __Richard P. Feynman, Appendix F of Shuttle Disaster Report
 {____/  </_</_/ / <_  {sun!sunburn | cadillac} !dover!fullmer   

jimm@amiga.UUCP (Jim Mackraz) (03/14/89)

In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes:
)1.  Why didn't the developers of the C compilers agree on a common linkable
)    format?

The culprit was Aztec, but at the time their linking speed was so much
quicker that nobody minded.  They also had different register conventions,
which will also haunt you.  Read the register conventions in both 
the RKM (pertains also to Lattice) and the Aztec manuals.

)2.  How does one make a generic package, like his graphics package, both
)    reexecutable and reentrant?  Does it require specific register allocation
)    via assembler programming?  Or is there an easier way? A different 
)    global hook that can be used to hang environments on?

Write it as an Exec library, in any language, respecting the register
parameter passing interface.  Each language then requires an interface
technique on the calling side, normally a link library.  New lattice
can use some #pragma files instead.  Create a .fd file, and then either
find tools to convert to link code or pragmas, or hand tool them.  The
technique is traditionally assembler stubs.  Examine the source to
Aztec's link libraries.

You will also need to do a library-side interface to get the parameters
out of registers, again compiler specific, but you only do this once,
for the compiler you implement the library with.  This is typically
done in assembler.

The key point is that the interface favors neither compiler: it is
for assembly language programming.  It is common practice for all
languages to translate in and out of that interface.

)3.  How does one restore the environment, for example, after an interrupt?

I don't follow.  Unless you are using interrupts as part of the package,
the system will restore you without your ever knowing.  All registers must
be saved/restored by an interrupt, since you could be in the middle of
anything.  If you want to write interrupts in C, you must have an
assembler interface that saves/restores all parameters you might
change.  See the Guru's Guide on Interrupts.

)4.  Is making a package that must retain context both over the life of a 
)    call and the life of the session different than  "pure" residentable
)    code?  If so, how are they different, and what are the coding 
)    differences?

Most system libraries don't have a notion of "session," that is, there is
typically no preserved state.  To do this, you would just introduce
a "handle" that is returned by InitSession() and passed to every pertinent
call (including EndSession()).  I guess you could say the OpenWindow() is
doing just that.  Think also about the model of a RastPort: it isn't
session-based, but it is a handle.  It has an Init() (although no Create()),
and no End().

)5.  How did CBM do this for their libraries?

Pretty much as I describe, except we have tools to go from .fd to
the lattice/amiga style interface links in amiga.lib.  Our C functions
have assembler stubs pulling parameters out of registers.  Non-conforming
vendors (such as Aztec and the Modula folks) have their own stubs, created
from the .fd's, which their applications link to for library interfaces.

)6.  Why isn't it simpler?

Actually, the stuff you are asking about is pretty "mechanical" once
you gather the pieces.  What is nowhere near as straightforward is
the traditional part of being re-entrant, which must be approached with
EXTREME CAUTION.

You can get bit in the butt by all sorts of things, many of which
show no problem during development time.  Hanging something off a
list that is shared, toggling a bit in a shared flags word, asking
most C compilers to return a structure, changing a string (overwriting
the null terminator), all this stuff can bite your butt.

Implementing a library is a hurdle, but reentrant code is a science.
I've heard some people argue that the technology of writing libraries
is best left a little arcane, so as to keep it out of the hands of
the naive.  I don't subscribe to this point of view, but I sympathize
with the intent.  Writing most shared code is HARD, and the bugs
can be very subtle.

)Granted, a lot of this is probably very naive for some of you guys, but
)where does one learn it?  References?

The best source is examples.  I have one, elib, on the fish disks somewhere.
It is written in Aztec, small model, and includes demonstrations of link
routines for Aztec.  It is not hard to add links for lattice callers.

I am also doing one up using all the new high-powered Lattice 5.0X stuff.

	jimm

-- 
Jim Mackraz, I and I Computing	   	"Like you said when we crawled down
{cbmvax,well,oliveb}!amiga!jimm          from the trees: We're in transition."
							- Gang of Four
Opinions are my own.  Comments are not to be taken as Commodore official policy.

shf@well.UUCP (Stuart H. Ferguson) (03/15/89)

+-- fullmer@dover.UUCP (Glen Fullmer) writes:
| I have a friend that has ported a generic graphics package to the Amiga.

Great!

| So he thought he would fix that and create a graphics Amiga-shared library 
| that anyone can call at run time.

Wonderful!

| 2.  How does one make a generic package, like his graphics package, both
|     reexecutable and reentrant?  Does it require specific register allocation
|     via assembler programming?  Or is there an easier way? A different 
|     global hook that can be used to hang environments on?

Gee.  It's really not as hard as all this.  The basic "trick" (if you can
call it that) is to organize the library interface so that it has no "global"
variables.  All the context and state information get stored in structures
that the client program (the one who opened the library) owns.  It gets
these strucutres either by calling functions in the library to allocate
(and free) them, or by allocating it itself and calling a library
function to initialize it.  Intuition uses the first strategy, and the
graphics library generally uses the second.

One you have all the state info in a big packet, the client just calls
the library and passes it this structure.  The library itself is just
a collection of code that operate on these "handles" or "objects."  It
isn't really so much a "trick" as it is a basic design feature of an
Amiga shared library.  Since it sounds like the ported graphics library
maintains state in global "session" variables, it may need re-designing
to make it fit the Amiga style of shared library.

Now, it turns out that there is a place to store "global" variables
in a shared library -- the library base structure.  A shared library
can have a normal set of "global" variables if they are initialized
at startup and never change, such as a pointer to SysBase.  But if
values may change during the use of the library, they need to be locked
with semaphores to prevent clashes when multiple tasks are calling the
same functions in the library.  This is why you must call LockIBase()
before looking at the window lists and other structures located in
IntuitionBase.  Locking protocols are no fun.

| 6.  Why isn't it simpler?

Aw, it's not that hard, really.

| Granted, a lot of this is probably very naive for some of you guys, but
| where does one learn it?  References?

Look at Jimm's "elib" on Fish disk 87 for how to make one.  Look at how
the calls to Intuition and other libraries are organized to see how
they maintain context for each client task.

Once you understand the basics its pretty straightforward.  Good luck!
-- 
		Stuart Ferguson		(shf@well.UUCP)
		Action by HAVOC

jesup@cbmvax.UUCP (Randell Jesup) (03/15/89)

In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes:
>that anyone can call at run time.  That was fine.  After a number of weeks
>trying to track down all that has been written about Amiga-shared libraries
>(which is precious little) and "ripping off" as much public-domain code that 
>he could find, has written a single-user-at-a-time graphics library.  I,

	There is an example library in the RKMs, and I think available in 
source code form somewhere.  There are libraries (source) on fish disks too.

>being as helpful as usual, suggested that a single-user-at-a-time graphics
>library is not very useful, especially if one wants to write a generic
>widowing system with his library (which I do).  I suggested that he rewrite
>his graphics library so that it is re-executable and reentrant (being the
>helpful person that I am).  Given that history, we have a number of questions:

	Most libraries should be reentrant.

>1.  Why didn't the developers of the C compilers agree on a common linkable
>    format?

	Ask them.  Lattice supports the Amiga Object File Format, Manx (I think)
preferred to use the same format they had used on other machines.  I think most
of the Modula writers use amiga object files, but I'm not sure.

>2.  How does one make a generic package, like his graphics package, both
>    reexecutable and reentrant?  Does it require specific register allocation
>    via assembler programming?  Or is there an easier way? A different 
>    global hook that can be used to hang environments on?

	The only real trick is to set up the global data pointer correctly,
and to use locking of shared global structures.  Most libraries have global
data in the librarybase, which you always get a pointer to at entry (in a6).
You don't have to though.  If you do a geta4() (check manx docs) at each
entrypoint, then you can access your globals.  Be careful with library routines
that may use globals (stdio is right out).  Use Semaphores for locking access
to structures in the base when needed (sometimes Forbid()/Permit() is 
reasonable, but please don't use it unless needed).

>3.  How does one restore the environment, for example, after an interrupt?

	Are you writing interrupt handlers??  I think they're well documented;
I highly advise NOT writing interrupt handlers in anything but ASM.

>4.  Is making a package that must retain context both over the life of a 
>    call and the life of the session different than  "pure" residentable
>    code?  If so, how are they different, and what are the coding 
>    differences?

	Your question isn't clear.  If by "context" you mean data, then yes,
many calls returns pointers (cookies) you have to pass back when doing other
calls (FileHandles, for example).  "Pure" code merely means there are no
hard references to globals, and thus can be invoked multiple times safely.
Most programs do this by either not having any globals or never modifying
any globals.  Some (ex: lattice with cres.o) do it by allocating the data
segments, and accessing them via a base pointer.

-- 
Randell Jesup, Commodore Engineering {uunet|rutgers|allegra}!cbmvax!jesup

papa@pollux.usc.edu (Marco Papa) (03/15/89)

In article <6279@cbmvax.UUCP> jesup@cbmvax.UUCP (Randell Jesup) writes:
>In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes:
>>3.  How does one restore the environment, for example, after an interrupt?
>
>	Are you writing interrupt handlers??  I think they're well documented;
>I highly advise NOT writing interrupt handlers in anything but ASM.

On this one I disagree. Carl Sassenrath clearly shows that it is not really
a big deal to write the interrupt handler in C (a least in MANX).  You just
have to know which registers have to be saved and what register does what
(so you can pass the values on the stack, Matt taught me this one).  MANX
provides the proper entry/exit routines (int_start, int_end). I don't know 
about Lattice, but I don't see how more difficult it could be.  The Guru's
Guide is definitely very helpful for the adventurous.  I wish the contents
of the Guide was part of the CBM docs.

-- Marco Papa 'Doc'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
uucp:...!pollux!papa       BIX:papa       ARPAnet:pollux!papa@oberon.usc.edu
 "There's Alpha, Beta, Gamma and Diga!" -- Leo Schwab [quoting Rick Unland]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

451061@UOTTAWA.BITNET (Valentin Pepelea) (03/15/89)

Glen Fullmer <fullmer@dover.uucp> wites in Message-ID: <913@dover.uucp>

> 1.  Why didn't the developers of the C compilers agree on a common linkable
>     format?

There is a common linkable object format, that is the one which Lattice C and
most assemblers use. Manx s Aztec C 4.0 apparently too will use the standard
format.

> 2.  How does one make a generic package, like his graphics package, both
>     reexecutable and reentrant?  Does it require specific register allocation
>     via assembler programming?  Or is there an easier way? A different
>     global hook that can be used to hang environments on?

This is as simple as specifying a switch on the command line for the Lattice
compiler. Basically, all data has to be allocated dynamically.

> 3.  How does one restore the environment, for example, after an interrupt?

The CPU automatically saves the current context on the supervisor stack when
an interrupt occurs. To restore the context, exit the interrupt processing
routine with an RTE. (return from exception)

> 4.  Is making a package that must retain context both over the life of a
>     call and the life of the session different than  "pure" residentable
>     code?  If so, how are they different, and what are the coding
>     differences?

Yes. To retain the context over a call simply save to current registers on the
user stack with the MOVEM A2-A6/D2-D7,-(A7) instruction, and restore it upon
return from the call with a MOVEM (A7)+,A2-A6/D2-D7.

> 5.  How did CBM do this for their libraries?

They used the Green Hills C compiler. All you have to do is use the Green
Lettuce C compiler.

> 6.  Why isn't it simpler?

It can t possibly simpler than this!

> Granted, a lot of this is probably very naive for some of you guys, but
> where does one learn it?  References?

Read your Lattice compiler manual, dammit. Other than that, get your hands
on the RKMs. The third edition if possible.

> Richard

Valentin

_________________________________________________________________________
"An  operating  system  without         Name: Valentin Pepelea
 virtual memory is an operating         Phone: (613) 233-1821
 system without virtue."                Bitnet: 451061@uottawa
                                        Usenet: Look at the header
         - Ancient Inca Proverb         Planet: Ontario!Canada!Earth

cmcmanis%pepper@Sun.COM (Chuck McManis) (03/16/89)

In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes:
>1.  Why didn't the developers of the C compilers agree on a common linkable
>    format?

Because they are different companies and have/had different priorities. 
Commodore/Amiga defined the "standard" object file format and documented
it in the developers documents. Lattice, being the "official" C compiler
for the Amiga used it. Aztec on the other hand already had an object 
module format that their code generators produced and it was simpler to
retain that and only wack the linker to produce Amiga standard executables.
This is a classic "Time to Market" trade-off. Anyway, that was the history.

>2.  How does one make a generic package, like his graphics package, both
>    reexecutable and reentrant?  Does it require specific register allocation
>    via assembler programming?  Or is there an easier way? A different 
>    global hook that can be used to hang environments on?

See the excellent Article by Carolyn Scheppner(sp?) in Amiga Mail on how
to make your code re-entrant. If you don't get Amiga Mail, see if you can
get back issues from CBM directly. (CATS, 1200 Wilson Dr, Westchester PA, 
19380) Generally, reexecutable requires only that you don't use uninitialized
variables or assume your globals are initialized to zeros, reentrant means
that you don't store state in global variables, and you arbitrate access
to those global variables that you have.

>3.  How does one restore the environment, for example, after an interrupt?

What environment? If you context is on the stack (local variables) then 
you just return. If you change things that affect other processes using 
your code at interrupt time, then you have a bug. (Generally, during
exception processing of any type one saves any values that your don't 
"own" like register contents, and then restore them before exit.)

>4.  Is making a package that must retain context both over the life of a 
>    call and the life of the session different than  "pure" residentable
>    code?  If so, how are they different, and what are the coding 
>    differences?

Generally "life of a call" context is stored in local variables on the 
stack. "Life of a session" is handled in one of two ways, the first is
to create a "session handle" when you first access the library. Do an
OpenLibrary() followed by a NewSession() call. The library may copy a
default context into the handle and then this handle is passed with 
all calls to the graphics library. In the case of the Amiga graphics
library the "session handle" is the RastPort structure. The other method
is to create a context in the task structure itself. This is much more
difficult but certainly possible. 

Optional info : Generally, if you create a session handle that is simply
a structure such as the RastPort in graphics.library, it has the advantage
of speed because the handle is the data. Unfortunately, it also has the
problem of compatibility later because changes to this structure will 
directly impact any program that "looks inside" of it. A common way 
around this problem is to return an index rather than the actual structure
to the program. This makes the handle opaque and allows the graphics
library to play around with the actual structure without breaking anyones
code. 

>5.  How did CBM do this for their libraries?

Well see the RastPort discussion above.

>6.  Why isn't it simpler?

Programming on real computers is always harder than programming on 
microcomputers. The flexibility and usefulness of a multitasking
kernel puts certain restrictions on what you can do but opens up 
the capabilities of what you can accomplish. Anything can be 
simplified by making assumptions about what will and will not
go on, but those same assumptions limit everything else as well.
If you make the assumption that everyone using the graphics library
will have a 2 bitplane, 640 X 200 pixel screen open, then all sorts
of stuff becomes easier. You always know where to clip lines, or 
how many color registers you will be using etc. But that limits
the other programmer who wants to use the same library with a 
704 X 440 screen. 

>Granted, a lot of this is probably very naive for some of you guys, but
>where does one learn it?  References?

Generally global concepts such as the differences between reentrant 
and reexecutable are taught in Universities. In terms of schools I
am familiar with, 2 year programs will teach sort of a mechanical 
programming style. Using canned algorithims to solve specific problems.
Whereas 4 year schools will tend to teach concepts and how to devise
an algorithim. 

Good system design is something that comes from practice. [Writing this
graphics library should be some good experience!] Whenever you write 
something, no matter how long you have been at it, you should go back
and look at it a year later. At that time all of it's flaws and deficiencies
will be glaringly apparent. Learn from that, take note of areas you 
didn't consider. Then add those questions to the repetoire of the ones
you ask yourself when starting a new design. It's a never ending quest
but after while the questions will be quite esoteric. [Example : What
if I wanted to port this to a 12bit machine with only 4010 terminals for
output, how would that affect the design?] The answers need not be 
completely relevent but they should point out weaknesses in the design.


--Chuck McManis
uucp: {anywhere}!sun!cmcmanis   BIX: cmcmanis  ARPAnet: cmcmanis@sun.com
These opinions are my own and no one elses, but you knew that didn't you.

fullmer@dover.azsps.mot.com (Glen Fullmer) (03/23/89)

In article <3623@amiga.UUCP> jimm@cloyd.UUCP (Jim Mackraz) writes:
>)3.  How does one restore the environment, for example, after an interrupt?
>
>I don't follow.  Unless you are using interrupts as part of the package,
>the system will restore you without your ever knowing.  All registers must
>...
>)4.  Is making a package that must retain context both over the life of a 
>)    call and the life of the session different than  "pure" residentable
>)    code?  If so, how are they different, and what are the coding 
>)    differences?
>
>Most system libraries don't have a notion of "session," that is, there is
>typically no preserved state.  To do this, you would just introduce
>a "handle" that is returned by InitSession() and passed to every pertinent
>call (including EndSession()).  I guess you could say the OpenWindow() is
>doing just that.  Think also about the model of a RastPort: it isn't
>session-based, but it is a handle.  It has an Init() (although no Create()),
>and no End().
>Jim Mackraz, I and I Computing	   	"Like you said when we crawled down
>{cbmvax,well,oliveb}!amiga!jimm          from the trees: We're in transition."
>							- Gang of Four

I want to thank all those who responded to my original inquiry about
Amiga run-time libraries.  It was very helpful and enlightening.
Howard Anderson, whose generic graphics system originally elicited the
questions, also appreciated the comments.

There was some misunderstanding concerning my reference to interrupts.
No, he is not using interrupts as part of the package.  The essential
question was: How does one maintain session context (reentrancy) in an
Amiga run-time library when multiple sessions use a set of changing
globals?  There was a couple of suggestions: (1) To require the user
to create a session context pointer (like Raster-Pointer in Intuition)
or (2) to maintain a session context table within the library. He had
some additional comments about his preference to (2) and questions
about methods to achieve it.  Any other methods that I missed?  As it
is more convenient for me to post news than he, I am including his
comments and questions:


OK,  let's try a concrete example.  I have a graphics system that
is a collection of library routines that can  be  called  by  the
user.   The  user  must call "gstart" first.  He must call "gend"
when finished using the library.   There  is  a  function  called
"gdraw"  that  the  user  can  call  to  draw a line.  There is a
function called "gset" that allows one to change modes,  such  as
the  drawing  color,  that  will  be  used for subsequent "gdraw"
calls.  There are of course a lot of other callable functions  in
the library. 

I  don't  want  the  user  to  have  to  bother with setting up a
graphics structure for the graphics system.   I  don't  want  the
user  to  have to bother with passing some structure pointer that
is irrelevant to  doing  graphics.   Since  the  graphics  system
library  is  the  object that NEEDS the structure for saving mode
data, it should  do  its  own  allocation,  pointer-finding,  and
deallocation  of  its  task-dependent  global/persistent storage.
The purpose of putting a generic graphics system up on the  Amiga
is to make it easier for the user to use. 

So  the  problem  on  the  Amiga  is how to set up a library with
task-dependent   global/persistent   storage   that,   after    a
time-share  interrupt  or  other  interrupt,  remembers where its
particular task-dependent global/persistent storage area is  when
it  again  receives  control  from  the scheduler.  I so far have
placed  all  of  the  global/persistent  storage  items  in   one
structure.   I  can  use  MemAlloc  to  allocate  storage for the
area.  The problem is how to  preserve  that  pointer  through  a
time-share   interrupt   (or   any  other  sort  of  asynchronous
interrupt that takes control away from me, runs  something  else,
then returns to me.)

During   an  interrupt,  70  bytes  are  pushed  onto  the  stack
representing  all  of  the  data  registers,  address  registers,
status   register,  and  the  program  counter.   When  you  find
yourself back in your code after an  interrupt,  those  70  bytes
are  all  you  really  know.   (You  wouldn't even know where the
stack was unless one of the registers was dedicated to holding  a
pointer  to  it.)   Clearly,  the best way to save a pointer to a
task-dependent global/persistent storage area is to DEDICATE  one
of  the  registers,  say A3, to always holding the pointer to the
task-dependent  global/persistent  storage  area.   This  is  how
Multics  achieved  what  I  consider  to  be  true  reentrancy  -
reentrancy with per process global/persistent data - and  spoiled
me  beyond  words...   (See page 68 of "Timesharing System Design
Concepts",  Richard  Watson,  McGraw-Hill,  1970.   The   Multics
operating  system,  by  convention, dedicates a hardware register
called a "link pointer" to the "linkage information",  i.e.,  the
task-dependent   storage.   This  is  always  restored  after  an
interrupt so the task ALWAYS knows  where  its  own  storage  is.
All  programs are shared.  New storage for task-dependent data of
a called function is always allocated whenever that  function  is
first  called by a new process.  On Multics, the operating system
takes care of all reentrancy/data mechanics.)

If I had written the graphics  system  in  assembly  language,  I
could  have  dedicated  a register to point to the task-dependent
storage.  This register would have to be reserved  and  untouched
in  the  user  program that calls the library function of course.
There does not appear to be a way  to  do  that  if  the  calling
program is written in C. 

My  best  guess  at  present  is  that  to  fake  reentrancy with
per-process global/persistent data, for a program written  in  C,
where   the   shared  code  is  responsible  for  allocating  and
deallocating its own global/persistent storage automatically,  is
to  use  FindTask  with  a  null argument to get a pointer to the
current task, then use tc_UserData as a storage location for  the
pointer  to  the task-dependent global/persistent storage area --
IF the Amiga designers meant for tc_UserData to be  used  by  ME.
Did  they?  My documentation says "per task data" but doesn't say
WHOSE.  (I see that it is NULL from within a CLI task.)

Now even if I have that, I don't exactly like  it.   My  "gstart"
routine  would  have  to call Forbid(), then FindTask so that the
pointer could be stored in tc_UserData.  Then  I  would  have  to
store  the  pointer in a local automatic variable for further use
within "gstart" then I'd have to call Permit().   That  would  be
OK  but EACH routine in the graphics library, subsequently called
by the user program,  would  have  to  call  FindTask  to  locate
tc_UserData  and  store the pointer in a local automatic variable
so that graphics mode  data  could  be  referenced  through  that
structure pointer.  What is the overhead for a FindTask?

Now  assuming  the  Commodore/Amiga  people  really don't want me
mucking around with tc_UserData, I could define a 2xn  vector  in
static  storage  that  would  contain  the address of the current
task block as determined by  a  FindTask  call  paired  with  the
address  of  the  corresponding  global/persistent  storage area.
Then EACH routine in the graphics  library  would  have  to  call
FindTask,  to  get the task block pointer, then search the vector
for  that  pointer  which  would   yield   the   index   of   the
corresponding  address  of  global/persistent  storage so that it
could be placed in a local automatic variable.  I don't like  the
idea  of  having  to  search  that vector even though I know some
great hash algorithms. 

So I really don't like any of the alternatives very well.   Am  I
missing something?

I  figured  out  how to link to a library and pass floating point
variables.  I know how to DO  reentrant  code  with   per-process
global/persistent  data.   I'm  just  not  sure  if  there  is an
efficient way to do it in C on the  Amiga.   I  definitely  don't
like  the  idea  of  the  user  having  to pass a pointer to each
routine he calls as is done with RPort. 

All of this began of course with  the  fact  that  the  Manx  and
Lattice  compilers  cannot  link each other's object segments.  I
had originally intended to create a library  of  object  segments
for  linking  in  with  the  user  program.   Most  of the larger
machines I've worked with such as the Philco  2000,  IBM  360/85,
3094-Q,  Honeywell  6080,  Multics  and  even Sun and Apollo have
standard linkers on the system provided as part  of  the  system.
(At  least  I've always thought they were provided as part of the
system.)  On smaller machines I've worked on such  as  the  Apple
II  and  C-64,  the  linkers  appear  to  have  been specified by
third-party  software  suppliers.   The  shared  library   method
however  seems to be a better answer than a link library if I can
get it to work the way I want it to. 

I like the Amiga  a  lot.   Don't  misconstrue  any  of  this  as
criticism  of the design.  Commodore/Amiga did a rather brilliant
job.  I am just trying to understand the Amiga conventions  while
attempting  to  implement some software whose interface I want to
conform to some of my conventions. 

Is there an easier way to do what I want to do?

  _____     _  "For a successful technology, reality must take precedence" 
 {____/    //  "over public relations, for Nature cannot be fooled."
      \   // _  __Richard P. Feynman, Appendix F of Shuttle Disaster Report
 {____/  </_</_/ / <_  {sun!sunburn | cadillac} !dover!fullmer   
-- 

utoddl@ecsvax.UUCP (Todd M. Lewis) (03/23/89)

I have not tried this, but if you just want to save
the programmer some typing, leaving out one parameter
in your function calls (from the Amiga perspective)
you could do something like:

#define glibcall(a,b,c) glibcal(RportEquiv,a,b,c)

Your user would have to make an initial call to set up
this context variable and one more at the end to reclaim
system resources consumed by the first one, but there is
_MUCH_ less overhead this way than having each routine in
your library do a FindTask() etc.  It is just syntactic
sugar, but it might make it taste more like what you want.

Note, however, that Amiga programmers have gotten quite
used to the pass-me-your-context-when-you-call-me way of
working with libraries.  It may not be such a big win 
after all.  Especially when someone eventually has multiple
contexts within one program.
  _____        
    |      Todd M. Lewis            Disclaimer: If you want my employer's
    ||\/|  utoddl@ecsvax.uncecs.edu             ideas, you'll have to
    ||  ||                                      _buy_ them. 
     |  ||     
         |___   (Never write a program bigger than your screen.)

ditto@cbmvax.UUCP (Michael "Ford" Ditto) (03/24/89)

In article <933@dover.azsps.mot.com> fullmer@dover.UUCP (Glen Fullmer) writes:
>The essential
>question was: How does one maintain session context (reentrancy) in an
>Amiga run-time library when multiple sessions use a set of changing
>globals?  There was a couple of suggestions: (1) To require the user
>to create a session context pointer (like Raster-Pointer in Intuition)
>or (2) to maintain a session context table within the library.

Since the reason given for prefering (2) was to eliminate the need for
a user to carry around this useless (as far as the program can see)
pointer, why not solve all the problems mentioned by hiding that
detail in the link part of the library (the part that would normally
just jsr to the shared library routine).  Since this is already done
anyway with the library base pointer itself, just make this "context"
pointer a "hidden" variable as well.

>OK,  let's try a concrete example.  I have a graphics system that
>is a collection of library routines that can  be  called  by  the
>user.   The  user  must call "gstart" first.  He must call "gend"
>when finished using the library.   There  is  a  function  called
>"gdraw"  that  the  user  can  call  to  draw a line.

/* simplified code -- this is linked with the user's program:  */

static struct Library *__glibbase; /* library base */
static void *__context;		/* "session handle" for this task */

int gstart()
{
    if (!__glibbase)		/* open the library */
	__glibbase = OpenLibrary("glib.library", 0);
    if (__context)		/* only one session at a time in this task */
	return -1;
    __context = _gstart();	/* _gstart is the "glue" function */
    return __context != 0;
}

void gend()
{
    _gend(__context);		/* another glue function */
    __context=0;		/* we could now gstart() again if desired */
    /* perhaps CloseLibrary(...) */
}

void gdraw(arg1, arg2)
{
    _gdraw(__context, arg1, arg2); /* another glue function */
}


With that method, you get the best of both worlds:

	+ The glib.library itself is effecient.
	+ Users have a convenient interface.
	+ Users can directly call the _functions if they want to
	  have multiple sessions.


>So  the  problem  on  the  Amiga  is how to set up a library with
>task-dependent   global/persistent   storage   that,   after    a
>time-share  interrupt  or  other  interrupt,  remembers where its
>particular task-dependent global/persistent storage area is  when
>it  again  receives  control  from  the scheduler.  I so far have
>placed  all  of  the  global/persistent  storage  items  in   one
>structure.   I  can  use  MemAlloc  to  allocate  storage for the
>area.  The problem is how to  preserve  that  pointer  through  a
>time-share   interrupt   (or   any  other  sort  of  asynchronous
>interrupt that takes control away from me, runs  something  else,
>then returns to me.)

You're trying real hard to find a way to do things the hard way.  :-)

Just don't worry about interrupts.  You'll never know they happened.
If you have the session pointer in an automatic (stack or register)
variable, you can access it as you like, even if another task is
also running your library.  Each task has its own stack and registers,
and you don't need to worry about what happens to yours during or
after an interrupt or task switch.

>During   an  interrupt,  70  bytes  are  pushed  onto  the  stack
>representing  all  of  the  data  registers,  address  registers,
>status   register,  and  the  program  counter.   When  you  find
>yourself back in your code after an  interrupt,  those  70  bytes
>are  all  you  really  know.

Well, those bytes + everything else you ever knew.  None of your
data will change during an interrupt or task switch unless you
change them.

>   (You  wouldn't even know where the
>stack was unless one of the registers was dedicated to holding  a
>pointer  to  it.)

No, you could keep a copy of it in any register you like.  There
are no "dedicated" registers as far as context switches go.  ALL
your registers will be saved.  And besides, IT DIDN'T MOVE, so
you know where it is just as well as you did before the interrupt.

>   Clearly,  the best way to save a pointer to a
>task-dependent global/persistent storage area is to DEDICATE  one
>of  the  registers,  say A3, to always holding the pointer to the
>task-dependent  global/persistent  storage  area.

You can do this if you like; but nobody but your code will ever
know or care.  You might as well just put it in an automatic
variable, any one will do.  Dedicating a register to hold that
pointer whenever in your library code is just another way of
passing a parameter to all the functions in your library.  You
still need to load that pointer when your library is called
from the user program, and restore the registers when you return.

>   This  is  how
>Multics  achieved  what  I  consider  to  be  true  reentrancy  -
>reentrancy with per process global/persistent data - and  spoiled
>me  beyond  words...   (See page 68 of "Timesharing System Design
>Concepts",  Richard  Watson,  McGraw-Hill,  1970.   The   Multics
>operating  system,  by  convention, dedicates a hardware register
>called a "link pointer" to the "linkage information",  i.e.,  the
>task-dependent   storage.   This  is  always  restored  after  an
>interrupt so the task ALWAYS knows  where  its  own  storage  is.

ALL registers are restored after an interrupt (in Exec and any
other OS I've used; I really can't imagine that Multics is any
different).  I think what you are asking for has nothing to do
with interrupts; you just want this magic pointer to always be
there for you.  The way to make this happen is to pass it in
as an argument to all your functions.  The user doesn't have to
do this, you could do it at the entry to the library or in the
glue routines that the user links with.

 [ description of using FindTask(0) to recognize a previous
   caller and search for its state information ]

This would work also, but it seems that you are stuck on the idea
of closely tying a "session" to a task, limiting each task to
only one active session.  Why go to the trouble of "True
Reentrancy" without allowing multiple sessions within a task?  If
you want to provide a convenient interface, that's great, but keep
the basic structure as flexible and efficient as possible, and put
the convenience as a layer on top.  That way when a user comes
along who prefers flexibility to convenience, your library won't
be in the way.

>Is there an easier way to do what I want to do?

I think you're trying to do many unnecessary things.  It's obvious
that you know about the complexities of reentrant programming, but
I think you must have learned them on a very strange system.  :-)
Perhaps it would help to get the source code to a library and see
how things are typically done.
-- 
					-=] Ford [=-

"The number of Unix installations	(In Real Life:  Mike Ditto)
has grown to 10, with more expected."	ford@kenobi.commodore.com
- The Unix Programmer's Manual,		...!sdcsvax!crash!kenobi!ford
  2nd Edition, June, 1972.		ditto@cbmvax.commodore.com