Simn / genjvm

13 stars 1 forks source link

javac synchronized equivalent #39

Closed Simn closed 5 years ago

Simn commented 5 years ago

There are two instructions for this:

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorexit

Sounds surprisingly straightforward actually.

Simn commented 5 years ago

Somewhat related to this: We need the finally functionality to ensure that our locks are released in the case of exceptions.

The documentation mentions that subroutines were used for that in older java versions: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.5

To implement the try-finally construct, a compiler for the Java programming language that generates class files with version number 50.0 or below may use the exception-handling facilities together with two special instructions: jsr ("jump to subroutine") and ret ("return from subroutine").

What it doesn't seem to mention is what to do in versions > 50.0...

Simn commented 5 years ago

Without subroutines, we're gonna have to duplicate some code.

What we need here is a block-exit detection. Given a Haxe code block, it can be exited these ways:

return

This should be straightforward: Emit the block-exit code, then emit the return instruction. I don't think there's anything else we have to do.

Exception

This requires wrapping the block in the Java equivalent of try block catch(e:Dynamic) { block-exit code; throw e; }.

break and continue

This is similar to return, but we have to detect if we're actually leaving our block. That should be a matter of distinguishing finally-in-loop and loop-in-finally.

Control flow falling through

If we can establish the restriction that finally blocks must be Void, all we have to do is put the block-exit code at the end.


We also have to remember that this stuff can be nested (in theory).

Let's assume that we have java.Lib.finally(f:Void -> Void, fFinally:Void ->Void). We would do the following:

  1. Push StackFinally fFinally to a block_exit stack.
  2. Set up an exception handler around the f which catches Dynamic, executes fFinally and rethrows the exception.
  3. Start generating the code for f:
    1. If we start generating any sort of loop, push StackLoop to the block_exit stack.
    2. If we exit the loop, pop the stack.
    3. If we generate a return instruction, generate all StackFinally code on the block_exit stack.
    4. If we generate break or continue, generate StackFinally from the block_exit stack until we find a StackLoop item.
  4. At the end of f, generate fFinally.
Simn commented 5 years ago

I implemented this as an expression filter, but I'm not gonna use it because I need finer control over it. Posting it here in case we can use it in Haxe core:

module FinallyHandler = struct
    type finally_stack =
        | StackFinally of texpr
        | StackLoop

    let transform_finally com e e_finally =
        let rec loop stack e = match e.eexpr with
            | TReturn e1 ->
                let e1 = Option.map (loop stack) e1 in
                let el = ExtList.List.filter_map (function
                    | StackFinally e -> Some e
                    | StackLoop -> None
                ) stack in
                mk (TBlock (el @ [{e with eexpr = TReturn e1}])) e.etype e.epos
            | TBreak | TContinue ->
                let rec loop stack = match stack with
                    | [] -> []
                    | StackLoop :: _ -> []
                    | StackFinally e :: stack -> e :: loop stack
                in
                let el = loop stack in
                mk (TBlock (el @ [e])) e.etype e.epos
            | TWhile(e1,e2,flag) ->
                let e1 = loop stack e1 in
                let e2 = loop (StackLoop :: stack) e2 in
                {e with eexpr = TWhile(e1,e2,flag)}
            | TFor(v,e1,e2) ->
                let e1 = loop stack e1 in
                let e2 = loop (StackLoop :: stack) e2 in
                {e with eexpr = TFor(v,e1,e2)}
            | _ ->
                Type.map_expr (loop stack) e
        in
        let e = loop [StackFinally e_finally] e in
        let e = mk (TBlock [
            e;
            e_finally;
        ]) e.etype e.epos in
        let v = alloc_var VGenerated "tmp" t_dynamic null_pos in
        let e_local = mk (TLocal v) v.v_type v.v_pos in
        let e_catch = mk (TBlock [
            e_finally;
            mk (TThrow e_local) t_dynamic null_pos
        ]) e_finally.etype e_finally.epos in
        mk (TTry(e,[v,e_catch])) e.etype e.epos
end
Simn commented 5 years ago

2019-04-12_12-02-33