Closed Bug 350509 Opened 18 years ago Closed 3 years ago

try/catch/finally bytecode efficiency improvements

Categories

(Core :: JavaScript Engine, defect)

defect
Not set
normal

Tracking

()

RESOLVED DUPLICATE of bug 965717
mozilla1.9alpha8

People

(Reporter: brendan, Unassigned)

Details

Igor suggested in bug 350312, based on Rhino experience, to split cases involving "gosub finally" and the ultimate "retsub" the ends the finally:

* When running a finally from the end of its try or catch block, or when breaking out of an outer loop, etc., no exception can be pending and the retsub will be followed by a goto.

* When running the finally from the rethrow sequence after an exception in a try without a catch, or another exception in a catch, or a failure to satisfy any catch guards in try-catch(guard)-finally, the retsub will be followed by throw.

Since there can only be one retsub at the end of a finally, without replicating finally block bytecode we would need to split cases in the interpreter code for retsub, as the patch for bug 350312 does, or do something smarter.

/be
(In reply to comment #0)
> Since there can only be one retsub at the end of a finally, without replicating
> finally block bytecode we would need to split cases in the interpreter code for
> retsub, as the patch for bug 350312 does, or do something smarter.

Should have added that Igor's smarter suggestion #1 was to use one stack slot instead of two, by combining/tagging harder.  Not sure this is a big win for perf and it could ugly up the code quite a bit.

Maybe there are better bytecode schemas for the larger macro-sequences to-do with try/catch/finally.

/be
Proposal for a better try/catch/finally implementation

1. Replace the current GOSUB by PUSHJMP (push jump). The instruction pushes to the stack the specified address without doing any jumps. With such instruction 

try {
   try_code;
} finally {
   finally_code;
}

can be implemented as:

  try_code
  pushjmp address_after_finally
  finally_code
  retsub
address_after_finally:

Similarly try-catch-finally becomes

  try_code
  pushjmp address_after_finally
  goto finally_address
  catch_code  
  pushjmp address_after_finally
  finally_code
  retsub
address_after_finally:

Catch-guards also follow this schema, see bellow for details.

break/continue/return across pending finally blocks use pushjmp to push their destination (in case of return the address of the RETRVAL), then to push addresses of intermediate finally blocks and then goto to the innermost finally.

2. The code that searches for an exception handler should find addresses of *all* finally handlers that cover the source of exception ("pending finally block") until the catch block or if there is no catch block, just all addresses of the pending finally blocks. When no exception handlers are pending, quit like currently. If there is catch or finally blocks, the code sets fp->exception (or other permanently allocated value slot) to the exception, clears cx->throwind and prepares for exception handling in the following way.

If there is no pending finally blocks, the control is transfered to the found catch block. With pending finally blocks the code first pushes to the stack the address of the found catch (or the address of newly introduced THROWEX if there are only pending finally blocks). Then the addresses of found finally blocks except the innermost are pushed to the stack in the reverse order. Lastly, the control is transfered to the innermost finally.

The purpose of THROWEX is to throw the exception stored in fp->exception.

Without the catch block this behaves exactly as if return was executed inside a sequence of try-with-finally blocks except the sequence is terminated with THROWEX, not RETVAL.

Compared with the current implementation the proposal makes the exception handler to work harder to find *all* pending finally blocks as the price for faster execution of exception-less code paths.   

3. RETSUB behaves exactly as it name suggests. That is, it just pops the address from the stack and jump there.

4. Catch block without guards are implemented via transferring fp->exception to the catch variable and clearing  fp->exception. The guarded case works similar except it clear fp->exception only if there is match. Otherwise it just throw fp->exception using THROWEX. That in turn would trigger the search of all pending finally blocks as specified in 2.

AFAICS the proposal should produce smaller bytecode and use only one stack slot per finally, not 2.
(In reply to comment #2)
> AFAICS the proposal should produce smaller bytecode and use only one stack slot
> per finally, not 2.

Plus no JSVAL_HOLE is necessary even for generator.close as for that the finally search can construct an address chain that ends with RETVAL, not THROWEX. Looks like my dislike of black holes from astrophysics spread to JS. Look, there is no JSVAL_HOLE in jsarray.c already ;)  
Yet another note about the proposal. PUSHJMP bytecode and pushing addresses of all pending finally blocks when preparing for handling of exception are independent from each other.

Push-all-addresses can be implemented exactly in the same way with gosub as with pushjmp.

Similarly, pushjmp does not need push-all-addresses, but then it has to push 2 slots as the patch for 350312 does.
(In reply to comment #2)

Please *IGNORE* that search-for-all proposal. I forgot about catch/finally inside the finally. Now I I am prepared for a review of the patch for bug 350312 much better ;).

Still that POPJMP stands and 2 slots for exception and jump can be combined without much ugliness. For example, the current code for gosub:

            i = PTRDIFF(pc, script->main, jsbytecode) + JSOP_GOSUB_LENGTH;
            len = GET_JUMP_OFFSET(pc);
            PUSH(INT_TO_JSVAL(i));

becomes

            i = PTRDIFF(pc, script->main, jsbytecode) + JSOP_GOSUB_LENGTH;
            len = GET_JUMP_OFFSET(pc);
            JS_ASSERT(i > JS_TRUE);
            PUSH(BOOLEAN_TO_JSVAL(i));

Similarly retsub is changed from:

            rval = POP();
            JS_ASSERT(JSVAL_IS_INT(rval));
            len = JSVAL_TO_INT(rval);
            pc = script->main;

to:
            rval = POP();
            if (!JSVAL_IS_PSEUDO_BOOLEAN(rval)) {
                cx->throwing = JS_TRUE;
                cx->exception = rval;
                ok = JS_FALSE;
                goto out;
            }
            len = JSVAL_TO_PSEUDO_BOOLEAN(rval);
            pc = script->main;

What is interesting is that in this form throw can be replaced by just retsub. It also removes the need for THROWING for catch guards as that can be replaced by EXCEPTION.
(In reply to comment #3)
> Plus no JSVAL_HOLE is necessary even for generator.close as for that the
> finally search can construct an address chain that ends with RETVAL, not
> THROWEX. Looks like my dislike of black holes from astrophysics spread to JS.
> Look, there is no JSVAL_HOLE in jsarray.c already ;)  

I like pseudo- (meaning "false", a bit confusing to me in a Boolean context) or crypto- (from Greek for hidden, covered -- this seems apt) booleans.  Using the boolean jsval tag for a 29-bit pc offset for exception-handling bytecode will prevent us from using other crypto-booleans in the future, unless we overload by bytecode context.

I had forgotten that JSVAL_HOLE is gone from jsarray.c, but I wonder whether it isn't wanted there, for fewer out params (no JSBool *hole) => memory bandwidth savings, and for less register pressure.

JSOP_THROWING was helpful to avoid consuming stack space on jumping from a failed guard to the next catch head.  JSOP_EXCEPTION will require a stack slot, and I have not looked to see if that can be modeled at compile time (I'm weary of that code now ;-).

Whatever we do here, we should do it after 1.8.1 is more wrapped up, to avoid merge problems and mis-prioritizing.  Sorry if this is redundant -- I often am tempted to optimize challenging things instead of fixing boring things, so these words apply to me first.

/be
(In reply to comment #6 made on 2006-08-29)
> I had forgotten that JSVAL_HOLE is gone from jsarray.c, but I wonder whether it
> isn't wanted there, for fewer out params (no JSBool *hole) => memory bandwidth
> savings, and for less register pressure.

The problem with JSVAL_HOLE with array functions is that they use rval as GC-protected storage for array elements. Storing JSVAL_HOLE there risks to leak the hole into embedding on errors. 
Assignee: general → nobody

Let's dupe this over to bug 965717.

Status: NEW → RESOLVED
Closed: 3 years ago
Resolution: --- → DUPLICATE
You need to log in before you can comment on or make changes to this bug.