[m-dev.] for review: support catching exceptions in call_engine()

Fergus Henderson fjh at cs.mu.OZ.AU
Thu Sep 16 02:04:21 AEST 1999


Hi,

If there's no objections, I'll commit this sometime soon.

----------

Estimated hours taken: 16

Add support for Mercury exception handling to call_engine().
Currently that support is not yet used, but the idea is to eventually
use this to e.g. allow Mercury exceptions to be automatically
converted into C++ exceptions when you export a Mercury procedure
to C++.

runtime/mercury_stacks.h:
	Add stuff for exception handling, adapted from code in
	library/exception.m, for use by mercury_engine.c.

runtime/mercury_engine.c:
	Rename call_engine as MR_call_engine().
	Add a new parameter `bool catch_exceptions', and change
	the result type from `void' to `Word *'.
	If the catch_exceptions is true, then it installs an exception handler
	to catch Mercury exceptions and returns the Mercury exception thrown,
	if any.

runtime/mercury_engine.h:
	Add a new field e_exception to the MercuryEngine structure.
	This thread-local variable is used to hold the Mercury exception
	object thrown, if any, when call_engine_inner() returns via longjmp().

	Update the prototype for call_engine() to match its new
	MR_call_engine() interface.

runtime/mercury_bootstrap.h:
	Add a #define of call_engine() that calls MR_call_engine(),
	for bootstrapping.

runtime/mercury_wrapper.c:
runtime/mercury_thread.c:
compiler/export.m:
	Call MR_call_engine() rather than call_engine().

runtime/mercury_wrapper.c:
	Ensure that we do not clobber the value of MR_curfr in do_interpreter,
	since it may be needed for the exception handler frame in
	MR_call_engine() if do_interpreter were invoked via
	MR_call_engine(ENTRY(do_interpreter), TRUE).

library/exception.m:
	Simplify the code for the different builtin_catch procedures by
	using the MR_create_exception_handler() macro defined in
	mercury_stacks.h (this avoids quite a bit of code duplication
	in the old code).
	Modify the code for builtin_throw to allow the exception handler
	to be C code; in that case, we save the exception object
	in the e_exception field of the MercuryEngine and then jump to
	the exception handler using longjmp().

Workspace: /home/mercury0/fjh/mercury
Index: compiler/export.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/export.m,v
retrieving revision 1.26
diff -u -r1.26 export.m
--- export.m	1998/11/20 04:07:35	1.26
+++ export.m	1999/09/15 15:28:38
@@ -138,15 +138,15 @@
 	%	restore_registers();
 	%	<copy input arguments from Mercury__Arguments into registers>
 	%		/* save the registers which may be clobbered      */
-	%		/* by the C function call call_engine().          */
+	%		/* by the C function call MR_call_engine().       */
 	%	save_transient_registers();
 	%	{
 	%	Declare_entry(<label of called proc>);
-	%	call_engine(ENTRY(<label of called proc>);
+	%	(void) MR_call_engine(ENTRY(<label of called proc>), FALSE);
 	%	}
 	%		/* restore the registers which may have been      */
 	%		/* clobbered by the return from the C function    */
-	%		/* call_engine()				  */
+	%		/* MR_call_engine()				  */
 	%	restore_transient_registers();
 	% #if SEMIDET
 	%	if (!r1) {
@@ -199,9 +199,9 @@
 				"\t{\n\tDeclare_entry(",
 				ProcLabelString,
 				");\n",
-				"\tcall_engine(ENTRY(",
+				"\t(void) MR_call_engine(ENTRY(",
 				ProcLabelString,
-				"));\n\t}\n",
+				"), FALSE);\n\t}\n",
 				"\trestore_transient_registers();\n",
 				MaybeFail,
 				OutputArgs,
Index: library/exception.m
===================================================================
RCS file: /home/mercury1/repository/mercury/library/exception.m,v
retrieving revision 1.1
diff -u -r1.1 exception.m
--- exception.m	1999/08/31 12:55:43	1.1
+++ exception.m	1999/09/15 15:58:08
@@ -463,8 +463,6 @@
 
 :- pragma c_code("
 
-Define_extern_entry(exception_handler_do_fail);
-
 /*
 ** MR_trace_throw():
 **	Unwind the stack as far as possible, until we reach a frame
@@ -546,8 +544,6 @@
 	return NULL;
 }
 
-enum CodeModel { MODEL_DET, MODEL_SEMI, MODEL_NON };
-
 /* swap the heap with the solutions heap */
 #define swap_heaps()							\\
 {									\\
@@ -564,29 +560,6 @@
 	MR_solutions_heap_zone = swap_heaps_temp_hp_zone;		\\
 }
 
-/*
-** Define a struct for the framevars that we use in an exception handler
-** nondet stack frame.  This struct gets allocated on the nondet stack
-** using mkpragmaframe().
-*/
-typedef struct Exception_Handler_Frame_struct {
-	Word code_model;
-	Word handler;
-	Word *stack_ptr;
-#ifdef MR_USE_TRAIL
-	Word trail_ptr;
-	Word ticket_counter;
-#endif
-#ifndef CONSERVATIVE_GC
-	Word *heap_ptr;
-	Word *solns_heap_ptr;
-	MemoryZone *heap_zone;
-#endif
-} Exception_Handler_Frame;
-
-#define FRAMEVARS \\
-	(((Exception_Handler_Frame *) (MR_curfr - MR_NONDET_FIXED_SIZE)) - 1)
-
 Define_extern_entry(mercury__exception__builtin_catch_3_0); /* det */
 Define_extern_entry(mercury__exception__builtin_catch_3_1); /* semidet */
 Define_extern_entry(mercury__exception__builtin_catch_3_2); /* cc_multi */
@@ -619,7 +592,6 @@
 ** MR_MAKE_PROC_LAYOUT(entry, detism, slots, succip_locn, pred_or_func,
 **			module, name, arity, mode)                         
 */
-/* do we need one for exception_handler_do_fail? */
 
 MR_MAKE_PROC_LAYOUT(mercury__exception__builtin_throw_1_0,
         MR_DETISM_DET, BUILTIN_THROW_STACK_SIZE, MR_LONG_LVAL_STACKVAR(1),
@@ -670,14 +642,13 @@
 #endif
 	init_entry(mercury__exception__builtin_throw_1_0);
 	init_label(mercury__exception__builtin_throw_1_0_i1);
-	init_entry(exception_handler_do_fail);
 BEGIN_CODE
 
 /*
 ** builtin_catch(Goal, Handler, Result)
 **	call Goal(R).
 **	if succeeds, set Result = R.
-**	if throws an exception, call Handler(Result).
+**	if throws an exception, call Handler(Exception, Result).
 **
 ** This is the model_det version.
 ** On entry, we have a type_info (which we don't use) in r1,
@@ -693,45 +664,12 @@
 #endif
 Define_entry(mercury__exception__builtin_catch_3_2); /* cc_multi */
 	/*
-	** Create a handler on the stack with the special redoip
-	** of `exception_handler_do_fail' (we'll look for this redoip
-	** when unwinding the nondet stack in builtin_throw/1),
-	** and save the stuff we will need if an exception is thrown.
-	*/
-	mkpragmaframe(""builtin_catch/3 [model_det]"", 0,
-		Exception_Handler_Frame_struct,
-		ENTRY(exception_handler_do_fail));
-	FRAMEVARS->code_model = MODEL_DET;
-	FRAMEVARS->handler = r3;		/* save the Handler closure */
-	FRAMEVARS->stack_ptr = MR_sp;	/* save the det stack pointer */
-#ifndef CONSERVATIVE_GC
-	/* save the heap and solutions heap pointers */
-	FRAMEVARS->heap_ptr = MR_hp;
-	FRAMEVARS->solns_heap_ptr = MR_sol_hp;
-	FRAMEVARS->heap_zone = MR_heap_zone;
-#endif
-#ifdef MR_USE_TRAIL
-	/* save the trail state */
-	MR_mark_ticket_stack(FRAMEVARS->ticket_counter);
-	MR_store_ticket(FRAMEVARS->trail_ptr);
-#endif
-
-	/*
-	** Now we need to create another frame.
-	** This is so that we can be sure that no-one will hijack
-	** the redoip of the special frame we created above.
-	** (The compiler sometimes generates ``hijacking'' code that saves
-	** the topmost redoip on the stack, and temporarily replaces it
-	** with a new redoip that will do some processing on failure
-	** before restoring the original redoip.  This would cause
-	** problems when doing stack unwinding in builtin_throw/1,
-	** because we wouldn't be able to find the special redoip.
-	** But code will only ever hijack the topmost frame, so we
-	** can avoid this by creating a second frame above the special
-	** frame.)
+	** Create an exception handler entry on the nondet stack.
+	** (Register r3 holds the Handler closure.)
 	*/
-	mktempframe(ENTRY(do_fail));
-
+	MR_create_exception_handler(""builtin_catch/3 [model_det]"",
+		MR_MODEL_DET_HANDLER, r3, ENTRY(do_fail));
+	
 	/*
 	** Now call `Goal(Result)'.
 	*/
@@ -746,6 +684,9 @@
 	update_prof_current_proc(LABEL(mercury__exception__builtin_catch_3_2));
 	/*
 	** On exit from do_call_det_closure, Result is in r1
+	**
+	** We must now deallocate the ticket and nondet stack frame that
+	** were allocated by MR_create_exception_handler().
 	*/
 #ifdef MR_USE_TRAIL
 	MR_discard_ticket();
@@ -757,7 +698,7 @@
 **	call Goal(R).
 **	if succeeds, set Result = R.
 **	if fails, fail.
-**	if throws an exception, call Handler(Result).
+**	if throws an exception, call Handler(Exception, Result).
 **
 ** This is the model_semi version.
 ** On entry, we have a type_info (which we don't use) in r1,
@@ -772,47 +713,13 @@
 }
 #endif
 Define_entry(mercury__exception__builtin_catch_3_3); /* cc_nondet */
-	/*
-	** Create a handler on the stack with the special redoip
-	** of `exception_handler_do_fail' (we'll look for this redoip
-	** when unwinding the nondet stack in builtin_throw/1),
-	** and save the stuff we will need if an exception is thrown.
-	*/
-	mkpragmaframe(""builtin_catch/3 [model_semi]"", 0,
-		Exception_Handler_Frame_struct,
-		ENTRY(exception_handler_do_fail));
-	FRAMEVARS->code_model = MODEL_SEMI;
-	FRAMEVARS->handler = r3;	/* save the Handler closure */
-	FRAMEVARS->stack_ptr = MR_sp;	/* save the det stack pointer */
-#ifndef CONSERVATIVE_GC
-	/* save the heap and solutions heap pointers */
-	FRAMEVARS->heap_ptr = MR_hp;
-	FRAMEVARS->solns_heap_ptr = MR_sol_hp;
-	FRAMEVARS->heap_zone = MR_heap_zone;
-#endif
-#ifdef MR_USE_TRAIL
-	/* save the trail state */
-	MR_mark_ticket_stack(FRAMEVARS->ticket_counter);
-	MR_store_ticket(FRAMEVARS->trail_ptr);
-#endif
-
-
 	/*
-	** Now we need to create another frame.
-	** This is so that we can be sure that no-one will hijack
-	** the redoip of the special frame we created above.
-	** (The compiler sometimes generates ``hijacking'' code that saves
-	** the topmost redoip on the stack, and temporarily replaces it
-	** with a new redoip that will do some processing on failure
-	** before restoring the original redoip.  This would cause
-	** problems when doing stack unwinding in builtin_throw/1,
-	** because we wouldn't be able to find the special redoip.
-	** But code will only ever hijacks the topmost frame, so we
-	** can avoid this by creating a second frame above the special
-	** frame.)
+	** Create an exception handler entry on the nondet stack.
+	** (Register r3 holds the Handler closure.)
 	*/
-	mktempframe(ENTRY(do_fail));
-
+	MR_create_exception_handler(""builtin_catch/3 [model_semi]"",
+		MR_MODEL_SEMI_HANDLER, r3, ENTRY(do_fail));
+	
 	/*
 	** Now call `Goal(Result)'.
 	*/
@@ -842,7 +749,7 @@
 **	call Goal(R).
 **	if succeeds, set Result = R.
 **	if fails, fail.
-**	if throws an exception, call Handler(Result).
+**	if throws an exception, call Handler(Exception, Result).
 **
 ** This is the model_non version.
 ** On entry, we have a type_info (which we don't use) in r1,
@@ -857,49 +764,19 @@
 }
 #endif
 Define_entry(mercury__exception__builtin_catch_3_5); /* nondet */
-	/*
-	** Create a handler on the stack with the special redoip
-	** of `exception_handler_do_fail' (we'll look for this redoip
-	** when unwinding the nondet stack in builtin_throw/1),
-	** and save the stuff we will need if an exception is thrown.
-	*/
-	mkpragmaframe(""builtin_catch/3 [model_nondet]"", 0,
-		Exception_Handler_Frame_struct,
-		ENTRY(exception_handler_do_fail));
-	FRAMEVARS->code_model = MODEL_NON;
-	FRAMEVARS->handler = r3;		/* save the Handler closure */
-	FRAMEVARS->stack_ptr = MR_sp;	/* save the det stack pointer */
-#ifndef CONSERVATIVE_GC
-	/* save the heap and solutions heap pointers */
-	FRAMEVARS->heap_ptr = MR_hp;
-	FRAMEVARS->solns_heap_ptr = MR_sol_hp;
-	FRAMEVARS->heap_zone = MR_heap_zone;
-#endif
-#ifdef MR_USE_TRAIL
-	/* save the trail state */
-	MR_mark_ticket_stack(FRAMEVARS->ticket_counter);
-	MR_store_ticket(FRAMEVARS->trail_ptr);
-#endif
-
 	/*
-	** Now we need to create another frame.
-	** This is so that we can be sure that no-one will hijack
-	** the redoip of the special frame we created above.
-	** (The compiler sometimes generates ``hijacking'' code that saves
-	** the topmost redoip on the stack, and temporarily replaces it
-	** with a new redoip that will do some processing on failure
-	** before restoring the original redoip.  This would cause
-	** problems when doing stack unwinding in builtin_throw/1,
-	** because we wouldn't be able to find the special redoip.
-	** But code will only ever hijacks the topmost frame, so we
-	** can avoid this by creating a second frame above the special
-	** frame.)
+	** Create an exception handler entry on the nondet stack.
+	** (Register r3 holds the Handler closure.)
 	*/
 #ifdef MR_USE_TRAIL
-	mktempframe(LABEL(mercury__exception__builtin_catch_3_5_i3));
+	MR_create_exception_handler(""builtin_catch/3 [model_nondet]"",
+		MR_MODEL_NON_HANDLER, r3,
+		LABEL(mercury__exception__builtin_catch_3_5_i3));
 #else
-	mktempframe(ENTRY(do_fail));
+	MR_create_exception_handler(""builtin_catch/3 [model_nondet]"",
+		MR_MODEL_NON_HANDLER, r3, ENTRY(do_fail));
 #endif
+	
 
 	/*
 	** Now call `Goal(Result)'.
@@ -932,8 +809,11 @@
 /*
 ** builtin_throw(Exception):
 **	Throw the specified exception.
-**	That means unwinding the nondet stack until we find a handler, and then
-**	calling Handler(Result).
+**	That means unwinding the nondet stack until we find a handler,
+**	unwinding all the other Mercury stacks, and then
+**	calling longjmp() to unwind the C stack.
+**	The longjmp() will branch to builtin_catch which will then
+**	call Handler(Exception, Result).
 **
 ** On entry, we have Exception in r1.
 */
@@ -941,7 +821,7 @@
 {
 	Word exception = r1;
 	Word handler;
-	enum CodeModel catch_code_model;
+	enum MR_HandlerCodeModel catch_code_model;
 	Word *orig_curfr;
 	Unsigned exception_event_number = MR_trace_event_number;
 
@@ -1016,19 +896,29 @@
 	}
 
 	/*
-	** Save the handler we found, and reset the det stack top.
+	** Save the handler we found
 	*/
+	catch_code_model = MR_EXCEPTION_FRAMEVARS->code_model;
+	handler = MR_EXCEPTION_FRAMEVARS->handler;	
+
+	/*
+	** Reset the success ip (i.e. return address).
+	** This ensures that when we return from this procedure,
+	** we will return to the caller of `builtin_catch'.
+	*/
 	MR_succip = MR_succip_slot(MR_curfr);
-	catch_code_model = FRAMEVARS->code_model;
-	handler = FRAMEVARS->handler;	
-	MR_sp = FRAMEVARS->stack_ptr;	/* reset the det stack pointer */
+
+	/*
+	** Reset the det stack.
+	*/
+	MR_sp = MR_EXCEPTION_FRAMEVARS->stack_ptr;
 
 #ifdef MR_USE_TRAIL
 	/*
 	** Reset the trail.
 	*/
-	MR_reset_ticket(FRAMEVARS->trail_ptr, MR_exception);
-	MR_discard_tickets_to(FRAMEVARS->ticket_counter);
+	MR_reset_ticket(MR_EXCEPTION_FRAMEVARS->trail_ptr, MR_exception);
+	MR_discard_tickets_to(MR_EXCEPTION_FRAMEVARS->ticket_counter);
 #endif
 #ifndef CONSERVATIVE_GC
 	/*
@@ -1049,7 +939,7 @@
 	Word * saved_solns_heap_ptr;
 
 	/* switch to the solutions heap */
-	if (MR_heap_zone == FRAMEVARS->heap_zone) {
+	if (MR_heap_zone == MR_EXCEPTION_FRAMEVARS->heap_zone) {
 		swap_heaps();
 	}
 
@@ -1060,22 +950,23 @@
 	** Note that we need to save/restore the hp register, if it
 	** is transient, before/after calling deep_copy().
 	*/
-	assert(FRAMEVARS->heap_ptr <= FRAMEVARS->heap_zone->top);
+	assert(FRAMEVARS->heap_ptr <= MR_EXCEPTION_FRAMEVARS->heap_zone->top);
 	save_transient_registers();
 	exception = deep_copy((Word *) exception,
 		(Word *) &mercury_data_std_util__type_ctor_info_univ_0,
-		FRAMEVARS->heap_ptr, FRAMEVARS->heap_zone->top);
+		FRAMEVARS->heap_ptr, MR_EXCEPTION_FRAMEVARS->heap_zone->top);
 	restore_transient_registers();
 
 	/* switch back to the ordinary heap */
 	swap_heaps();
 
 	/* reset the heap */
-	assert(FRAMEVARS->heap_ptr <= MR_hp);
-	MR_hp = FRAMEVARS->heap_ptr;
+	assert(MR_EXCEPTION_FRAMEVARS->heap_ptr <= MR_hp);
+	MR_hp = MR_EXCEPTION_FRAMEVARS->heap_ptr;
 
 	/* deep_copy the exception back to the ordinary heap */
-	assert(FRAMEVARS->solns_heap_ptr <= MR_solutions_heap_zone->top);
+	assert(MR_EXCEPTION_FRAMEVARS->solns_heap_ptr <=
+		MR_solutions_heap_zone->top);
 	save_transient_registers();
 	exception = deep_copy((Word *) exception,
 		(Word *) &mercury_data_std_util__type_ctor_info_univ_0,
@@ -1083,10 +974,9 @@
 	restore_transient_registers();
 
 	/* reset the solutions heap */
-	fflush(NULL);
-	assert(FRAMEVARS->solns_heap_ptr <= saved_solns_heap_ptr);
+	assert(MR_EXCEPTION_FRAMEVARS->solns_heap_ptr <= saved_solns_heap_ptr);
 	assert(saved_solns_heap_ptr <= MR_sol_hp);
-	if (catch_code_model == MODEL_NON) {
+	if (catch_code_model == MR_MODEL_NON_HANDLER) {
 		/*
 		** If the code inside the try (catch) was nondet,
 		** then its caller (which may be solutions/2) may
@@ -1094,7 +984,7 @@
 		** after the goal succeeded; the goal may have
 		** only thrown after being re-entered on backtracking.
 		** Thus we can only reset the solutions heap to
-		** where it was before
+		** where it was before copying the exception object to it.
 		*/
 		MR_sol_hp = saved_solns_heap_ptr;
 	} else {
@@ -1103,7 +993,7 @@
 		** we can safely reset the solutions heap to where
 		** it was when it try (catch) was entered.
 		*/
-		MR_sol_hp = FRAMEVARS->solns_heap_ptr;
+		MR_sol_hp = MR_EXCEPTION_FRAMEVARS->solns_heap_ptr;
 	}
 }
 #endif /* !defined(CONSERVATIVE_GC) */
@@ -1117,9 +1007,20 @@
 	MR_curfr = MR_maxfr;
 
 	/*
-	** Now invoke the handler that we found, as `Handler(Result)',
+	** Now longjmp to the catch, which will invoke the handler
+	** that we found.
 	*/
 
+	if (catch_code_model == MR_C_LONGJMP_HANDLER) {
+		MR_ENGINE(e_exception) = (Word *) exception;
+		save_registers();
+		longjmp(*(MR_ENGINE(e_jmp_buf)), 1);
+	}
+
+	/*
+	** Otherwise, the handler is a Mercury closure.
+	** Invoke the handler as `Handler(Exception, Result)'.
+	*/
 	r1 = handler;		/* get the Handler closure */
 	r2 = 1;			/* One additional input argument */
 	r3 = 1;			/* One output argument */
@@ -1131,7 +1032,7 @@
 	** the result in r1, which is where do_call_det_closure puts it,
 	** so we can to a tailcall.
 	*/
-	if (catch_code_model != MODEL_SEMI) {
+	if (catch_code_model != MR_MODEL_SEMI_HANDLER) {
 		tailcall(ENTRY(do_call_det_closure), 
 			ENTRY(mercury__exception__builtin_throw_1_0));
 	}
@@ -1148,17 +1049,8 @@
 	r1 = TRUE;
 	MR_succip = (Code *) MR_stackvar(1);
 	decr_sp_pop_msg(1);
-	proceed();
+	proceed(); /* return to the caller of `builtin_catch' */
 
-Define_entry(exception_handler_do_fail);
-	/*
-	** `exception_handler_do_fail' is the same as `do_fail':
-	** it just invokes fail().  The reason we don't just use
-	** `do_fail' for this is that when unwinding the stack we
-	** check for a redoip of `exception_handler_do_fail' and
-	** handle it specially.
-	*/
-	fail();
 END_MODULE
 
 
Index: runtime/mercury_bootstrap.h
===================================================================
RCS file: /home/mercury1/repository/mercury/runtime/mercury_bootstrap.h,v
retrieving revision 1.9
diff -u -r1.9 mercury_bootstrap.h
--- mercury_bootstrap.h	1999/09/10 10:26:24	1.9
+++ mercury_bootstrap.h	1999/09/15 15:09:15
@@ -233,6 +233,9 @@
 #define mercury_data___type_ctor_info_func_0_struct \
 	MR_TypeCtorInfo_struct
 
+/* stuff from mercury_engine.h */
+#define call_engine(e) ((void) MR_call_engine((e), FALSE))
+
 #endif	/* not MR_NO_BACKWARDS_COMPAT */
 
 #endif	/* MERCURY_BOOTSTRAP_H */
Index: runtime/mercury_engine.c
===================================================================
RCS file: /home/mercury1/repository/mercury/runtime/mercury_engine.c,v
retrieving revision 1.16
diff -u -r1.16 mercury_engine.c
--- mercury_engine.c	1999/04/30 04:25:39	1.16
+++ mercury_engine.c	1999/09/15 15:16:14
@@ -28,7 +28,7 @@
 
 #endif
 
-static	void	call_engine_inner(Code *entry_point);
+static	void	call_engine_inner(Code *entry_point) NO_RETURN;
 
 #ifndef USE_GCC_NONLOCAL_GOTOS
   static Code	*engine_done(void);
@@ -152,13 +152,27 @@
 /*---------------------------------------------------------------------------*/
 
 /*
-** call_engine(Code *entry_point)
+** Word *
+** MR_call_engine(Code *entry_point, bool catch_exceptions)
 **
 **	This routine calls a Mercury routine from C.
 **
 **	The called routine should be det/semidet/cc_multi/cc_nondet.
-**	The virtual machine registers must be set up correctly
-**	before the call.  Specifically, the non-transient real registers
+**
+**	If the called routine returns normally (this includes the case of a
+**	semidet/cc_nondet routine failing, i.e. returning with r1 = FALSE),
+**	then MR_call_engine() will return NULL.
+**
+**	If the called routine exits by throwing an exception, then the
+**	behaviour depends on the `catch_exceptions' flag.
+**	if `catch_exceptions' is true, then MR_call_engine() will return the
+**	Mercury exception object thrown.  If `catch_exceptions' is false,
+**	then MR_call_engine() will not return; instead, the code for `throw'
+**	will unwind the stacks (including the C stack) back to the nearest
+**	enclosing exception handler.
+**
+**	The virtual machine registers must be set up correctly before the call
+**	to MR_call_engine().  Specifically, the non-transient real registers
 **	must have valid values, and the fake_reg copies of the transient
 **	(register window) registers must have valid values; call_engine()
 **	will call restore_transient_registers() and will then assume that
@@ -189,8 +203,8 @@
 **	and another portable version that works on standard ANSI C compilers.
 */
 
-void 
-call_engine(Code *entry_point)
+Word *
+MR_call_engine(Code *entry_point, bool catch_exceptions)
 {
 
 	jmp_buf		curr_jmp_buf;
@@ -208,17 +222,56 @@
 	MR_ENGINE(e_jmp_buf) = &curr_jmp_buf;
 
 	/*
+	** Create an exception handler frame on the nondet stack
+	** so that we can catch and return Mercury exceptions.
+	*/
+	if (catch_exceptions) {
+		MR_create_exception_handler("call_engine",
+			MR_C_LONGJMP_HANDLER, 0, ENTRY(do_fail));
+	}
+		
+	/*
 	** Mark this as the spot to return to.
-	** On return, restore the registers (since longjmp may clobber
-	** them), restore the saved value of MR_ENGINE(e_jmp_buf), and then
-	** exit.
 	*/
-
 	if (setjmp(curr_jmp_buf)) {
+		Word	* this_frame;
+		Word	* exception;
+
 		debugmsg0("...caught longjmp\n");
+		/*
+		** On return,
+		** restore the registers (since longjmp may clobber them),
+		** and restore the saved value of MR_ENGINE(e_jmp_buf).
+		*/
 		restore_registers();
 		MR_ENGINE(e_jmp_buf) = prev_jmp_buf;
-		return;
+		if (catch_exceptions) {
+			/*
+			** Figure out whether or not we got an exception.
+			** If we got an exception, then all of the necessary
+			** cleanup such as stack unwinding has already been
+			** done, so all we have to do here is to return the
+			** exception.
+			*/
+			exception = MR_ENGINE(e_exception);
+			if (exception != NULL) {
+				return exception;
+			}
+			/*
+			** If we added an exception hander, but we didn't
+			** get an exception, then we need to remove the
+			** exception handler frames from the nondet stack
+			** and deallocate the trail ticket allocated by
+			** MR_create_exception_handler().
+			*/
+			this_frame = MR_curfr;
+			MR_maxfr = MR_prevfr_slot(this_frame);
+			MR_curfr = MR_succfr_slot(this_frame);
+#ifdef MR_USE_TRAIL
+			MR_discard_ticket();
+#endif
+		}
+		return NULL;
 	}
 
 	call_engine_inner(entry_point);
@@ -228,7 +281,7 @@
 
 /* The gcc-specific version */
 
-void 
+static void 
 call_engine_inner(Code *entry_point)
 {
 	/*
@@ -287,7 +340,7 @@
 #ifdef MR_LOWLEVEL_DEBUG
 	memset((void *)locals, MAGIC_MARKER, LOCALS_SIZE);
 #endif
-	debugmsg1("in `call_engine', locals at %p\n", (void *)locals);
+	debugmsg1("in `call_engine_inner', locals at %p\n", (void *)locals);
 
 	/*
 	** Increment the number of times we've entered this
@@ -346,10 +399,10 @@
 
 	/*
 	** We need to ensure that there is at least one
-	** real function call in call_engine(), because
+	** real function call in call_engine_inner(), because
 	** otherwise gcc thinks that it doesn't need to
 	** restore the caller-save registers (such as
-	** the return address!) because it thinks call_engine() is
+	** the return address!) because it thinks call_engine_inner() is
 	** a leaf routine which doesn't call anything else,
 	** and so it thinks that they won't have been clobbered.
 	**
@@ -395,6 +448,7 @@
 	** Since longjmp() may clobber the registers, we need to
 	** save them first.
 	*/
+	MR_ENGINE(e_exception) = NULL;
 	save_registers();
 	debugmsg0("longjmping out...\n");
 	longjmp(*(MR_ENGINE(e_jmp_buf)), 1);
@@ -424,6 +478,7 @@
 static Code *
 engine_done(void)
 {
+	MR_ENGINE(e_exception) = NULL;
 	save_registers();
 	debugmsg0("longjmping out...\n");
 	longjmp(*(MR_ENGINE(e_jmp_buf)), 1);
@@ -529,6 +584,7 @@
 Define_extern_entry(do_succeed);
 Define_extern_entry(do_last_succeed);
 Define_extern_entry(do_not_reached);
+Define_extern_entry(exception_handler_do_fail);
 
 BEGIN_MODULE(special_labels_module)
 	init_entry_ai(do_redo);
@@ -536,6 +592,7 @@
 	init_entry_ai(do_succeed);
 	init_entry_ai(do_last_succeed);
 	init_entry_ai(do_not_reached);
+	init_entry_ai(exception_handler_do_fail);
 BEGIN_CODE
 
 Define_entry(do_redo);
@@ -551,11 +608,18 @@
 	MR_succeed_discard();
 
 Define_entry(do_not_reached);
-	printf("reached not_reached\n");
-	exit(1);
-#ifndef	USE_GCC_NONLOCAL_GOTOS
-	return 0;
-#endif
+	fatal_error("reached not_reached\n");
+
+Define_entry(exception_handler_do_fail);
+	/*
+	** `exception_handler_do_fail' is the same as `do_fail':
+	** it just invokes fail().  The reason we don't just use
+	** `do_fail' for this is that when unwinding the stack we
+	** check for a redoip of `exception_handler_do_fail' and
+	** handle it specially.
+	*/
+	fail();
+
 END_MODULE
 
 void mercury_sys_init_engine(void); /* suppress gcc warning */
Index: runtime/mercury_engine.h
===================================================================
RCS file: /home/mercury1/repository/mercury/runtime/mercury_engine.h,v
retrieving revision 1.12
diff -u -r1.12 mercury_engine.h
--- mercury_engine.h	1999/04/20 11:48:13	1.12
+++ mercury_engine.h	1999/09/15 15:16:45
@@ -212,6 +212,7 @@
 		*/
 #endif
 	jmp_buf		*e_jmp_buf;
+	Word		*e_exception;
 #ifndef	CONSERVATIVE_GC
 	MemoryZone	*heap_zone;
 	MemoryZone	*solutions_heap_zone;
@@ -313,8 +314,9 @@
 
 /*
 ** Functions that act on the current Mercury engine.
+** See the comments in mercury_engine.c for documentation on MR_call_engine().
 */
-extern	void	call_engine(Code *entry_point);
+extern	Word *	MR_call_engine(Code *entry_point, bool catch_exceptions);
 extern	void	terminate_engine(void);
 extern	void	dump_prev_locations(void);
 
@@ -330,5 +332,6 @@
 Declare_entry(do_reset_framevar0_fail);
 Declare_entry(do_succeed);
 Declare_entry(do_not_reached);
+Declare_entry(exception_handler_do_fail);
 
 #endif /* not MERCURY_ENGINE_H */
Index: runtime/mercury_stacks.h
===================================================================
RCS file: /home/mercury1/repository/mercury/runtime/mercury_stacks.h,v
retrieving revision 1.17
diff -u -r1.17 mercury_stacks.h
--- mercury_stacks.h	1999/04/30 04:25:41	1.17
+++ mercury_stacks.h	1999/09/10 14:54:54
@@ -15,7 +15,10 @@
 #include "mercury_debug.h"
 #include "mercury_goto.h"
 #include "mercury_tabling.h"
+#include "mercury_engine.h"
 
+/*---------------------------------------------------------------------------*/
+
 /* DEFINITIONS FOR MANIPULATING THE DET STACK */
 
 #define	MR_based_stackvar(base_sp, n)	((base_sp)[-(n)])
@@ -53,6 +56,8 @@
 				(void)0				\
 			)
 
+/*---------------------------------------------------------------------------*/
+
 /* DEFINITIONS FOR NONDET STACK FRAMES */
 
 #define	MR_PREVFR	(-0)	/* prev frame on stack, set up at call	*/
@@ -94,6 +99,8 @@
 
 #define	MR_framevar(n)		MR_based_framevar(MR_curfr, n)
 
+/*---------------------------------------------------------------------------*/
+
 /* DEFINITIONS FOR MANIPULATING THE NONDET STACK */
 
 #define	MR_mkframe(predname, numslots, redoip)				\
@@ -202,6 +209,147 @@
 				MR_curfr = MR_redofr_slot(MR_maxfr);	\
 				GOTO(MR_redoip_slot(MR_maxfr));		\
 			} while (0)
+
+/*---------------------------------------------------------------------------*/
+
+/* DEFINITIONS FOR EXCEPTION HANDLING */
+
+#ifdef CONSERVATIVE_GC
+  #define MR_IF_NOT_CONSERVATIVE_GC(x)
+#else
+  #define MR_IF_NOT_CONSERVATIVE_GC(x) x
+#endif
+
+#ifdef MR_USE_TRAIL
+  #define MR_IF_USE_TRAIL(x) x
+#else
+  #define MR_IF_USE_TRAIL(x)
+#endif
+
+/*
+** This enum specifies the kind of handler in an exception handler
+** nondet stack frame.
+*/
+enum MR_HandlerCodeModel {
+	/* 
+	** For these three values, the exception handler is a Mercury closure
+	** with the specified determinism.  If an exception occurs, then
+	** after the Mercury stacks have been unwound, the closure will
+	** be called.
+	*/
+	MR_MODEL_DET_HANDLER,
+	MR_MODEL_SEMI_HANDLER,
+	MR_MODEL_NON_HANDLER,
+	/*
+	** For this value, the exception will be handled by C code using
+	** setjmp/longjmp.  If an exception occurs, then after the Mercury
+	** stacks have been unwound, `MR_longjmp(MR_ENGINE(e_jmp_buf))' will
+	** be called.
+	*/
+	MR_C_LONGJMP_HANDLER
+};
+
+
+/*
+** Define a struct for the framevars that we use in an exception handler
+** nondet stack frame.  This struct gets allocated on the nondet stack
+** using mkpragmaframe(), with a special redoip of
+** `exception_handler_do_fail'.
+*/
+typedef struct MR_Exception_Handler_Frame_struct {
+	/*
+	** The `code_model' field is used to identify what kind of
+	** handler it is. It holds values of type MR_HandlerCodeModel
+	** (see above), but it is declared to have type `Word' to ensure
+	** that everything remains word-aligned.
+	*/
+	Word code_model;
+
+	/*
+	** If code_model is MR_MODEL_*_HANDLER, then
+	** the `handler' field holds the Mercury closure for the handler,
+	** which will be a closure of the specified determinism.
+	** If code_model is MR_C_LONGJMP, then this field is unused.
+	*/
+	Word handler;
+
+	/*
+	** The remaining fields hold stuff that must be saved in order
+	** to unwind the Mercury stacks.
+	*/
+
+	/* the det stack pointer */
+	Word *stack_ptr;
+
+	/* the trail state */
+	MR_IF_USE_TRAIL(
+		Word trail_ptr;
+		Word ticket_counter;
+	)
+
+	/* the heap state */
+	MR_IF_NOT_CONSERVATIVE_GC(
+		Word *heap_ptr;
+		Word *solns_heap_ptr;
+		MemoryZone *heap_zone;
+	)
+} MR_Exception_Handler_Frame;
+
+#define MR_EXCEPTION_FRAMEVARS \
+    (((MR_Exception_Handler_Frame *) (MR_curfr - MR_NONDET_FIXED_SIZE)) - 1)
+
+#define MR_create_exception_handler(name,				      \
+		handler_code_model, handler_closure, redoip)		      \
+	do {								      \
+		/*							      \
+		** Create a handler on the stack with the special redoip      \
+		** of `exception_handler_do_fail' (we'll look for this        \
+		** redoip when unwinding the nondet stack in		      \
+		** builtin_throw/1), and save the stuff we will		      \
+		** need if an exception is thrown.			      \
+		*/							      \
+		mkpragmaframe((name), 0,	      		      	      \
+			MR_Exception_Handler_Frame_struct,		      \
+			ENTRY(exception_handler_do_fail));		      \
+		/* record the handler's code model */			      \
+		MR_EXCEPTION_FRAMEVARS->code_model = (handler_code_model);    \
+		/* save the handler's closure */			      \
+		MR_EXCEPTION_FRAMEVARS->handler = (handler_closure);	      \
+		/* save the det stack pointer */			      \
+		MR_EXCEPTION_FRAMEVARS->stack_ptr = MR_sp;		      \
+		MR_IF_NOT_CONSERVATIVE_GC(				      \
+			/* save the heap and solutions heap pointers */	      \
+			MR_EXCEPTION_FRAMEVARS->heap_ptr = MR_hp;	      \
+			MR_EXCEPTION_FRAMEVARS->solns_heap_ptr = MR_sol_hp;   \
+			MR_EXCEPTION_FRAMEVARS->heap_zone = MR_heap_zone;     \
+		)							      \
+		MR_IF_USE_TRAIL(					      \
+			/* save the trail state */			      \
+			MR_mark_ticket_stack(				      \
+				MR_EXCEPTION_FRAMEVARS->ticket_counter);      \
+			MR_store_ticket(MR_EXCEPTION_FRAMEVARS->trail_ptr);   \
+		)							      \
+									      \
+		/*							      \
+		** Now we need to create another frame.			      \
+		** This is so that we can be sure that no-one will hijack     \
+		** the redoip of the special frame we created above.	      \
+		** (The compiler sometimes generates ``hijacking'' code       \
+		** that saves the topmost redoip on the stack, and	      \
+		** temporarily replaces it with a new redoip that will	      \
+		** do some processing on failure before restoring the	      \
+		** original redoip.  This would cause problems when	      \
+		** doing stack unwinding in builtin_throw/1, because	      \
+		** we wouldn't be able to find the special redoip.	      \
+		** But code will only ever hijack the topmost frame,	      \
+		** so we can avoid this by creating a second frame	      \
+		** above the special frame.)				      \
+		*/							      \
+		mktempframe(redoip);				      	      \
+	} while (0)
+
+
+/*---------------------------------------------------------------------------*/
 
 #ifdef	MR_USE_MINIMAL_MODEL
 
Index: runtime/mercury_thread.c
===================================================================
RCS file: /home/mercury1/repository/mercury/runtime/mercury_thread.c,v
retrieving revision 1.8
diff -u -r1.8 mercury_thread.c
--- mercury_thread.c	1998/12/15 00:22:25	1.8
+++ mercury_thread.c	1999/09/15 15:20:49
@@ -102,7 +103,7 @@
 
 	switch (when_to_use) {
 		case MR_use_later :
-			call_engine(ENTRY(do_runnext));
+			(void) MR_call_engine(ENTRY(do_runnext), FALSE);
 
 			destroy_engine(eng);
 			return;
Index: runtime/mercury_wrapper.c
===================================================================
RCS file: /home/mercury1/repository/mercury/runtime/mercury_wrapper.c,v
retrieving revision 1.41
diff -u -r1.41 mercury_wrapper.c
--- mercury_wrapper.c	1999/05/30 03:54:55	1.41
+++ mercury_wrapper.c	1999/09/15 15:10:38
@@ -21,7 +21,7 @@
 **	processes options (which are specified via an environment variable).
 **
 **	It also defines mercury_runtime_main(), which invokes
-**	call_engine(do_interpreter), which invokes main/2.
+**	MR_call_engine(do_interpreter), which invokes main/2.
 **
 **	It also defines mercury_runtime_terminate(), which performs
 **	various cleanups that are needed to terminate cleanly.
@@ -342,7 +342,7 @@
 	*/
 	restore_regs_from_mem(c_regs);
 
-} /* end runtime_mercury_main() */
+} /* end runtime_mercury_init() */
 
 void 
 do_init_modules(void)
@@ -859,8 +861,8 @@
 
 	for (repcounter = 0; repcounter < repeats; repcounter++) {
 		debugmsg0("About to call engine\n");
-		call_engine(ENTRY(do_interpreter));
-		debugmsg0("Returning from call_engine()\n");
+		(void) MR_call_engine(ENTRY(do_interpreter), FALSE);
+		debugmsg0("Returning from MR_call_engine()\n");
 	}
 
         if (use_own_timer) {
@@ -999,10 +1001,11 @@
 BEGIN_CODE
 
 Define_entry(do_interpreter);
-	MR_incr_sp(3);
+	MR_incr_sp(4);
 	MR_stackvar(1) = (Word) MR_hp;
 	MR_stackvar(2) = (Word) MR_succip;
 	MR_stackvar(3) = (Word) MR_maxfr;
+	MR_stackvar(4) = (Word) MR_curfr;
 
 	MR_mkframe("interpreter", 1, LABEL(global_fail));
 
@@ -1054,7 +1057,8 @@
 	MR_hp     = (Word *) MR_stackvar(1);
 	MR_succip = (Code *) MR_stackvar(2);
 	MR_maxfr  = (Word *) MR_stackvar(3);
-	MR_decr_sp(3);
+	MR_curfr  = (Word *) MR_stackvar(4);
+	MR_decr_sp(4);
 
 #ifdef MR_LOWLEVEL_DEBUG
 	if (MR_finaldebug && MR_detaildebug) {

-- 
Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3        |     -- the last words of T. S. Garp.
--------------------------------------------------------------------------
mercury-developers mailing list
Post messages to:       mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions:          mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------



More information about the developers mailing list