Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add labels and gotos. (Fixes #101) #5699

Merged
merged 11 commits into from
Jun 18, 2014
Merged

Conversation

dcjones
Copy link
Contributor

@dcjones dcjones commented Feb 6, 2014

I've taken a crack at implementing gotos.

What I've done is add new ast nodes SymbolicLabelNode and SymbolicGotoNode mirroring LabelNode and GotoNode but with symbol labels instead of integers. In goto-form, integer labels are assigned along with everything else, so this plays nicely with other control flow.

Rather than introducing new syntax or keywords I just made macros @label and @goto. I figured goto is (should be) used rarely enough that new syntax might not be worth it.

Now we can make local jumps to our hearts' content:

function f()
    @goto a
    @label c
    println("they seem")
    return
    @label b
    println("not what")
    @goto c
    @label a
    println("things are")
    @goto b
    return
end

This is my first foray into the src directory, so there could be something I've totally overlooked.

@timholy
Copy link
Sponsor Member

timholy commented Feb 6, 2014

Impressive, @dcjones!

@JeffBezanson
Copy link
Sponsor Member

This does pretty much work; no real src-related problems.

I suspect the macroexpander will have to handle renaming labels to make them locally unique.

The good news is the new types of AST nodes are not really necessary for this approach. Macros only need to emit Expr objects, and since the front-end (in julia-syntax.scm) lowers them to normal goto and label nodes, the lowered form remains the same as before. The @label macro could just return Expr(:symboliclabel, name).

@dcjones
Copy link
Contributor Author

dcjones commented Feb 6, 2014

Ok, so the AST nodes are only actually needed if I were to introduce new syntax. If no one feels strongly about that, I'll strip those out and update this PR.

@JeffBezanson
Copy link
Sponsor Member

No, new AST nodes are only needed in the internal lowered form, if you need to convey something fundamentally different to the code generator. New surface syntax can be added just in the parser, without touching any other layers.

@dcjones
Copy link
Contributor Author

dcjones commented Feb 7, 2014

I've finished my parser generator backend using this PR. The results are both encouraging and disappointing. Running the generated parser function on my benchmark the first time take ~30 seconds, and the second time ~0.5 seconds. The 0.5 number is a terrific start; it's only about 4-5x slower than the fastest C parser, but the function takes a seemingly excessive time to compile.

Granted, the resulting code is super gnarly, with ~1700 labels and ~3000 gotos. Maybe there's no easy remedy, but is there at least a good way to go about tracking down the bottleneck? The profiler doesn't tell me much here. Is valgrind the next step?

@JeffBezanson
Copy link
Sponsor Member

Probably certain LLVM optimization passes are very complex in the number of basic blocks. You can try commenting out FPM->run(*f) in codegen.cpp.

@dcjones
Copy link
Contributor Author

dcjones commented Feb 7, 2014

That drops it by a few seconds, but it's still ~25 seconds on the first run.

@StefanKarpinski
Copy link
Sponsor Member

Hmm. Seems like a good use case for pre-compilation.

@JeffBezanson
Copy link
Sponsor Member

Aha, I think there are some cases (at least in inference.jl) where we do linear lookup per-label. That will add an n^2 term. We should use a hash table for long functions.

@dcjones
Copy link
Contributor Author

dcjones commented Feb 26, 2014

Updated with goto without the unnecessary AST nodes, plus some simple optimizations. My test case takes 10 seconds now instead of 25. As far as I can tell, most of the time is still spent bouncing around in typeinf, but I don't understand that code well enough to try to optimize it.

@dcjones
Copy link
Contributor Author

dcjones commented Mar 30, 2014

Can we have gotos in 0.3, pretty please?

There are now proper error messages when the same label is defined twice. And labels that are both defined and referenced in the same macro are renamed by macroexpander, which seemed to me the right way to deal with that.

@JeffBezanson
Copy link
Sponsor Member

Looking pretty solid; great job with this. I'll read it in detail in a bit but this seems possible to merge for 0.3.

@dcjones dcjones added this to the 0.3 milestone Jun 3, 2014
@dcjones dcjones added build and removed build labels Jun 3, 2014
@dcjones
Copy link
Contributor Author

dcjones commented Jun 3, 2014

Tagging as 0.3 just to pester you. 😉

It's ok if this doesn't make 0.3, but please consider before tagging.

@@ -480,7 +500,13 @@ static jl_value_t *eval_body(jl_array_t *stmts, jl_value_t **locals, size_t nl,
}
if (jl_is_expr(stmt)) {
jl_sym_t *head = ((jl_expr_t*)stmt)->head;
if (head == goto_ifnot_sym) {
if (head == symbolicgoto_sym) {
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? It looks like the front end converts all symbolicgotos to plain gotos.

@dcjones
Copy link
Contributor Author

dcjones commented Jun 3, 2014

The interpret.c changes were unnecessary, just leftovers from figuring the right way to do this. I removed them and rebased.

@JeffBezanson
Copy link
Sponsor Member

Ok, I think my only remaining concern here is exception handlers. Looking at the case for break in goto-form shows what's needed. Every statement has a handler-level, and if you branch from a higher handler level to a lower you need to do a (leave ,(- handler-level dest-handler-level)). If the destination handler level is greater, the goto should probably be disallowed.

The really hard problem is finally. For now, we might just have to live with finally blocks not running when you exit the try block with goto.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Jun 7, 2014

this appears to be missing the code for inlining these correctly

@JeffBezanson
Copy link
Sponsor Member

I don't think it matters, since these are lowered to normal gotos.
On Jun 7, 2014 4:34 PM, "Jameson Nash" notifications@github.com wrote:

this appears to be missing the code for inlining these correctly


Reply to this email directly or view it on GitHub
#5699 (comment).

@dcjones
Copy link
Contributor Author

dcjones commented Jun 9, 2014

How's that?

It will throw an error if jumping into a try/catch block, and generate the correct leave expression when jumping out now.

@Keno
Copy link
Member

Keno commented Jun 9, 2014

What about finally blocks? I'm in favor of throwing an error for now when jumping out of finally blocks.

@dcjones
Copy link
Contributor Author

dcjones commented Jun 9, 2014

Oh, right. Goto out of a finally throws an error now.

@Keno
Copy link
Member

Keno commented Jun 9, 2014

Not exactly what I meant:

try 
@goto a 
finally
println("I won't be executed")
end
@label a

@dcjones
Copy link
Contributor Author

dcjones commented Jun 9, 2014

Ah, that makes more sense.

@jakebolewski
Copy link
Member

Does this work with nested try/catch/finally blocks?

@dcjones
Copy link
Contributor Author

dcjones commented Jun 9, 2014

You mean detecting gotos that skip finally blocks? It should. All these examples correctly throw errors.

try
    try
        @goto a
    catch
    finally
    end
    @label a
catch
finally
end

try
    try
        @goto a
    catch
    finally
    end
catch
finally
end
@label a

try
    @goto a
    try
    catch
    finally
    end
catch
finally
end
@label a

@quinnj
Copy link
Member

quinnj commented Jun 16, 2014

Bump. One of the remaining v0.3 issues.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Jun 16, 2014

I'm still not convinced that this feature needs to be in 0.3

@StefanKarpinski
Copy link
Sponsor Member

I'm not either, but it seems so very close. The only issue is jumping out of a try/finally, which I don't think we need to support immediately.

@dcjones
Copy link
Contributor Author

dcjones commented Jun 16, 2014

Obviously goto statements aren't the mission-critical feature that everyone's been pining for. But I think this PR is pretty solid now, it should not effect existing behavior, and the addition of goto statements will enable some pretty nice things: notably, non-standard control-flow constructs implemented as macros (e.g. switch statements, multilevel breaks), and very fast pure julia parsers (as mentioned, I've already written a julia ragel backend but need this PR to actually use it).

@@ -77,6 +77,7 @@ jl_sym_t *line_sym; jl_sym_t *jl_incomplete_sym;
// head symbols for each expression type
jl_sym_t *goto_sym; jl_sym_t *goto_ifnot_sym;
jl_sym_t *label_sym; jl_sym_t *return_sym;
jl_sym_t *symboliclabel_sym; jl_sym_t *symbolicgoto_sym;
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like these are now unused?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I've removed them now.

@JeffBezanson
Copy link
Sponsor Member

While it does seem strange to add a new control flow construct at this point, the implementation is now pretty much impeccable. After all of @dcjones ' work on this, I'm inclined to merge it.

@StefanKarpinski
Copy link
Sponsor Member

+1

@Keno
Copy link
Member

Keno commented Jun 16, 2014

Yes, I think we can merge this as well.

JeffBezanson added a commit that referenced this pull request Jun 18, 2014
Add labels and gotos. (Fixes #101)
@JeffBezanson JeffBezanson merged commit 5c01387 into JuliaLang:master Jun 18, 2014
@timholy
Copy link
Sponsor Member

timholy commented Jun 18, 2014

Nice! Looking forward to trying it.

@quinnj
Copy link
Member

quinnj commented Jun 18, 2014

Hey me too! Time to convert all my for and while loops to gotos! :P

@ivarne
Copy link
Sponsor Member

ivarne commented Jun 18, 2014

@quinnj Don't forget the else blocks.

@StefanKarpinski
Copy link
Sponsor Member

Am I doing something really obvious wrong?

julia> function foo()
           @label top
           x = 0
           println(x)
           x += 1
           if x < 10
               @goto top
           end
       end
foo (generic function with 1 method)

julia> foo()
ERROR: error compiling foo: unsupported or misplaced expression symbolicgoto in function foo

@dcjones
Copy link
Contributor Author

dcjones commented Jun 18, 2014

It looks like there is an issue with @goto being the last expression in a function. This works:

function foo()
    @label top
    x = 0
    println(x)
    x += 1
    if x < 10
        @goto top
    end
    return
end

I'll see if I can figure out the correct behavior. I guess a special case is needed for dealing with implicit returns.

@StefanKarpinski
Copy link
Sponsor Member

Funny how different people hit entirely different use cases naturally. The first several things I tried all resulted in this problem – clearly that didn't ever happen while you were testing.

tkelman added a commit that referenced this pull request Jul 1, 2014
apparently missed from #5699
wlbksy referenced this pull request in JuliaCN/julia_zh_cn Jul 3, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.