Mercurial > hg > Members > anatofuz > MoarVM
diff docs/exceptions.markdown @ 0:2cf249471370
convert mercurial for git
author | Takahiro SHIMIZU <anatofuz@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 08 May 2018 16:09:12 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/exceptions.markdown Tue May 08 16:09:12 2018 +0900 @@ -0,0 +1,263 @@ +# Exceptions + +Exceptions in MoarVM need to handle a range of cases. There exist both control +exceptions (last/next/redo) where we want to reach the handler in the most +expedient way possible, unwinding the stack as we go, and probably just do a +goto instruction. In these cases, we don't expect to need any kind of exception +object. At the other end of the scale, there are Perl 6 exceptions. These want +to run the handler in the dynamic scope of the exception, and potentially resume +rather than unwinding. These differences are properties of the handler rather than +the exception; a CONTROL is interested in being run on the stack top when a "next" +reaches it, whereas a while loop's handler for that just wants control delivered +to the appropriate place. + +## Handlers + +Handlers are associated with (static) frames. A handler consists of: + +* The start of the protected region (an offset from the frame's bytecode start) +* The end of the protected region (an offset from the frame's bytecode start) +* An exception category filter: + * 1 = Catch Exception + * 2 = Control Exception + * 4 = Next + * 8 = Redo + * 16 = Last + * 32 = Return + * 64 = Unwind (triggers if we unwind out of it due to an exception being + thrown; normal block exits do not cause this) +* A handler action + * 0 = Unwind any required frames, then goto the specified address. It is + not possible to get any exception object or do any kind of rethrow. + * 1 = Unwind any required frames, then goto the specified address. An + exception object is available. This kind of handler leaves a handler + record active on the stack, which the handler should remove by doing + a rethrow or making the exception handled. + * 2 = Invoke the specified block, and unwind unless it chooses to resume. + Once the block returns, the handler is over. +* In the case of a goto address handler, the offset of the handler +* In the case of a block handler, the register in the frame that holds the + block to invoke. The block should take no parameters. + +A bitwise `and` between the category filter and the category of the exception +being thrown is used to check if the handler is applicable. + +Note that an Unwind handler is never actually set as the category of an +exception; these are just for triggering actions during unwinds due to +other exceptions. In the case of an unwind handler, the current exception +is thus the one to blame for the unwinding. It is expected that an unwind +handler will always rethrow once it's done what is needed. + +## Handler representation in MAST + +The MAST::HandlerScope node indicates the instructions covered by handler +and details of the kind of handler it is. See the MAST node definitions for +more. + +## Handler representation in bytecode + +Handlers are stored per frame and listed in a table. It is important that +more deeply nested handlers appear in the table earlier than those lexically +outer to them. This is really a job for the MAST to Bytecode compiler, since +the MAST encodes the structure as nested nodes. Really, though, it's just a +case of writing an entry into the frame's table after the node has been +processed. See the bytecode specification for details. + +## Exception Objects + +Some opcodes exist for creating exception objects and working with them. An +exception object is anything with the VMException representation. Note that +most HLLs will wish to attach their own objects as the payload. + +### exception w(obj) +Gets the exception currently being handled. Only valid in the scope of handler. + +### handled r(obj) +Marks the specified exception as handled. Only valid in the scope of a handler +for the specified exception. Also, only required for goto handlers that also +include an exception object. + +### newexception w(obj) +Creates a new exception object, based on the current HLL's configured exception +type object or using BOOTException otherwise. By default it has an empty message +and a category of 1 (a catch exception). + +### bindexmessage r(obj), r(str) +Sets the exception object's string message. + +### bindexpayload r(obj), r(obj) +Sets the exception object's payload (some other object). + +### bindexcategory r(obj), r(int64) +Sets the exception object's category + +### getexmessage w(str), r(obj) +Gets the exception object's string message. + +### getexpayload W(obj), r(obj) +Gets the exception object's payload. + +### getexcategory w(int64), r(obj) +Gets the exception object's category + +## Throwing Exceptions +There are various instructions for throwing a new exception object. + + throwdyn w(obj) r(obj) + throwlex w(obj) r(obj) + throwlexotic w(obj) r(obj) + +There are also instructions for throwing a particular category of exception +without first creating an exception object. + + throwcatdyn w(obj) int64 + throwcatlex w(obj) int64 + throwcatlexotic w(obj) int64 + +These will only produce an exception object for handlers that need it. The +object that is produced will have a null message and payload, so only its +category will be of interest. These are mostly intended for control exceptions. + +Finally, for convenience, there is also: + + die w(obj) r(str) + +Which creates a catch exception with a string message and throws it. + +One may wonder why all of these throw instructions take a register to write into. +This is because a handler that invokes in the dynamic scope of the throw has the +option to prevent stack unwinding by instead indicating that execution be resumed. +When it does so, it specifies an argument for the resumption; this argument is then +written into the register should resumption take place. + +As for the dyn/lex/lexotic difference: + +* dyn means "search caller" +* lex means "search outer", with the caveat that the outer must also be on the caller + chain too +* lexotic combines the two; for each entry in the dynamic scope, we scan all outers + from that point; note that such an outer should also be in the call chain + +## Rethrowing + +Sometimes, a handler may want to look at an exception, see if it's what it expects to +handle, and if not pass it along as if the handler never saw it. This is the job of +rethrow. A rethrow may only be used on the exception currently being handled. It is +a simple instruction: + + rethrow + +Since it's always about the exception for the current handler, there's no need to say +what should be rethrown. + +## Goto handlers that access exception objects and may rethrow + +A goto handler that is allowed to get the exception object and/or rethrow it must mark +the point they consider the handler over in the case they do not rethrow. The op for +this is simply: + + handled r(obj) + +Note that if, while the handler is active, another exception is thrown and unwinds the +stack past this handler, that's fine. + +## Overall mechanism + +A stack of current handlers is maintained. Note that this is handlers we've actually +invoked as the result of an exception being thrown (there may be many handler scopes +that we are in, but only those that are presently handling exceptions get an entry on +the stack). + +When we search for handlers to invoke, any active handler is automatically skipped, +so that a handler can never catch an exception thrown within it. Otherwise, you can +easily imagine a mass of hangs. + +When an exception is thrown, some pieces of information are initially needed: + +* The category, CAT +* The exception object, OBJ +* How to search (dyn, lex, lexotic), MODE +* The current scope, SCOPE +* The curent thread's active handler stack, HSTACK + +Here is the overall algorithm in pseudo-code. + +XXX TODO: Finish this up. :-) + + search_frame_handlers(f, cat): + for h in f.handlers + if h.category_mask & cat + if f.pc >= h.from && f.pc < h.to + if !in_handler_stack(HSTACK, h) + return h + return NULL + + search_for_handler_from(f, mode, cat) + if mode == LEXOTIC + while f != NULL + h = search_for_handler_from(f, LEX, cat) + if h != NULL + return h + f = f.caller + else + while f != NULL + h = search_frame_handlers(f, cat) + if h != NULL + return h + if mode == DYN + f = f.caller + else if f == LEX + f_maybe = f.outer + while f_maybe != NULL && !is_in_caller_chain(f, f_maybe) + f_maybe = f_maybe.outer + f = f_maybe + return NULL + + run_handler(h, target_scope) + if h.mode == 0 + unwind_to(target_scope) + pc = h.goto + return_to_runloop + if h.mode == 1 + unwind_to(target_scope) + pc = h.goto + push_handler(h, target_scope) + return_to_runloop + if h.mode == 2 + unwind_to(target_scope) + push_handler(h, target_scope) + SCOPE.return_special = ... + SCOPE.return_special_data = ... + invoke(get_reg(target_scope, h.local_idx)) + return_to_runloop + if h.mode == 3 + push_handler(h, target_scope) + SCOPE.return_special = ... + SCOPE.return_special_data = ... + invoke(get_reg(target_scope, h.local_idx)) + return_to_runloop + + panic_unhandled(scope, obj): + note "Unahndled exception: " + obj.message + note backtrace(scope) + exit 1 + + panic_unhandled_cat(scope, cat): + note "Unahndled exception of category " + category_name(cat) + note backtrace(scope) + exit 1 + + throw(mode): + (h, target_scope) = search_for_handler_from(SCOPE, mode, CAT) + if h == NULL + panic_unhandled_cat(SCOPE, CAT) + run_handler_(h, target_scope, obj) + + throwcat(mode): + (h, target_scope) = search_for_handler_from(SCOPE, mode, CAT) + if h == NULL + panic_unhandled_cat(SCOPE, CAT) + run_handler_(h, target_scope, NULL) + + handled(): + HSTACK.pop()