Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!know!zaphod.mps.ohio-state.edu!uakari.primate.wisc.edu!sdd.hp.com!ucsd!hub.ucsb.edu!eiffel!kimr From: kimr@eiffel.UUCP (Kim Rochat) Newsgroups: comp.lang.eiffel Subject: Re: Exceptions and Assertions Summary: Patch to to allow assertions without exception history tracing Keywords: eiffel exceptions assertions preconditions Message-ID: <410@eiffel.UUCP> Date: 21 Sep 90 17:44:16 GMT References: <1990Sep7.032121.6456@bony1.uucp> <14831@yunexus.YorkU.CA> <731@tetrauk.UUCP> Organization: Interactive Software Engineering, Santa Barbara CA Lines: 243 In article <731@tetrauk.UUCP>, rick@tetrauk.UUCP (Rick Jones) writes: > >| [paraphrased]: should Eiffel programs generally be run with pre-condition > >| checking on? - yes! yes! yes! > > > I have done a few more experiments on compilation options, and the > overhead of running with pre-condition checks on in the current > implementation seems to more associated with exception history tracing > than with the assertion checks. This is because every routine gets a > default rescue clause even if there is no explicit one. This only > disappears when the "no-assertion-check" compile option is used. > > The most effective run-time (i.e. C compile-time) option would be to > check pre-conditions, void references, "check" clauses (my preference), > but omit history tracing. This means that spurious setjmp() calls are > avoided, and a longjmp() in the case of an exception will go direct to > the currently effective rescue handler. This all seems possible with a > little re-arrangement of the _eiffel.h file; any comments from ISE, > please? I'd like to thank Rick for this suggestion. We've been trying to figure out a way to reduce the cost of assertion monitoring, but had been assuming that a full exception history trace was a requirement. If you are willing to do without the full history trace, this message contains a work-around which reduces the time to execute a program which monitors assertions by a factor of 10. The context diffs at the end of this article are intended to be used with the 'patch' program to modify Eiffel/files/_eiffel.h for Eiffel 2.2B. The modified _eiffel.h works as usual unless your 'C_COMPILER' environment variable (See section 4.17 of the Environment manual) or the makefile 'CC' macro in a C package defines -DNOSTACKTRACE. When -DNOSTACKTRACE is used in conjunction with PRECONDITIONS or ALL_ASSERTIONS, the appropriate assertions are monitored. If an assertion fails, only the routine raising the exception plus those routines having rescue clauses will be shown in the exception history. This behavior results from only calling setjmp to register a rescue clause when a routine actually has a rescue clause (or a class rescue) which must be registered, and not registering routines with default rescue clauses. The resulting program monitors the appropriate assertions, and executes in approximately 1/10 the time. If an assertion fails, you will usually see a single exception history entry like the following: System execution failed. Below is the sequence of recorded exceptions: -------------------------------------------------------------------------------- Object Class Routine Nature of exception Effect -------------------------------------------------------------------------------- 2EB3C LIST put "index_small_enough": Precondition violated. Fail ------------------------------------------------------------------------------- Usage Information: (Note: STACKTRACE is used below to indicate the absence of the -DNOSTACKTRACE option from the C_COMPILER environment variable) 1) This modification has been tested for Eiffel version 2.2B on a Sun-3. It is provided for those customers who want to run with assertions on and not pay the overhead associated with maintaining the full exception history stack. While it is believed to work, this modification is not yet supported by Interactive. The user should understand the Eiffel compilation and configuration management model before trying to use -DNOSTACKTRACE, in particular the locations and names of object files corresponding to the CHECK1 and CHECK2 compilation options. (If you don't know what I'm talking about, you probably shouldn't try to use this). 2) There is a minor semantic difference between running in -DNOSTACKTRACE and normal modes. With -DNOSTACKTRACE, if a routine which has a precondition failure also has a rescue clause (or is in a class having a class rescue clause), the exception will be raised in the routine containing the failed precondition, as opposed to the normal condition where the exception is raised in the caller of the routine. This occurs because a failed precondition raises the exception by doing a longjmp to the top entry on the exception stack. In the STACKTRACE case, the top stack entry is ignored since it is assumed to be the rescue clause of the current routine, default or real. In the NOSTACKTRACE case, the routine knows that the topmost entry is a valid rescue clause, but doesn't know who registered it. An easy way to fix this would be to register the rescue clause for a routine AFTER checking the precondition, which would require rearranging the generated code. 3) Since NOSTACKTRACE is not an SDF option, 'es' doesn't keep track of which classes have been compiled with NOSTACKTRACE. You must be careful to understand what you are doing if you use NOSTACKTRACE. Because of the difference in the handling of the top exception stack entry in the case of a precondition failure (ignored by STACKTRACE, used by -DNOSTACKTRACE), it is important that classes compiled with -DNOSTACKTRACE not call classes compiled with STACKTRACE. If this occurs, a valid rescue clause will be ignored. If the top exception stack entry is for the system exception handler and it is ignored, a serious system error will result if an exception occurs. On my MIPS, I see 'longjmp botch' followed by 'exception occurred in exception handler'. An easy way to protect against this happening is to put an empty rescue clause in the Create routine of your root class to register a second rescue clause in the exception stack. It is permissible for classes compiled with STACKTRACE to call classes compiled with -DNOSTACKTRACE. This will work just as usual, with a full exception history trace produced for those classes compiled with STACKTRACE. A convenient way of using -DNOSTACKTRACE would be to compile the libraries with -DNOSTACKTRACE, and your application with STACKTRACE. The (hopefully large) percentage of the time spent in library routines won't incur any overhead, but if your application causes a precondition failure in a library routine, you will get a stack trace indicating which library routine and precondition failed, followed by a trace of your application up to the point where it called the library routine. 4) If you want to switch STACKTRACE on or off, you will need to remove all object files compiled with the opposite sense and rebuild your application. These object files can be found in each cluster directory in the files .E/*C1.o (for PRECONDITIONS) and .E/*C2.o (for ALL_ASSERTIONS). Don't forget the KERNEL cluster, which isn't normally listed in the SDF. An easier way of switching back and forth would be to use the OPTIMIZE SDF option. If all classes compiled with OPTIMIZE and PRECONDITIONS (or ALL_ASSERTIONS) are compiled with -DNOSTACKTRACE, and all classes compiled without OPTIMIZE (but with PRECONDITIONS or ALL_ASSERTIONS) are compiled without STACKTRACE, then you can switch between STACKTRACE and NOSTACKTRACE simply by changing the OPTIMIZE option in the SDF. I suggest using OPTIMIZE with -DNOSTACKTRACE because it doesn't make much sense to optimize classes which do all those setjmp calls. 5) If you want to isolate the object files compiled with -DNOSTACKTRACE from other users sharing library or application classes with you, you can make a c_package of your application and modify the makefile to add -DNOSTACKTRACE to the 'CC' macro. Remember to remove all object files before recompiling. 6) Just a reminder that -DNOSTACKTRACE doesn't make any difference if used with NO_ASSERTION_CHECK. 7) This is only one of several possible solutions for improving the performance of assertion monitoring. After trying this out, please let us know how important it is to maintain the full exception history mechanism in conjunction with faster assertion checking. (That is, should ISE provide a switch in the .eiffel file to turn off the full exception history, or should ISE work on keeping the full exception history but making it faster?) Kim Rochat Responses to: eiffel@eiffel.com ------------------------------------------------------------------------------- *** Eiffel/files/_eiffel.h.orig Wed Nov 29 11:37:32 1989 --- Eiffel/files/_eiffel.h Mon Sep 17 19:17:51 1990 *************** *** 12,19 **** --- 12,21 ---- #ifdef CHECK1 #define REQUIRE #define TESTVOID + #ifndef NOSTACKTRACE #define FULLEXCEPT #endif + #endif #ifdef CHECK2 #define REQUIRE #define TESTVOID *************** *** 22,29 **** --- 24,33 ---- #define INVARIANT #define VARIANT #define CHECK + #ifndef NOSTACKTRACE #define FULLEXCEPT #endif + #endif #define EB_ASSER_START -1 #define E_VOID -1 #define E_REQUIRE -2 *************** *** 56,64 **** --- 60,76 ---- #define PROJMP12 PROJMP012 #endif #ifdef CHECK2 + #ifdef FULLEXCEPT #define ERRJMP12 ERRJMP012 + #else + #define ERRJMP12 _longjmp (*(jmp_stack+jmp_level-1), 1) + #endif #define PROJMP12 PROJMP012 #endif + #ifndef NOSTACKTRACE + #define ERRJMP12 ERRJMP012 + #define PROJMP12 PROJMP012 + #endif #ifndef ERRJMP12 #define ERRJMP12 #define PROJMP12 *************** *** 72,83 **** #endif #define VIOLAT012 violated (DT[class], routine, last_exception, e_tag, BCurrent) #define SETRES012 set_name_except (DT[class], routine, BCurrent) ! #ifdef FULLEXCEPT #define VIOLAT12 VIOLAT012 #define SETRES12 SETRES012 #else - #define VIOLAT12 #define SETRES12 #endif #define RETRY TRACERET;if (dc_unrolled!=0) VIOLAT012; retry (DT[class], routine, BCurrent);goto start #ifdef TRACE --- 84,100 ---- #endif #define VIOLAT012 violated (DT[class], routine, last_exception, e_tag, BCurrent) #define SETRES012 set_name_except (DT[class], routine, BCurrent) ! #ifdef CHECK2 ! #define SETRES12 SETRES012 #define VIOLAT12 VIOLAT012 + #else + #ifdef FULLEXCEPT #define SETRES12 SETRES012 + #define VIOLAT12 VIOLAT012 #else #define SETRES12 + #define VIOLAT12 + #endif #endif #define RETRY TRACERET;if (dc_unrolled!=0) VIOLAT012; retry (DT[class], routine, BCurrent);goto start #ifdef TRACE