Article 149499 of comp.os.vms:
	Last week i tried to implement MODULA2 coroutines on VMS/VAX by using
	setjmp/longjmp and get that strange fault at runtime.

The VAX C library (and probably the DEC C library) uses uses the VMS condition
signaling mechanism (not to be confused with Unix signals!) to implement
longjmp.  Consider what needs to happen to execute a longjmp():

	1.  The stack must be unwound to the stack frame that was current at
		the point that setjmp() was executed;
	2.  Control must be transfered to the appropriate point, as saved in
		the jump buffer by setjmp().

Many C implementations implement (1) trivially - they simply change the stack
pointer, immediately discarding all intermediate frames.  This would not be
acceptable under VMS since there can be condition handlers declared by some of
those intermediate frames, and the VMS procedure calling standard requires
that they be executed.

Fortunately, VMS already provides a means to accomplish (1) through the signal
handling mechanism.  The C RTL's implement longjmp() in a couple of steps:

	(1)  The C RTL declares a signal handler way up at the top of the
		stack.
	(2)  longjmp() signals condition LONGJMP.  No user-defined handler is
		supposed to attempt to handle this condition.
	(3)  The LONGJMP condition propagates all the way up the stack to the
		C RTL handler.  It grabs it, looks at jump buffer (which was
		presumably left at some known place by longjmp()), and uses
		$UNWIND to properly unwind the stack to the appropriate frame
		(giving any intermediate handlers the opportunity to respond
		to the "unwinding" condition that they will be invoked with).
	(4)  Finally, it causes control to be transfered to the appropriate
		point.  (It actually does this as part of the $UNWIND call.)

The details get more complicated, though I can't reconstruct them at the
moment.  But this is the basic idea.

	The main idea is that coroutine is a co-process that has separate
	context(stack and registers) and commonn with others co-routines
	global variables. Coroutine is not scheduled by kernel - control flow
	is switched by special procedure. And me, as a modula2 compiler
	developer, tried to implement this proceedure. On ALL the others
	platforms we did setjmp, patch the stack-pointer(r13 in this case?)
	with separate coroutine stack, and then we did longjmp on patched
	jmp_buf. And for VMS/VAX it says %SUBJ%.

This approach cannot work on VMS.  Because of the way the implementation is
done, a longjmp() can only work if actually directed to a frame "further up
the stack".  (Note that what you are attempting to do is most definitely *not*
supported by the ANSI definition of setjmp() and longjmp().  It happens to
work on other implementations, which don't have as rich a procedure call
semantics, but on VMS - forget it.)

	I have absolutely no idea what kind of fault may be cause by such a
	stupid procedure as longjmp at all. Can anyone help me? I would
	greatly appreciate it!

	BTW, normal usage of setjmp/longjmp works fine, we did implement
	MODULA2 exceptions in this way and they work. HOW can that longjmp
	distinguish one case from another? It's unbelievable!

	Coroutine's stack was allocated both on main stack and as a static
	variable-buffer - it makes no difference.

What's probably happening is that your new stack starts with a frame with no
backward frame pointer, and no condition handler.  When longjmp() signals
LONGJMP, VMS's search for a handler works up frame by frame until it reaches
the top of your allocated stack, at which point it calls the last-chance
handler, which reports the error you see and, since LONGJMP is signalled with
a Fatal severity (since the program mustn't continue by returning from the
longjmp() call whatever may go wrong!), terminates your image.

You *might* be able to fake things out by making sure top frame of each stack
you create points back to some frame on the system stack.  However, at least
on VAX'es, it's really much simpler to just write a few lines of MACRO code
and do exactly what you need, rather than playing around trying to sneak
things past some code that is doing its best to make sure you "play by the
rules".  Attached below is an example of such code.  I extracted this from a
large system, so it's full of stuff specific to that system - but you should
be able to get the ideas from here.

Note:  On an Alpha, this probably isn't quite as easy!
							-- Jerry


	.TITLE		TASKSUPPORT - Support routines for tasking
	.IDENT		'V01-000'
; EDITLEVEL=08

	.PSECT		SUPPORT_CODE,CON,EXE,GBL,PIC,SHR,RD,REL,NOWRT
	.SUBTITLE	Get caller's FP
;
;	FRAME *
;	GetFP()
;
; Return to caller the the value of his frame pointer.

.ENTRY	GetFP,^M<>
	MOVL		12(FP),R0		;R0 <- saved FP
	RET					;Easy enough























	.SUBTITLE	Tasking support - swap tasks
;
; This function is used only in conjunction with the tasking code.
;
; The TASK datatype:
;	struct task
;	{	RQUEUE	rqueue;		/* Links			*/
;		RQUEUE	msgs;		/* Pending messages		*/
;		LONG	flags;
;	#define  TASK$V_INIT	0	/* Task is initialized		*/
;	#define  TASK_INIT	BITVAL(TASK$V_INIT)
;	#define  TASK$V_ASLEEP	1	/* Task is asleep		*/
;	#define  TASK_ASLEEP	BITVAL(TASK$V_ASLEEP)
;	#define  TASK$V_TIMEDOUT					 \
;				2	/* Task timeout completed	*/
;	#define  TASK_TIMEDOUT						 \
;				BITVAL(TASK$V_TIMEDOUT)
;		FRAME	*FPsave;	/* Saved Frame Pointer		*/
;		void	(*func)();	/* Initial function		*/
;		ADDR	alist;		/* Initial argument list	*/
;		ADDR	stack;		/* Task's stack			*/

	.PSECT		TASK,ABS

next:	.BLKL			;Forward (relative) queue link
last:	.BLKL			;Backward (relative) queue link
mnext:	.BLKL			;Forward (relative) queue link for msgs
mlast:	.BLKL			;Backward (relative) queue link for msgs
flags:	.BLKL			;Flags
 TASK$V_INIT = 0		;Task is initialized
 TASK_INIT = 1@TASK$V_INIT
 TASK$V_ASLEEP = 1		;Task is asleep
 TASK_ASLEEP = 1@TASK$V_ASLEEP
 TASK$V_TIMEDOUT = 2		;Task timeout completed
 TASK_TIMEDOUT = 1@TASK$V_TIMEDOUT
FPsave:	.BLKA			;Saved Frame Pointer
func:	.BLKA			;Initial function
alist:	.BLKA			;Initial argument list
stack:	.BLKA			;Process stack

old = 4				;Old state (to be saved)
new = 8				;New state (to be started)

.EXTERNAL	TaskExit

	.PSECT		SUPPORT_CODE,CON,EXE,GBL,PIC,SHR,RD,REL,NOWRT
;
;	void
;	TaskSwap(old,new)
;	TASK *old;
;	TASK *new;
;
; The current state is saved in the old structure; the current state is
; registers R2-R11, and FP.  Then the current machine state is set up from
; the new structure.
;
; TaskSwap() does not return; rather, it effectively suspends within the
; current task, starting the new one.  When the current process is later
; re-started (i.e., appears as new), TaskSwap() will return.
;
; TaskSwap() is NOT AST reentrant and must not be called from AST level - if
; it is, the process will remain at AST level until old is again current.

.ENTRY	TaskSwap,^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>

; First, save the old state.  R2-R11 are already saved by our entry mask.
;
	MOVL	old(AP),R1		;R1->old state structure
	MOVL	FP,FPsave(R1)		;Save FP

; Now start the new state.  There is a special case here:  If flag bit INIT
; in the new state structure is set, this is the initial startup of the
; process.
	MOVL	new(AP),R1		;R1->new state structure
	MOVL	FPsave(R1),FP		;Restore FP
	BBCC	#TASK$V_INIT,flags(R1),10$
					;Skip if NOT initializing
; Task initialization.  NewTask() initialized FPSave to point to an appropri-
; ate base frame; on startup, we must point SP to the same place.  Then all
; we need do is call the initial function, and get rid of the task if it ever
; returns.
	MOVL	FP,SP			;SP at start
	MOVL	alist(R1),R2		;Get arglist address
	MOVL	func(R1),R3		;Get function address
	CALLG	(R2),(R3)		;Call user's code
	CALLS	#0,TaskExit		;User returned, terminate task
	BPT				;Can't get here
10$:
	RET				;Go

	.END


